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