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 test_count(&self) -> usize {
74 self.test_list.test_count()
75 }
76
77 pub fn register_test(&mut self, test_instance: OwnedTestInstanceId) {
82 self.test_data.insert(test_instance);
83 }
84
85 pub fn lookup_test_instance_id(
89 &self,
90 test_instance: &OwnedTestInstanceId,
91 ) -> Option<TestInstanceId<'_>> {
92 self.test_data.get(test_instance).map(|data| data.as_ref())
93 }
94
95 pub fn convert_event<'cx>(
100 &'cx self,
101 summary: &TestEventSummary<RecordingSpec>,
102 reader: &mut dyn StoreReader,
103 load_output: LoadOutput,
104 ) -> Result<TestEvent<'cx>, ReplayConversionError> {
105 let kind = self.convert_event_kind(&summary.kind, reader, load_output)?;
106 Ok(TestEvent {
107 timestamp: summary.timestamp,
108 elapsed: summary.elapsed,
109 kind,
110 })
111 }
112
113 fn convert_event_kind<'cx>(
114 &'cx self,
115 kind: &TestEventKindSummary<RecordingSpec>,
116 reader: &mut dyn StoreReader,
117 load_output: LoadOutput,
118 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
119 match kind {
120 TestEventKindSummary::Core(core) => self.convert_core_event(core),
121 TestEventKindSummary::Output(output) => {
122 self.convert_output_event(output, reader, load_output)
123 }
124 }
125 }
126
127 fn convert_core_event<'cx>(
128 &'cx self,
129 kind: &CoreEventKind,
130 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
131 match kind {
132 CoreEventKind::RunStarted {
133 run_id,
134 profile_name,
135 cli_args,
136 stress_condition,
137 } => {
138 let stress_condition = stress_condition
139 .as_ref()
140 .map(convert_stress_condition)
141 .transpose()?;
142 Ok(TestEventKind::RunStarted {
143 test_list: self.test_list,
144 run_id: *run_id,
145 profile_name: profile_name.clone(),
146 cli_args: cli_args.clone(),
147 stress_condition,
148 })
149 }
150
151 CoreEventKind::StressSubRunStarted { progress } => {
152 Ok(TestEventKind::StressSubRunStarted {
153 progress: *progress,
154 })
155 }
156
157 CoreEventKind::SetupScriptStarted {
158 stress_index,
159 index,
160 total,
161 script_id,
162 program,
163 args,
164 no_capture,
165 } => Ok(TestEventKind::SetupScriptStarted {
166 stress_index: stress_index.as_ref().map(convert_stress_index),
167 index: *index,
168 total: *total,
169 script_id: script_id.clone(),
170 program: program.clone(),
171 args: args.clone(),
172 no_capture: *no_capture,
173 }),
174
175 CoreEventKind::SetupScriptSlow {
176 stress_index,
177 script_id,
178 program,
179 args,
180 elapsed,
181 will_terminate,
182 } => Ok(TestEventKind::SetupScriptSlow {
183 stress_index: stress_index.as_ref().map(convert_stress_index),
184 script_id: script_id.clone(),
185 program: program.clone(),
186 args: args.clone(),
187 elapsed: *elapsed,
188 will_terminate: *will_terminate,
189 }),
190
191 CoreEventKind::TestStarted {
192 stress_index,
193 test_instance,
194 current_stats,
195 running,
196 command_line,
197 } => {
198 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
199 ReplayConversionError::TestNotFound {
200 binary_id: test_instance.binary_id.clone(),
201 test_name: test_instance.test_name.clone(),
202 }
203 })?;
204 Ok(TestEventKind::TestStarted {
205 stress_index: stress_index.as_ref().map(convert_stress_index),
206 test_instance: instance_id,
207 current_stats: *current_stats,
208 running: *running,
209 command_line: command_line.clone(),
210 })
211 }
212
213 CoreEventKind::TestSlow {
214 stress_index,
215 test_instance,
216 retry_data,
217 elapsed,
218 will_terminate,
219 } => {
220 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
221 ReplayConversionError::TestNotFound {
222 binary_id: test_instance.binary_id.clone(),
223 test_name: test_instance.test_name.clone(),
224 }
225 })?;
226 Ok(TestEventKind::TestSlow {
227 stress_index: stress_index.as_ref().map(convert_stress_index),
228 test_instance: instance_id,
229 retry_data: *retry_data,
230 elapsed: *elapsed,
231 will_terminate: *will_terminate,
232 })
233 }
234
235 CoreEventKind::TestRetryStarted {
236 stress_index,
237 test_instance,
238 retry_data,
239 running,
240 command_line,
241 } => {
242 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
243 ReplayConversionError::TestNotFound {
244 binary_id: test_instance.binary_id.clone(),
245 test_name: test_instance.test_name.clone(),
246 }
247 })?;
248 Ok(TestEventKind::TestRetryStarted {
249 stress_index: stress_index.as_ref().map(convert_stress_index),
250 test_instance: instance_id,
251 retry_data: *retry_data,
252 running: *running,
253 command_line: command_line.clone(),
254 })
255 }
256
257 CoreEventKind::TestSkipped {
258 stress_index,
259 test_instance,
260 reason,
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 })
273 }
274
275 CoreEventKind::RunBeginCancel {
276 setup_scripts_running,
277 running,
278 reason,
279 } => {
280 let stats = RunStats {
281 cancel_reason: Some(*reason),
282 ..Default::default()
283 };
284 Ok(TestEventKind::RunBeginCancel {
285 setup_scripts_running: *setup_scripts_running,
286 current_stats: stats,
287 running: *running,
288 })
289 }
290
291 CoreEventKind::RunPaused {
292 setup_scripts_running,
293 running,
294 } => Ok(TestEventKind::RunPaused {
295 setup_scripts_running: *setup_scripts_running,
296 running: *running,
297 }),
298
299 CoreEventKind::RunContinued {
300 setup_scripts_running,
301 running,
302 } => Ok(TestEventKind::RunContinued {
303 setup_scripts_running: *setup_scripts_running,
304 running: *running,
305 }),
306
307 CoreEventKind::StressSubRunFinished {
308 progress,
309 sub_elapsed,
310 sub_stats,
311 } => Ok(TestEventKind::StressSubRunFinished {
312 progress: *progress,
313 sub_elapsed: *sub_elapsed,
314 sub_stats: *sub_stats,
315 }),
316
317 CoreEventKind::RunFinished {
318 run_id,
319 start_time,
320 elapsed,
321 run_stats,
322 outstanding_not_seen,
323 } => Ok(TestEventKind::RunFinished {
324 run_id: *run_id,
325 start_time: *start_time,
326 elapsed: *elapsed,
327 run_stats: *run_stats,
328 outstanding_not_seen: outstanding_not_seen.as_ref().map(|t| TestsNotSeen {
329 not_seen: t.not_seen.clone(),
330 total_not_seen: t.total_not_seen,
331 }),
332 }),
333 }
334 }
335
336 fn convert_output_event<'cx>(
337 &'cx self,
338 kind: &OutputEventKind<RecordingSpec>,
339 reader: &mut dyn StoreReader,
340 load_output: LoadOutput,
341 ) -> Result<TestEventKind<'cx>, ReplayConversionError> {
342 match kind {
343 OutputEventKind::SetupScriptFinished {
344 stress_index,
345 index,
346 total,
347 script_id,
348 program,
349 args,
350 no_capture,
351 run_status,
352 } => Ok(TestEventKind::SetupScriptFinished {
353 stress_index: stress_index.as_ref().map(convert_stress_index),
354 index: *index,
355 total: *total,
356 script_id: script_id.clone(),
357 program: program.clone(),
358 args: args.clone(),
359 junit_store_success_output: false,
360 junit_store_failure_output: false,
361 no_capture: *no_capture,
362 run_status: convert_setup_script_status(run_status, reader, load_output)?,
363 }),
364
365 OutputEventKind::TestAttemptFailedWillRetry {
366 stress_index,
367 test_instance,
368 run_status,
369 delay_before_next_attempt,
370 failure_output,
371 running,
372 } => {
373 let instance_id = self.lookup_test_instance_id(test_instance).ok_or_else(|| {
374 ReplayConversionError::TestNotFound {
375 binary_id: test_instance.binary_id.clone(),
376 test_name: test_instance.test_name.clone(),
377 }
378 })?;
379 Ok(TestEventKind::TestAttemptFailedWillRetry {
380 stress_index: stress_index.as_ref().map(convert_stress_index),
381 test_instance: instance_id,
382 run_status: convert_execute_status(run_status, reader, load_output)?,
383 delay_before_next_attempt: *delay_before_next_attempt,
384 failure_output: *failure_output,
385 running: *running,
386 })
387 }
388
389 OutputEventKind::TestFinished {
390 stress_index,
391 test_instance,
392 success_output,
393 failure_output,
394 junit_store_success_output,
395 junit_store_failure_output,
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 run_statuses: convert_execution_statuses(run_statuses, reader, load_output)?,
414 current_stats: *current_stats,
415 running: *running,
416 })
417 }
418 }
419 }
420}
421
422#[derive(Debug, thiserror::Error)]
424#[non_exhaustive]
425pub enum ReplayConversionError {
426 #[error("test not found under `{binary_id}`: {test_name}")]
428 TestNotFound {
429 binary_id: RustBinaryId,
431 test_name: TestCaseName,
433 },
434
435 #[error("error reading record")]
437 RecordRead(#[from] RecordReadError),
438
439 #[error("invalid stress count: expected non-zero value, got 0")]
441 InvalidStressCount,
442}
443
444fn convert_stress_condition(
447 summary: &StressConditionSummary,
448) -> Result<StressCondition, ReplayConversionError> {
449 match summary {
450 StressConditionSummary::Count { count } => {
451 let stress_count = match count {
452 Some(n) => {
453 let non_zero =
454 NonZero::new(*n).ok_or(ReplayConversionError::InvalidStressCount)?;
455 StressCount::Count { count: non_zero }
456 }
457 None => StressCount::Infinite,
458 };
459 Ok(StressCondition::Count(stress_count))
460 }
461 StressConditionSummary::Duration { duration } => Ok(StressCondition::Duration(*duration)),
462 }
463}
464
465fn convert_stress_index(summary: &StressIndexSummary) -> StressIndex {
466 StressIndex {
467 current: summary.current,
468 total: summary.total,
469 }
470}
471
472fn convert_execute_status(
473 status: &ExecuteStatus<RecordingSpec>,
474 reader: &mut dyn StoreReader,
475 load_output: LoadOutput,
476) -> Result<ExecuteStatus<LiveSpec>, ReplayConversionError> {
477 let output = convert_child_execution_output(&status.output, reader, load_output)?;
478 Ok(ExecuteStatus {
479 retry_data: status.retry_data,
480 output,
481 result: status.result.clone(),
482 start_time: status.start_time,
483 time_taken: status.time_taken,
484 is_slow: status.is_slow,
485 delay_before_start: status.delay_before_start,
486 error_summary: status.error_summary.clone(),
487 output_error_slice: status.output_error_slice.clone(),
488 })
489}
490
491fn convert_execution_statuses(
492 statuses: &ExecutionStatuses<RecordingSpec>,
493 reader: &mut dyn StoreReader,
494 load_output: LoadOutput,
495) -> Result<ExecutionStatuses<LiveSpec>, ReplayConversionError> {
496 let statuses: Vec<ExecuteStatus<LiveSpec>> = statuses
497 .iter()
498 .map(|s| convert_execute_status(s, reader, load_output))
499 .collect::<Result<_, _>>()?;
500
501 Ok(ExecutionStatuses::new(statuses))
502}
503
504fn convert_setup_script_status(
505 status: &SetupScriptExecuteStatus<RecordingSpec>,
506 reader: &mut dyn StoreReader,
507 load_output: LoadOutput,
508) -> Result<SetupScriptExecuteStatus<LiveSpec>, ReplayConversionError> {
509 let output = convert_child_execution_output(&status.output, reader, load_output)?;
510 Ok(SetupScriptExecuteStatus {
511 output,
512 result: status.result.clone(),
513 start_time: status.start_time,
514 time_taken: status.time_taken,
515 is_slow: status.is_slow,
516 env_map: status.env_map.clone(),
517 error_summary: status.error_summary.clone(),
518 })
519}
520
521fn convert_child_execution_output(
522 output: &ChildExecutionOutputDescription<RecordingSpec>,
523 reader: &mut dyn StoreReader,
524 load_output: LoadOutput,
525) -> Result<ChildExecutionOutputDescription<LiveSpec>, ReplayConversionError> {
526 match output {
527 ChildExecutionOutputDescription::Output {
528 result,
529 output,
530 errors,
531 } => {
532 let output = convert_child_output(output, reader, load_output)?;
533 Ok(ChildExecutionOutputDescription::Output {
534 result: result.clone(),
535 output,
536 errors: errors.clone(),
537 })
538 }
539 ChildExecutionOutputDescription::StartError(err) => {
540 Ok(ChildExecutionOutputDescription::StartError(err.clone()))
541 }
542 }
543}
544
545fn convert_child_output(
546 output: &ZipStoreOutputDescription,
547 reader: &mut dyn StoreReader,
548 load_output: LoadOutput,
549) -> Result<ChildOutputDescription, ReplayConversionError> {
550 if load_output == LoadOutput::Skip {
551 return Ok(ChildOutputDescription::NotLoaded);
552 }
553
554 match output {
555 ZipStoreOutputDescription::Split { stdout, stderr } => {
556 let stdout = stdout
557 .as_ref()
558 .map(|o| read_output_as_child_single(reader, o))
559 .transpose()?;
560 let stderr = stderr
561 .as_ref()
562 .map(|o| read_output_as_child_single(reader, o))
563 .transpose()?;
564 Ok(ChildOutputDescription::Split { stdout, stderr })
565 }
566 ZipStoreOutputDescription::Combined { output } => {
567 let output = read_output_as_child_single(reader, output)?;
568 Ok(ChildOutputDescription::Combined { output })
569 }
570 }
571}
572
573fn read_output_as_child_single(
574 reader: &mut dyn StoreReader,
575 output: &ZipStoreOutput,
576) -> Result<ChildSingleOutput, ReplayConversionError> {
577 let bytes = read_output_file(reader, output.file_name().map(OutputFileName::as_str))?;
578 Ok(ChildSingleOutput::from(bytes.unwrap_or_default()))
579}
580
581fn read_output_file(
582 reader: &mut dyn StoreReader,
583 file_name: Option<&str>,
584) -> Result<Option<Bytes>, ReplayConversionError> {
585 match file_name {
586 Some(name) => {
587 let bytes = reader.read_output(name)?;
588 Ok(Some(Bytes::from(bytes)))
589 }
590 None => Ok(None),
591 }
592}
593
594use crate::{
597 config::overrides::CompiledDefaultFilter,
598 errors::WriteEventError,
599 record::{
600 run_id_index::{RunIdIndex, ShortestRunIdPrefix},
601 store::{RecordedRunInfo, RecordedRunStatus},
602 },
603 reporter::{
604 DisplayConfig, DisplayReporter, DisplayReporterBuilder, DisplayerKind, FinalStatusLevel,
605 MaxProgressRunning, OutputLoadDecider, ReporterOutput, ShowProgress, ShowTerminalProgress,
606 StatusLevel, TestOutputDisplay,
607 },
608};
609use chrono::{DateTime, FixedOffset};
610use quick_junit::ReportUuid;
611
612#[derive(Clone, Debug)]
617pub struct ReplayHeader {
618 pub run_id: ReportUuid,
620 pub unique_prefix: Option<ShortestRunIdPrefix>,
625 pub started_at: DateTime<FixedOffset>,
627 pub status: RecordedRunStatus,
629}
630
631impl ReplayHeader {
632 pub fn new(
638 run_id: ReportUuid,
639 run_info: &RecordedRunInfo,
640 run_id_index: Option<&RunIdIndex>,
641 ) -> Self {
642 let unique_prefix = run_id_index.and_then(|index| index.shortest_unique_prefix(run_id));
643 Self {
644 run_id,
645 unique_prefix,
646 started_at: run_info.started_at,
647 status: run_info.status.clone(),
648 }
649 }
650}
651
652#[derive(Debug)]
654pub struct ReplayReporterBuilder {
655 status_level: StatusLevel,
656 final_status_level: FinalStatusLevel,
657 success_output: Option<TestOutputDisplay>,
658 failure_output: Option<TestOutputDisplay>,
659 should_colorize: bool,
660 verbose: bool,
661 show_progress: ShowProgress,
662 max_progress_running: MaxProgressRunning,
663 no_output_indent: bool,
664}
665
666impl Default for ReplayReporterBuilder {
667 fn default() -> Self {
668 Self {
669 status_level: StatusLevel::Pass,
670 final_status_level: FinalStatusLevel::Fail,
671 success_output: None,
672 failure_output: None,
673 should_colorize: false,
674 verbose: false,
675 show_progress: ShowProgress::default(),
676 max_progress_running: MaxProgressRunning::default(),
677 no_output_indent: false,
678 }
679 }
680}
681
682impl ReplayReporterBuilder {
683 pub fn new() -> Self {
685 Self::default()
686 }
687
688 pub fn set_status_level(&mut self, status_level: StatusLevel) -> &mut Self {
690 self.status_level = status_level;
691 self
692 }
693
694 pub fn set_final_status_level(&mut self, final_status_level: FinalStatusLevel) -> &mut Self {
696 self.final_status_level = final_status_level;
697 self
698 }
699
700 pub fn set_success_output(&mut self, output: TestOutputDisplay) -> &mut Self {
702 self.success_output = Some(output);
703 self
704 }
705
706 pub fn set_failure_output(&mut self, output: TestOutputDisplay) -> &mut Self {
708 self.failure_output = Some(output);
709 self
710 }
711
712 pub fn set_colorize(&mut self, colorize: bool) -> &mut Self {
714 self.should_colorize = colorize;
715 self
716 }
717
718 pub fn set_verbose(&mut self, verbose: bool) -> &mut Self {
720 self.verbose = verbose;
721 self
722 }
723
724 pub fn set_show_progress(&mut self, show_progress: ShowProgress) -> &mut Self {
726 self.show_progress = show_progress;
727 self
728 }
729
730 pub fn set_max_progress_running(
732 &mut self,
733 max_progress_running: MaxProgressRunning,
734 ) -> &mut Self {
735 self.max_progress_running = max_progress_running;
736 self
737 }
738
739 pub fn set_no_output_indent(&mut self, no_output_indent: bool) -> &mut Self {
741 self.no_output_indent = no_output_indent;
742 self
743 }
744
745 pub fn build<'a>(
747 self,
748 mode: NextestRunMode,
749 test_count: usize,
750 output: ReporterOutput<'a>,
751 ) -> ReplayReporter<'a> {
752 let display_reporter = DisplayReporterBuilder {
753 mode,
754 default_filter: CompiledDefaultFilter::for_default_config(),
755 display_config: DisplayConfig::with_overrides(
756 self.show_progress,
757 false, self.status_level,
759 self.final_status_level,
760 ),
761 test_count,
762 success_output: self.success_output,
763 failure_output: self.failure_output,
764 should_colorize: self.should_colorize,
765 verbose: self.verbose,
766 no_output_indent: self.no_output_indent,
767 max_progress_running: self.max_progress_running,
768 show_term_progress: ShowTerminalProgress::No,
771 displayer_kind: DisplayerKind::Replay,
772 }
773 .build(output);
774
775 ReplayReporter { display_reporter }
776 }
777}
778
779pub struct ReplayReporter<'a> {
789 display_reporter: DisplayReporter<'a>,
790}
791
792impl<'a> ReplayReporter<'a> {
793 pub fn output_load_decider(&self) -> OutputLoadDecider {
799 self.display_reporter.output_load_decider()
800 }
801
802 pub fn write_header(&mut self, header: &ReplayHeader) -> Result<(), WriteEventError> {
807 self.display_reporter.write_replay_header(header)
808 }
809
810 pub fn write_event(&mut self, event: &TestEvent<'a>) -> Result<(), WriteEventError> {
812 self.display_reporter.write_event(event)
813 }
814
815 pub fn finish(mut self) {
817 self.display_reporter.finish();
818 }
819}