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 } => {
261 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
262 ReplayConversionError::TestNotFound {
263 binary_id: test_instance.binary_id.clone(),
264 test_name: test_instance.test_name.clone(),
265 }
266 })?;
267 Ok(TestEventKind::TestSkipped {
268 stress_index: stress_index.as_ref().map(convert_stress_index),
269 test_instance: instance_id,
270 reason: *reason,
271 })
272 }
273
274 CoreEventKind::RunBeginCancel {
275 setup_scripts_running,
276 running,
277 reason,
278 } => {
279 let stats = RunStats {
280 cancel_reason: Some(*reason),
281 ..Default::default()
282 };
283 Ok(TestEventKind::RunBeginCancel {
284 setup_scripts_running: *setup_scripts_running,
285 current_stats: stats,
286 running: *running,
287 })
288 }
289
290 CoreEventKind::RunPaused {
291 setup_scripts_running,
292 running,
293 } => Ok(TestEventKind::RunPaused {
294 setup_scripts_running: *setup_scripts_running,
295 running: *running,
296 }),
297
298 CoreEventKind::RunContinued {
299 setup_scripts_running,
300 running,
301 } => Ok(TestEventKind::RunContinued {
302 setup_scripts_running: *setup_scripts_running,
303 running: *running,
304 }),
305
306 CoreEventKind::StressSubRunFinished {
307 progress,
308 sub_elapsed,
309 sub_stats,
310 } => Ok(TestEventKind::StressSubRunFinished {
311 progress: *progress,
312 sub_elapsed: *sub_elapsed,
313 sub_stats: *sub_stats,
314 }),
315
316 CoreEventKind::RunFinished {
317 run_id,
318 start_time,
319 elapsed,
320 run_stats,
321 outstanding_not_seen,
322 } => Ok(TestEventKind::RunFinished {
323 run_id: *run_id,
324 start_time: *start_time,
325 elapsed: *elapsed,
326 run_stats: *run_stats,
327 outstanding_not_seen: outstanding_not_seen.as_ref().map(|t| TestsNotSeen {
328 not_seen: t.not_seen.clone(),
329 total_not_seen: t.total_not_seen,
330 }),
331 }),
332 }
333 }
334
335 fn convert_output_event<'cx>(
336 &'cx self,
337 kind: &OutputEventKind<RecordingSpec>,
338 reader: &mut dyn StoreReader,
339 load_output: LoadOutput,
340 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
341 match kind {
342 OutputEventKind::SetupScriptFinished {
343 stress_index,
344 index,
345 total,
346 script_id,
347 program,
348 args,
349 no_capture,
350 run_status,
351 } => Ok(TestEventKind::SetupScriptFinished {
352 stress_index: stress_index.as_ref().map(convert_stress_index),
353 index: *index,
354 total: *total,
355 script_id: script_id.clone(),
356 program: program.clone(),
357 args: args.clone(),
358 junit_store_success_output: false,
359 junit_store_failure_output: false,
360 no_capture: *no_capture,
361 run_status: convert_setup_script_status(run_status, reader, load_output)?,
362 }),
363
364 OutputEventKind::TestAttemptFailedWillRetry {
365 stress_index,
366 test_instance,
367 run_status,
368 delay_before_next_attempt,
369 failure_output,
370 running,
371 } => {
372 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
373 ReplayConversionError::TestNotFound {
374 binary_id: test_instance.binary_id.clone(),
375 test_name: test_instance.test_name.clone(),
376 }
377 })?;
378 Ok(TestEventKind::TestAttemptFailedWillRetry {
379 stress_index: stress_index.as_ref().map(convert_stress_index),
380 test_instance: instance_id,
381 run_status: convert_execute_status(run_status, reader, load_output)?,
382 delay_before_next_attempt: *delay_before_next_attempt,
383 failure_output: *failure_output,
384 running: *running,
385 })
386 }
387
388 OutputEventKind::TestFinished {
389 stress_index,
390 test_instance,
391 success_output,
392 failure_output,
393 junit_store_success_output,
394 junit_store_failure_output,
395 junit_flaky_fail_status,
396 run_statuses,
397 current_stats,
398 running,
399 } => {
400 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
401 ReplayConversionError::TestNotFound {
402 binary_id: test_instance.binary_id.clone(),
403 test_name: test_instance.test_name.clone(),
404 }
405 })?;
406 Ok(TestEventKind::TestFinished {
407 stress_index: stress_index.as_ref().map(convert_stress_index),
408 test_instance: instance_id,
409 success_output: *success_output,
410 failure_output: *failure_output,
411 junit_store_success_output: *junit_store_success_output,
412 junit_store_failure_output: *junit_store_failure_output,
413 junit_flaky_fail_status: *junit_flaky_fail_status,
414 run_statuses: convert_execution_statuses(run_statuses, reader, load_output)?,
415 current_stats: *current_stats,
416 running: *running,
417 })
418 }
419 }
420 }
421}
422
423#[derive(Debug, thiserror::Error)]
425#[non_exhaustive]
426pub enum ReplayConversionError {
427 #[error("test not found under `{binary_id}`: {test_name}")]
429 TestNotFound {
430 binary_id: RustBinaryId,
432 test_name: TestCaseName,
434 },
435
436 #[error("error reading record")]
438 RecordRead(#[from] RecordReadError),
439
440 #[error("invalid stress count: expected non-zero value, got 0")]
442 InvalidStressCount,
443}
444
445fn convert_stress_condition(
448 summary: &StressConditionSummary,
449) -> Result<StressCondition, ReplayConversionError> {
450 match summary {
451 StressConditionSummary::Count { count } => {
452 let stress_count = match count {
453 Some(n) => {
454 let non_zero =
455 NonZero::new(*n).ok_or(ReplayConversionError::InvalidStressCount)?;
456 StressCount::Count { count: non_zero }
457 }
458 None => StressCount::Infinite,
459 };
460 Ok(StressCondition::Count(stress_count))
461 }
462 StressConditionSummary::Duration { duration } => Ok(StressCondition::Duration(*duration)),
463 }
464}
465
466fn convert_stress_index(summary: &StressIndexSummary) -> StressIndex {
467 StressIndex {
468 current: summary.current,
469 total: summary.total,
470 }
471}
472
473fn convert_execute_status(
474 status: &ExecuteStatus<RecordingSpec>,
475 reader: &mut dyn StoreReader,
476 load_output: LoadOutput,
477) -> Result<ExecuteStatus<LiveSpec>, ReplayConversionError> {
478 let output = convert_child_execution_output(&status.output, reader, load_output)?;
479 Ok(ExecuteStatus {
480 retry_data: status.retry_data,
481 output,
482 result: status.result.clone(),
483 start_time: status.start_time,
484 time_taken: status.time_taken,
485 is_slow: status.is_slow,
486 delay_before_start: status.delay_before_start,
487 error_summary: status.error_summary.clone(),
488 output_error_slice: status.output_error_slice.clone(),
489 })
490}
491
492fn convert_execution_statuses(
493 statuses: &ExecutionStatuses<RecordingSpec>,
494 reader: &mut dyn StoreReader,
495 load_output: LoadOutput,
496) -> Result<ExecutionStatuses<LiveSpec>, ReplayConversionError> {
497 let flaky_result = statuses.flaky_result();
498 let statuses: Vec<ExecuteStatus<LiveSpec>> = statuses
499 .iter()
500 .map(|s| convert_execute_status(s, reader, load_output))
501 .collect::<Result<_, _>>()?;
502
503 Ok(ExecutionStatuses::new(statuses, flaky_result))
504}
505
506fn convert_setup_script_status(
507 status: &SetupScriptExecuteStatus<RecordingSpec>,
508 reader: &mut dyn StoreReader,
509 load_output: LoadOutput,
510) -> Result<SetupScriptExecuteStatus<LiveSpec>, ReplayConversionError> {
511 let output = convert_child_execution_output(&status.output, reader, load_output)?;
512 Ok(SetupScriptExecuteStatus {
513 output,
514 result: status.result.clone(),
515 start_time: status.start_time,
516 time_taken: status.time_taken,
517 is_slow: status.is_slow,
518 env_map: status.env_map.clone(),
519 error_summary: status.error_summary.clone(),
520 })
521}
522
523fn convert_child_execution_output(
524 output: &ChildExecutionOutputDescription<RecordingSpec>,
525 reader: &mut dyn StoreReader,
526 load_output: LoadOutput,
527) -> Result<ChildExecutionOutputDescription<LiveSpec>, ReplayConversionError> {
528 match output {
529 ChildExecutionOutputDescription::Output {
530 result,
531 output,
532 errors,
533 } => {
534 let output = convert_child_output(output, reader, load_output)?;
535 Ok(ChildExecutionOutputDescription::Output {
536 result: result.clone(),
537 output,
538 errors: errors.clone(),
539 })
540 }
541 ChildExecutionOutputDescription::StartError(err) => {
542 Ok(ChildExecutionOutputDescription::StartError(err.clone()))
543 }
544 }
545}
546
547fn convert_child_output(
548 output: &ZipStoreOutputDescription,
549 reader: &mut dyn StoreReader,
550 load_output: LoadOutput,
551) -> Result<ChildOutputDescription, ReplayConversionError> {
552 if load_output == LoadOutput::Skip {
553 return Ok(ChildOutputDescription::NotLoaded);
554 }
555
556 match output {
557 ZipStoreOutputDescription::Split { stdout, stderr } => {
558 let stdout = stdout
559 .as_ref()
560 .map(|o| read_output_as_child_single(reader, o))
561 .transpose()?;
562 let stderr = stderr
563 .as_ref()
564 .map(|o| read_output_as_child_single(reader, o))
565 .transpose()?;
566 Ok(ChildOutputDescription::Split { stdout, stderr })
567 }
568 ZipStoreOutputDescription::Combined { output } => {
569 let output = read_output_as_child_single(reader, output)?;
570 Ok(ChildOutputDescription::Combined { output })
571 }
572 }
573}
574
575fn read_output_as_child_single(
576 reader: &mut dyn StoreReader,
577 output: &ZipStoreOutput,
578) -> Result<ChildSingleOutput, ReplayConversionError> {
579 let bytes = read_output_file(reader, output.file_name().map(OutputFileName::as_str))?;
580 Ok(ChildSingleOutput::from(bytes.unwrap_or_default()))
581}
582
583fn read_output_file(
584 reader: &mut dyn StoreReader,
585 file_name: Option<&str>,
586) -> Result<Option<Bytes>, ReplayConversionError> {
587 match file_name {
588 Some(name) => {
589 let bytes = reader.read_output(name)?;
590 Ok(Some(Bytes::from(bytes)))
591 }
592 None => Ok(None),
593 }
594}
595
596use crate::{
599 config::overrides::CompiledDefaultFilter,
600 errors::WriteEventError,
601 record::{
602 run_id_index::{RunIdIndex, ShortestRunIdPrefix},
603 store::{RecordedRunInfo, RecordedRunStatus},
604 },
605 redact::Redactor,
606 reporter::{
607 DisplayConfig, DisplayReporter, DisplayReporterBuilder, DisplayerKind, FinalStatusLevel,
608 MaxProgressRunning, OutputLoadDecider, ReporterOutput, ShowProgress, ShowTerminalProgress,
609 StatusLevel, TestOutputDisplay,
610 },
611};
612use chrono::{DateTime, FixedOffset};
613use quick_junit::ReportUuid;
614
615#[derive(Clone, Debug)]
620pub struct ReplayHeader {
621 pub run_id: ReportUuid,
623 pub unique_prefix: Option<ShortestRunIdPrefix>,
628 pub started_at: DateTime<FixedOffset>,
630 pub status: RecordedRunStatus,
632}
633
634impl ReplayHeader {
635 pub fn new(
641 run_id: ReportUuid,
642 run_info: &RecordedRunInfo,
643 run_id_index: Option<&RunIdIndex>,
644 ) -> Self {
645 let unique_prefix = run_id_index.and_then(|index| index.shortest_unique_prefix(run_id));
646 Self {
647 run_id,
648 unique_prefix,
649 started_at: run_info.started_at,
650 status: run_info.status.clone(),
651 }
652 }
653}
654
655#[derive(Debug)]
657pub struct ReplayReporterBuilder {
658 status_level: StatusLevel,
659 final_status_level: FinalStatusLevel,
660 success_output: Option<TestOutputDisplay>,
661 failure_output: Option<TestOutputDisplay>,
662 should_colorize: bool,
663 verbose: bool,
664 show_progress: ShowProgress,
665 max_progress_running: MaxProgressRunning,
666 no_output_indent: bool,
667 redactor: Redactor,
668}
669
670impl Default for ReplayReporterBuilder {
671 fn default() -> Self {
672 Self {
673 status_level: StatusLevel::Pass,
674 final_status_level: FinalStatusLevel::Fail,
675 success_output: None,
676 failure_output: None,
677 should_colorize: false,
678 verbose: false,
679 show_progress: ShowProgress::default(),
680 max_progress_running: MaxProgressRunning::default(),
681 no_output_indent: false,
682 redactor: Redactor::noop(),
683 }
684 }
685}
686
687impl ReplayReporterBuilder {
688 pub fn new() -> Self {
690 Self::default()
691 }
692
693 pub fn set_status_level(&mut self, status_level: StatusLevel) -> &mut Self {
695 self.status_level = status_level;
696 self
697 }
698
699 pub fn set_final_status_level(&mut self, final_status_level: FinalStatusLevel) -> &mut Self {
701 self.final_status_level = final_status_level;
702 self
703 }
704
705 pub fn set_success_output(&mut self, output: TestOutputDisplay) -> &mut Self {
707 self.success_output = Some(output);
708 self
709 }
710
711 pub fn set_failure_output(&mut self, output: TestOutputDisplay) -> &mut Self {
713 self.failure_output = Some(output);
714 self
715 }
716
717 pub fn set_colorize(&mut self, colorize: bool) -> &mut Self {
719 self.should_colorize = colorize;
720 self
721 }
722
723 pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
725 self.verbose = verbose;
726 self
727 }
728
729 pub fn set_show_progress(&mut self, show_progress: ShowProgress) -> &mut Self {
731 self.show_progress = show_progress;
732 self
733 }
734
735 pub fn set_max_progress_running(
737 &mut self,
738 max_progress_running: MaxProgressRunning,
739 ) -> &mut Self {
740 self.max_progress_running = max_progress_running;
741 self
742 }
743
744 pub fn set_no_output_indent(&mut self, no_output_indent: bool) -> &mut Self {
746 self.no_output_indent = no_output_indent;
747 self
748 }
749
750 pub fn set_redactor(&mut self, redactor: Redactor) -> &mut Self {
752 self.redactor = redactor;
753 self
754 }
755
756 pub fn build<'a>(
758 self,
759 mode: NextestRunMode,
760 run_count: usize,
761 output: ReporterOutput<'a>,
762 ) -> ReplayReporter<'a> {
763 let display_reporter = DisplayReporterBuilder {
764 mode,
765 default_filter: CompiledDefaultFilter::for_default_config(),
766 display_config: DisplayConfig::with_overrides(
767 self.show_progress,
768 false, self.status_level,
770 self.final_status_level,
771 ),
772 run_count,
773 success_output: self.success_output,
774 failure_output: self.failure_output,
775 should_colorize: self.should_colorize,
776 verbose: self.verbose,
777 no_output_indent: self.no_output_indent,
778 max_progress_running: self.max_progress_running,
779 show_term_progress: ShowTerminalProgress::No,
782 displayer_kind: DisplayerKind::Replay,
783 redactor: self.redactor,
784 }
785 .build(output);
786
787 ReplayReporter { display_reporter }
788 }
789}
790
791pub struct ReplayReporter<'a> {
801 display_reporter: DisplayReporter<'a>,
802}
803
804impl<'a> ReplayReporter<'a> {
805 pub fn output_load_decider(&self) -> OutputLoadDecider {
811 self.display_reporter.output_load_decider()
812 }
813
814 pub fn write_header(&mut self, header: &ReplayHeader) -> Result<(), WriteEventError> {
819 self.display_reporter.write_replay_header(header)
820 }
821
822 pub fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
824 self.display_reporter.write_event(event)
825 }
826
827 pub fn finish(mut self) {
829 self.display_reporter.finish();
830 }
831}