git_psect/commands/
run.rs1use std::process::Command;
2use std::sync::{
3 Arc,
4 atomic::{AtomicBool, Ordering},
5};
6
7use crate::{
8 candidates,
9 error::Error,
10 repo,
11 state::{self, Sample},
12};
13
14pub fn run(script: Vec<String>, confidence_threshold: f64) -> Result<(), Error> {
15 let ctx = repo::open()?;
16 let mut state = state::read(&ctx.state_dir)?;
17
18 if state.old_revisions.is_empty() {
19 return Err(Error::Validation("run 'git psect old <rev>' first".into()));
20 }
21 if state.new_revisions.is_empty() {
22 return Err(Error::Validation("run 'git psect new <rev>' first".into()));
23 }
24
25 let interrupted = Arc::new(AtomicBool::new(false));
26 let flag = interrupted.clone();
27 ctrlc::set_handler(move || {
28 flag.store(true, Ordering::SeqCst);
29 })
30 .map_err(|e| Error::Validation(format!("failed to set Ctrl+C handler: {e}")))?;
31
32 loop {
33 let head_sha = ctx.repo.head()?.peel_to_commit()?.id().to_string();
34 println!("{}: running test...", &head_sha[..10]);
35
36 let mut child = Command::new(&script[0]).args(&script[1..]).spawn()?;
37 let status = child.wait()?;
38
39 if interrupted.load(Ordering::SeqCst) {
40 println!("Interrupted.");
41 break;
42 }
43
44 let outcome = status.success();
45 let verb = if outcome { "passed" } else { "failed" };
46 println!("{}: test {verb}", &head_sha[..10]);
47
48 state.samples.push(Sample {
49 revision: head_sha,
50 outcome,
51 recorded_at: chrono::Utc::now().to_rfc3339(),
52 comment: None,
53 });
54 state::write(&ctx.state_dir, &state)?;
55
56 let candidates = candidates::build(&ctx.repo, &state)?;
57 let distributions = candidates::build_distributions(&state);
58 let ps = candidates::reconstruct(&ctx.repo, &state, &candidates, &distributions)?;
59
60 let confidence = ps.confidence();
61 let best = ps.most_likely_regression_revision().0.to_string();
62 let best_oid = ctx.repo.revparse_single(&best)?.id();
63 let best_summary = ctx
64 .repo
65 .find_commit(best_oid)?
66 .summary()
67 .unwrap_or("")
68 .to_string();
69
70 if confidence >= confidence_threshold {
71 candidates::checkout(&ctx.repo, best_oid)?;
72 println!(
73 "{:.1}% chance the regression was introduced in {}: {}.",
74 confidence * 100.0,
75 &best[..10],
76 best_summary
77 );
78 println!(
79 "Run 'git psect reset' to clear the session or continue running tests to increase confidence."
80 );
81 break;
82 }
83
84 if confidence > 0.5 {
85 println!(
86 "Current best guess: {} \"{}\" ({:.1}% confidence).",
87 &best[..10],
88 best_summary,
89 confidence * 100.0
90 );
91 }
92
93 let next_sha = candidates::checkout_next(&ctx.repo, &distributions, &ps)?;
94 let next_summary = ctx
95 .repo
96 .find_commit(ctx.repo.revparse_single(&next_sha)?.id())?
97 .summary()
98 .unwrap_or("")
99 .to_string();
100 println!("{}: checking out \"{}\"", &next_sha[..10], next_summary);
101 }
102
103 Ok(())
104}