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