Skip to main content

git_psect/commands/
run.rs

1use 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}