1use crate::{
11 errors::RecordReadError,
12 list::{OwnedTestInstanceId, TestInstanceId, TestList},
13 output_spec::{LiveSpec, RecordingSpec},
14 record::{
15 CoreEventKind, OutputEventKind, OutputFileName, StoreReader, StressConditionSummary,
16 StressIndexSummary, TestEventKindSummary, TestEventSummary, ZipStoreOutput,
17 ZipStoreOutputDescription,
18 },
19 reporter::events::{
20 ChildExecutionOutputDescription, ChildOutputDescription, ExecuteStatus, ExecutionStatuses,
21 RunStats, SetupScriptExecuteStatus, StressIndex, TestEvent, TestEventKind, TestsNotSeen,
22 },
23 run_mode::NextestRunMode,
24 runner::{StressCondition, StressCount},
25 test_output::ChildSingleOutput,
26};
27use bytes::Bytes;
28use nextest_metadata::{RustBinaryId, TestCaseName};
29use std::{collections::HashSet, num::NonZero};
30
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33pub enum LoadOutput {
34 Load,
36 Skip,
38}
39
40pub struct ReplayContext<'a> {
48 test_data: HashSet<OwnedTestInstanceId>,
50
51 test_list: &'a TestList<'a>,
53}
54
55impl<'a> ReplayContext<'a> {
56 pub fn new(test_list: &'a TestList<'a>) -> Self {
61 Self {
62 test_data: HashSet::new(),
63 test_list,
64 }
65 }
66
67 pub fn mode(&self) -> NextestRunMode {
69 self.test_list.mode()
70 }
71
72 pub fn register_test(&mut self, test_instance: OwnedTestInstanceId) {
77 self.test_data.insert(test_instance);
78 }
79
80 pub fn lookup_test_instance_id(
84 &self,
85 test_instance: &OwnedTestInstanceId,
86 ) -> Option<TestInstanceId<'_>> {
87 self.test_data.get(test_instance).map(|data| data.as_ref())
88 }
89
90 pub fn convert_event<'cx>(
95 &'cx self,
96 summary: &TestEventSummary<RecordingSpec>,
97 reader: &mut dyn StoreReader,
98 load_output: LoadOutput,
99 ) -> Result<TestEvent<'cx>, ReplayConversionError> {
100 let kind = self.convert_event_kind(&summary.kind, reader, load_output)?;
101 Ok(TestEvent {
102 timestamp: summary.timestamp,
103 elapsed: summary.elapsed,
104 kind,
105 })
106 }
107
108 fn convert_event_kind<'cx>(
109 &'cx self,
110 kind: &TestEventKindSummary<RecordingSpec>,
111 reader: &mut dyn StoreReader,
112 load_output: LoadOutput,
113 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
114 match kind {
115 TestEventKindSummary::Core(core) => self.convert_core_event(core),
116 TestEventKindSummary::Output(output) => {
117 self.convert_output_event(output, reader, load_output)
118 }
119 }
120 }
121
122 fn convert_core_event<'cx>(
123 &'cx self,
124 kind: &CoreEventKind,
125 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
126 match kind {
127 CoreEventKind::RunStarted {
128 run_id,
129 profile_name,
130 cli_args,
131 stress_condition,
132 } => {
133 let stress_condition = stress_condition
134 .as_ref()
135 .map(convert_stress_condition)
136 .transpose()?;
137 Ok(TestEventKind::RunStarted {
138 test_list: self.test_list,
139 run_id: *run_id,
140 profile_name: profile_name.clone(),
141 cli_args: cli_args.clone(),
142 stress_condition,
143 })
144 }
145
146 CoreEventKind::StressSubRunStarted { progress } => {
147 Ok(TestEventKind::StressSubRunStarted {
148 progress: *progress,
149 })
150 }
151
152 CoreEventKind::SetupScriptStarted {
153 stress_index,
154 index,
155 total,
156 script_id,
157 program,
158 args,
159 no_capture,
160 } => Ok(TestEventKind::SetupScriptStarted {
161 stress_index: stress_index.as_ref().map(convert_stress_index),
162 index: *index,
163 total: *total,
164 script_id: script_id.clone(),
165 program: program.clone(),
166 args: args.clone(),
167 no_capture: *no_capture,
168 }),
169
170 CoreEventKind::SetupScriptSlow {
171 stress_index,
172 script_id,
173 program,
174 args,
175 elapsed,
176 will_terminate,
177 } => Ok(TestEventKind::SetupScriptSlow {
178 stress_index: stress_index.as_ref().map(convert_stress_index),
179 script_id: script_id.clone(),
180 program: program.clone(),
181 args: args.clone(),
182 elapsed: *elapsed,
183 will_terminate: *will_terminate,
184 }),
185
186 CoreEventKind::TestStarted {
187 stress_index,
188 test_instance,
189 slot_assignment,
190 current_stats,
191 running,
192 command_line,
193 } => {
194 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
195 ReplayConversionError::TestNotFound {
196 binary_id: test_instance.binary_id.clone(),
197 test_name: test_instance.test_name.clone(),
198 }
199 })?;
200 Ok(TestEventKind::TestStarted {
201 stress_index: stress_index.as_ref().map(convert_stress_index),
202 test_instance: instance_id,
203 slot_assignment: slot_assignment.clone(),
204 current_stats: *current_stats,
205 running: *running,
206 command_line: command_line.clone(),
207 })
208 }
209
210 CoreEventKind::TestSlow {
211 stress_index,
212 test_instance,
213 retry_data,
214 elapsed,
215 will_terminate,
216 } => {
217 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
218 ReplayConversionError::TestNotFound {
219 binary_id: test_instance.binary_id.clone(),
220 test_name: test_instance.test_name.clone(),
221 }
222 })?;
223 Ok(TestEventKind::TestSlow {
224 stress_index: stress_index.as_ref().map(convert_stress_index),
225 test_instance: instance_id,
226 retry_data: *retry_data,
227 elapsed: *elapsed,
228 will_terminate: *will_terminate,
229 })
230 }
231
232 CoreEventKind::TestRetryStarted {
233 stress_index,
234 test_instance,
235 slot_assignment,
236 retry_data,
237 running,
238 command_line,
239 } => {
240 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
241 ReplayConversionError::TestNotFound {
242 binary_id: test_instance.binary_id.clone(),
243 test_name: test_instance.test_name.clone(),
244 }
245 })?;
246 Ok(TestEventKind::TestRetryStarted {
247 stress_index: stress_index.as_ref().map(convert_stress_index),
248 test_instance: instance_id,
249 slot_assignment: slot_assignment.clone(),
250 retry_data: *retry_data,
251 running: *running,
252 command_line: command_line.clone(),
253 })
254 }
255
256 CoreEventKind::TestSkipped {
257 stress_index,
258 test_instance,
259 reason,
260 junit_report_skipped,
261 } => {
262 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
263 ReplayConversionError::TestNotFound {
264 binary_id: test_instance.binary_id.clone(),
265 test_name: test_instance.test_name.clone(),
266 }
267 })?;
268 Ok(TestEventKind::TestSkipped {
269 stress_index: stress_index.as_ref().map(convert_stress_index),
270 test_instance: instance_id,
271 reason: *reason,
272 junit_report_skipped: *junit_report_skipped,
273 })
274 }
275
276 CoreEventKind::RunBeginCancel {
277 setup_scripts_running,
278 running,
279 reason,
280 } => {
281 let stats = RunStats {
282 cancel_reason: Some(*reason),
283 ..Default::default()
284 };
285 Ok(TestEventKind::RunBeginCancel {
286 setup_scripts_running: *setup_scripts_running,
287 current_stats: stats,
288 running: *running,
289 })
290 }
291
292 CoreEventKind::RunPaused {
293 setup_scripts_running,
294 running,
295 } => Ok(TestEventKind::RunPaused {
296 setup_scripts_running: *setup_scripts_running,
297 running: *running,
298 }),
299
300 CoreEventKind::RunContinued {
301 setup_scripts_running,
302 running,
303 } => Ok(TestEventKind::RunContinued {
304 setup_scripts_running: *setup_scripts_running,
305 running: *running,
306 }),
307
308 CoreEventKind::StressSubRunFinished {
309 progress,
310 sub_elapsed,
311 sub_stats,
312 } => Ok(TestEventKind::StressSubRunFinished {
313 progress: *progress,
314 sub_elapsed: *sub_elapsed,
315 sub_stats: *sub_stats,
316 }),
317
318 CoreEventKind::RunFinished {
319 run_id,
320 start_time,
321 elapsed,
322 run_stats,
323 outstanding_not_seen,
324 } => Ok(TestEventKind::RunFinished {
325 run_id: *run_id,
326 start_time: *start_time,
327 elapsed: *elapsed,
328 run_stats: *run_stats,
329 outstanding_not_seen: outstanding_not_seen.as_ref().map(|t| TestsNotSeen {
330 not_seen: t.not_seen.clone(),
331 total_not_seen: t.total_not_seen,
332 }),
333 }),
334 }
335 }
336
337 fn convert_output_event<'cx>(
338 &'cx self,
339 kind: &OutputEventKind<RecordingSpec>,
340 reader: &mut dyn StoreReader,
341 load_output: LoadOutput,
342 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
343 match kind {
344 OutputEventKind::SetupScriptFinished {
345 stress_index,
346 index,
347 total,
348 script_id,
349 program,
350 args,
351 no_capture,
352 run_status,
353 } => Ok(TestEventKind::SetupScriptFinished {
354 stress_index: stress_index.as_ref().map(convert_stress_index),
355 index: *index,
356 total: *total,
357 script_id: script_id.clone(),
358 program: program.clone(),
359 args: args.clone(),
360 junit_store_success_output: false,
361 junit_store_failure_output: false,
362 no_capture: *no_capture,
363 run_status: convert_setup_script_status(run_status, reader, load_output)?,
364 }),
365
366 OutputEventKind::TestAttemptFailedWillRetry {
367 stress_index,
368 test_instance,
369 run_status,
370 delay_before_next_attempt,
371 failure_output,
372 running,
373 } => {
374 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
375 ReplayConversionError::TestNotFound {
376 binary_id: test_instance.binary_id.clone(),
377 test_name: test_instance.test_name.clone(),
378 }
379 })?;
380 Ok(TestEventKind::TestAttemptFailedWillRetry {
381 stress_index: stress_index.as_ref().map(convert_stress_index),
382 test_instance: instance_id,
383 run_status: convert_execute_status(run_status, reader, load_output)?,
384 delay_before_next_attempt: *delay_before_next_attempt,
385 failure_output: *failure_output,
386 running: *running,
387 })
388 }
389
390 OutputEventKind::TestFinished {
391 stress_index,
392 test_instance,
393 success_output,
394 failure_output,
395 junit_store_success_output,
396 junit_store_failure_output,
397 junit_flaky_fail_status,
398 run_statuses,
399 current_stats,
400 running,
401 } => {
402 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
403 ReplayConversionError::TestNotFound {
404 binary_id: test_instance.binary_id.clone(),
405 test_name: test_instance.test_name.clone(),
406 }
407 })?;
408 Ok(TestEventKind::TestFinished {
409 stress_index: stress_index.as_ref().map(convert_stress_index),
410 test_instance: instance_id,
411 success_output: *success_output,
412 failure_output: *failure_output,
413 junit_store_success_output: *junit_store_success_output,
414 junit_store_failure_output: *junit_store_failure_output,
415 junit_flaky_fail_status: *junit_flaky_fail_status,
416 run_statuses: convert_execution_statuses(run_statuses, reader, load_output)?,
417 current_stats: *current_stats,
418 running: *running,
419 })
420 }
421 }
422 }
423}
424
425#[derive(Debug, thiserror::Error)]
427#[non_exhaustive]
428pub enum ReplayConversionError {
429 #[error("test not found under `{binary_id}`: {test_name}")]
431 TestNotFound {
432 binary_id: RustBinaryId,
434 test_name: TestCaseName,
436 },
437
438 #[error("error reading record")]
440 RecordRead(#[from] RecordReadError),
441
442 #[error("invalid stress count: expected non-zero value, got 0")]
444 InvalidStressCount,
445}
446
447fn convert_stress_condition(
450 summary: &StressConditionSummary,
451) -> Result<StressCondition, ReplayConversionError> {
452 match summary {
453 StressConditionSummary::Count { count } => {
454 let stress_count = match count {
455 Some(n) => {
456 let non_zero =
457 NonZero::new(*n).ok_or(ReplayConversionError::InvalidStressCount)?;
458 StressCount::Count { count: non_zero }
459 }
460 None => StressCount::Infinite,
461 };
462 Ok(StressCondition::Count(stress_count))
463 }
464 StressConditionSummary::Duration { duration } => Ok(StressCondition::Duration(*duration)),
465 }
466}
467
468fn convert_stress_index(summary: &StressIndexSummary) -> StressIndex {
469 StressIndex {
470 current: summary.current,
471 total: summary.total,
472 }
473}
474
475fn convert_execute_status(
476 status: &ExecuteStatus<RecordingSpec>,
477 reader: &mut dyn StoreReader,
478 load_output: LoadOutput,
479) -> Result<ExecuteStatus<LiveSpec>, ReplayConversionError> {
480 let output = convert_child_execution_output(&status.output, reader, load_output)?;
481 Ok(ExecuteStatus {
482 retry_data: status.retry_data,
483 output,
484 result: status.result.clone(),
485 start_time: status.start_time,
486 time_taken: status.time_taken,
487 is_slow: status.is_slow,
488 delay_before_start: status.delay_before_start,
489 error_summary: status.error_summary.clone(),
490 output_error_slice: status.output_error_slice.clone(),
491 })
492}
493
494fn convert_execution_statuses(
495 statuses: &ExecutionStatuses<RecordingSpec>,
496 reader: &mut dyn StoreReader,
497 load_output: LoadOutput,
498) -> Result<ExecutionStatuses<LiveSpec>, ReplayConversionError> {
499 let flaky_result = statuses.flaky_result();
500 let statuses: Vec<ExecuteStatus<LiveSpec>> = statuses
501 .iter()
502 .map(|s| convert_execute_status(s, reader, load_output))
503 .collect::<Result<_, _>>()?;
504
505 Ok(ExecutionStatuses::new(statuses, flaky_result))
506}
507
508fn convert_setup_script_status(
509 status: &SetupScriptExecuteStatus<RecordingSpec>,
510 reader: &mut dyn StoreReader,
511 load_output: LoadOutput,
512) -> Result<SetupScriptExecuteStatus<LiveSpec>, ReplayConversionError> {
513 let output = convert_child_execution_output(&status.output, reader, load_output)?;
514 Ok(SetupScriptExecuteStatus {
515 output,
516 result: status.result.clone(),
517 start_time: status.start_time,
518 time_taken: status.time_taken,
519 is_slow: status.is_slow,
520 env_map: status.env_map.clone(),
521 error_summary: status.error_summary.clone(),
522 })
523}
524
525fn convert_child_execution_output(
526 output: &ChildExecutionOutputDescription<RecordingSpec>,
527 reader: &mut dyn StoreReader,
528 load_output: LoadOutput,
529) -> Result<ChildExecutionOutputDescription<LiveSpec>, ReplayConversionError> {
530 match output {
531 ChildExecutionOutputDescription::Output {
532 result,
533 output,
534 errors,
535 } => {
536 let output = convert_child_output(output, reader, load_output)?;
537 Ok(ChildExecutionOutputDescription::Output {
538 result: result.clone(),
539 output,
540 errors: errors.clone(),
541 })
542 }
543 ChildExecutionOutputDescription::StartError(err) => {
544 Ok(ChildExecutionOutputDescription::StartError(err.clone()))
545 }
546 }
547}
548
549fn convert_child_output(
550 output: &ZipStoreOutputDescription,
551 reader: &mut dyn StoreReader,
552 load_output: LoadOutput,
553) -> Result<ChildOutputDescription, ReplayConversionError> {
554 if load_output == LoadOutput::Skip {
555 return Ok(ChildOutputDescription::NotLoaded);
556 }
557
558 match output {
559 ZipStoreOutputDescription::Split { stdout, stderr } => {
560 let stdout = stdout
561 .as_ref()
562 .map(|o| read_output_as_child_single(reader, o))
563 .transpose()?;
564 let stderr = stderr
565 .as_ref()
566 .map(|o| read_output_as_child_single(reader, o))
567 .transpose()?;
568 Ok(ChildOutputDescription::Split { stdout, stderr })
569 }
570 ZipStoreOutputDescription::Combined { output } => {
571 let output = read_output_as_child_single(reader, output)?;
572 Ok(ChildOutputDescription::Combined { output })
573 }
574 }
575}
576
577fn read_output_as_child_single(
578 reader: &mut dyn StoreReader,
579 output: &ZipStoreOutput,
580) -> Result<ChildSingleOutput, ReplayConversionError> {
581 let bytes = read_output_file(reader, output.file_name().map(OutputFileName::as_str))?;
582 Ok(ChildSingleOutput::from(bytes.unwrap_or_default()))
583}
584
585fn read_output_file(
586 reader: &mut dyn StoreReader,
587 file_name: Option<&str>,
588) -> Result<Option<Bytes>, ReplayConversionError> {
589 match file_name {
590 Some(name) => {
591 let bytes = reader.read_output(name)?;
592 Ok(Some(Bytes::from(bytes)))
593 }
594 None => Ok(None),
595 }
596}
597
598use crate::{
601 config::overrides::CompiledDefaultFilter,
602 errors::WriteEventError,
603 helpers::progress::ShowTerminalProgress,
604 record::{
605 run_id_index::{RunIdIndex, ShortestRunIdPrefix},
606 store::{RecordedRunInfo, RecordedRunStatus},
607 },
608 redact::Redactor,
609 reporter::{
610 DisplayConfig, DisplayReporter, DisplayReporterBuilder, DisplayerKind, FinalStatusLevel,
611 MaxProgressRunning, OutputLoadDecider, ReporterOutput, ShowProgress, StatusLevel,
612 TestOutputDisplay,
613 },
614};
615use chrono::{DateTime, FixedOffset};
616use quick_junit::ReportUuid;
617
618#[derive(Clone, Debug)]
623pub struct ReplayHeader {
624 pub run_id: ReportUuid,
626 pub unique_prefix: Option<ShortestRunIdPrefix>,
631 pub started_at: DateTime<FixedOffset>,
633 pub status: RecordedRunStatus,
635}
636
637impl ReplayHeader {
638 pub fn new(
644 run_id: ReportUuid,
645 run_info: &RecordedRunInfo,
646 run_id_index: Option<&RunIdIndex>,
647 ) -> Self {
648 let unique_prefix = run_id_index.and_then(|index| index.shortest_unique_prefix(run_id));
649 Self {
650 run_id,
651 unique_prefix,
652 started_at: run_info.started_at,
653 status: run_info.status.clone(),
654 }
655 }
656}
657
658#[derive(Debug)]
660pub struct ReplayReporterBuilder {
661 status_level: StatusLevel,
662 final_status_level: FinalStatusLevel,
663 success_output: Option<TestOutputDisplay>,
664 failure_output: Option<TestOutputDisplay>,
665 should_colorize: bool,
666 verbose: bool,
667 show_progress: ShowProgress,
668 max_progress_running: MaxProgressRunning,
669 no_output_indent: bool,
670 redactor: Redactor,
671}
672
673impl Default for ReplayReporterBuilder {
674 fn default() -> Self {
675 Self {
676 status_level: StatusLevel::Pass,
677 final_status_level: FinalStatusLevel::Fail,
678 success_output: None,
679 failure_output: None,
680 should_colorize: false,
681 verbose: false,
682 show_progress: ShowProgress::default(),
683 max_progress_running: MaxProgressRunning::default(),
684 no_output_indent: false,
685 redactor: Redactor::noop(),
686 }
687 }
688}
689
690impl ReplayReporterBuilder {
691 pub fn new() -> Self {
693 Self::default()
694 }
695
696 pub fn set_status_level(&mut self, status_level: StatusLevel) -> &mut Self {
698 self.status_level = status_level;
699 self
700 }
701
702 pub fn set_final_status_level(&mut self, final_status_level: FinalStatusLevel) -> &mut Self {
704 self.final_status_level = final_status_level;
705 self
706 }
707
708 pub fn set_success_output(&mut self, output: TestOutputDisplay) -> &mut Self {
710 self.success_output = Some(output);
711 self
712 }
713
714 pub fn set_failure_output(&mut self, output: TestOutputDisplay) -> &mut Self {
716 self.failure_output = Some(output);
717 self
718 }
719
720 pub fn set_colorize(&mut self, colorize: bool) -> &mut Self {
722 self.should_colorize = colorize;
723 self
724 }
725
726 pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
728 self.verbose = verbose;
729 self
730 }
731
732 pub fn set_show_progress(&mut self, show_progress: ShowProgress) -> &mut Self {
734 self.show_progress = show_progress;
735 self
736 }
737
738 pub fn set_max_progress_running(
740 &mut self,
741 max_progress_running: MaxProgressRunning,
742 ) -> &mut Self {
743 self.max_progress_running = max_progress_running;
744 self
745 }
746
747 pub fn set_no_output_indent(&mut self, no_output_indent: bool) -> &mut Self {
749 self.no_output_indent = no_output_indent;
750 self
751 }
752
753 pub fn set_redactor(&mut self, redactor: Redactor) -> &mut Self {
755 self.redactor = redactor;
756 self
757 }
758
759 pub fn build<'a>(
761 self,
762 mode: NextestRunMode,
763 run_count: usize,
764 output: ReporterOutput<'a>,
765 ) -> ReplayReporter<'a> {
766 let display_reporter = DisplayReporterBuilder {
767 mode,
768 default_filter: CompiledDefaultFilter::for_default_config(),
769 display_config: DisplayConfig::with_overrides(
770 self.show_progress,
771 false, self.status_level,
773 self.final_status_level,
774 ),
775 run_count,
776 success_output: self.success_output,
777 failure_output: self.failure_output,
778 should_colorize: self.should_colorize,
779 verbose: self.verbose,
780 no_output_indent: self.no_output_indent,
781 max_progress_running: self.max_progress_running,
782 show_term_progress: ShowTerminalProgress::No,
785 displayer_kind: DisplayerKind::Replay,
786 redactor: self.redactor,
787 }
788 .build(output);
789
790 ReplayReporter { display_reporter }
791 }
792}
793
794pub struct ReplayReporter<'a> {
804 display_reporter: DisplayReporter<'a>,
805}
806
807impl<'a> ReplayReporter<'a> {
808 pub fn output_load_decider(&self) -> OutputLoadDecider {
814 self.display_reporter.output_load_decider()
815 }
816
817 pub fn write_header(&mut self, header: &ReplayHeader) -> Result<(), WriteEventError> {
822 self.display_reporter.write_replay_header(header)
823 }
824
825 pub fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
827 self.display_reporter.write_event(event)
828 }
829
830 pub fn finish(mut self) {
832 self.display_reporter.finish();
833 }
834}