speccoder_runtime/pipeline/
full.rs1use std::path::Path;
5
6use speccoder_core::FullTier;
7
8use crate::git::Git;
9use crate::{attempts, sandbox, verify};
10
11pub const PREFIX: &str = "refs/speccoder/full";
13
14#[must_use]
17pub fn run_once(project: &Path, wt_root: &Path) -> FullTier {
18 let config = verify::find_config(project)
19 .and_then(|root| std::fs::read_to_string(root.join(verify::config::FILE)).ok())
20 .unwrap_or_default();
21 let runner = match sandbox::parse(&config).and_then(|c| sandbox::runner(wt_root, &c)) {
22 Ok((r, _)) => r,
23 Err(e) => {
24 return FullTier {
25 note: Some(format!("the full tier could not run: {e}")),
26 ..FullTier::default()
27 };
28 }
29 };
30 match verify::run_checks(project, Some(verify::config::Tier::Full), &runner) {
31 Ok(report) => FullTier {
32 ran: report.checks.iter().map(|c| c.name.clone()).collect(),
33 passed: report.passed,
34 failed: report
35 .checks
36 .iter()
37 .filter(|c| c.status.is_failure())
38 .map(|c| c.name.clone())
39 .collect(),
40 note: None,
41 },
42 Err(e) => FullTier {
44 passed: true,
45 note: Some(if e.starts_with("no applicable checks") {
46 "no full-tier checks declared".to_owned()
47 } else {
48 e
49 }),
50 ..FullTier::default()
51 },
52 }
53}
54
55pub fn record(wt_root: &Path, feature: &str, tier: &FullTier) {
59 let git = Git::at(wt_root);
60 let (Ok(head), Ok(tree)) = (
61 git.line(&["rev-parse", "--verify", "HEAD^{commit}"]),
62 git.line(&["rev-parse", "--verify", "HEAD^{tree}"]),
63 ) else {
64 return;
65 };
66 let outcome = if tier.passed { "passed" } else { "failed" };
67 let message = format!(
68 "speccoder: full tier {outcome}\n\nTier: full\nOutcome: {outcome}\nRan: {}\n{}",
69 tier.ran.join(", "),
70 if tier.failed.is_empty() {
71 String::new()
72 } else {
73 format!("Failed: {}\n", tier.failed.join(", "))
74 }
75 );
76 let mut args = attempts::IDENTITY.to_vec();
77 args.extend(["commit-tree", &tree, "-p", &head, "-m", &message]);
78 if let Ok(commit) = git.line(&args) {
79 let _ = git.run(&["update-ref", &format!("{PREFIX}/{feature}"), &commit]);
80 }
81}
82
83#[must_use]
86pub fn read(repo_root: &Path, feature: &str, branch: &str) -> Option<FullTier> {
87 let git = Git::at(repo_root);
88 let reference = format!("{PREFIX}/{feature}");
89 let parent = git
90 .line(&["rev-parse", "--verify", &format!("{reference}^1")])
91 .ok()?;
92 let head = git
93 .line(&["rev-parse", "--verify", &format!("{branch}^{{commit}}")])
94 .ok()?;
95 if parent != head {
96 return None;
97 }
98 let body = git.line(&["log", "-1", "--format=%B", &reference]).ok()?;
99 let field = |key: &str| {
100 body.lines()
101 .find_map(|l| l.strip_prefix(key))
102 .map(|v| {
103 v.split(", ")
104 .filter(|s| !s.is_empty())
105 .map(str::to_owned)
106 .collect::<Vec<_>>()
107 })
108 .unwrap_or_default()
109 };
110 Some(FullTier {
111 ran: field("Ran: "),
112 passed: body.contains("\nOutcome: passed"),
113 failed: field("Failed: "),
114 note: None,
115 })
116}