speccoder_runtime/bug/
confirm.rs1use std::path::Path;
6
7use speccoder_core::acceptance::{Decided, PreFrozenAcceptance, Producer, VisibleTest};
8use speccoder_core::actions::Via;
9use speccoder_core::bug::{
10 AcceptanceRef, Basis, BugReport, Confirmation, Outcome, PreFrozenRef, Reproduction, Stage,
11};
12
13use crate::integrity::sha256_hex;
14
15#[must_use]
17pub fn now() -> u64 {
18 std::time::SystemTime::now()
19 .duration_since(std::time::UNIX_EPOCH)
20 .map_or(0, |d| d.as_secs())
21}
22
23#[must_use]
26pub fn digest(reproductions: &[Reproduction]) -> String {
27 let mut lines: Vec<String> = reproductions
28 .iter()
29 .map(|r| {
30 format!(
31 "{}\t{}\t{}\t{}\t{}",
32 r.statement,
33 r.file,
34 r.digest,
35 r.test,
36 serde_json::to_string(&r.argv).unwrap_or_default()
37 )
38 })
39 .collect();
40 lines.sort();
41 format!("sha256:{}", sha256_hex(lines.join("\n").as_bytes()))
42}
43
44pub fn freeze(
47 state: &Path,
48 bug: &mut BugReport,
49 by: &str,
50 via: Via,
51 basis: Basis,
52) -> Result<(PreFrozenAcceptance, Vec<(String, String)>), String> {
53 let digest = bug
54 .confirm_digest
55 .clone()
56 .ok_or_else(|| "there are no reproductions to confirm".to_owned())?;
57 let paths: Vec<String> = bug.reproductions.iter().map(|r| r.file.clone()).collect();
58 let files = super::state::repro(state, &bug.unit, &bug.task, &paths);
59 if files.len() != paths.len() {
60 return Err("the reproductions' contents are missing from the state directory".to_owned());
61 }
62 let at = now();
63 let record = PreFrozenAcceptance {
64 unit: bug.unit.clone(),
65 requirement: bug.requirement.clone(),
66 wording_digest: bug.triad.digest.clone(),
67 approval_digest: digest.clone(),
68 visible: bug
69 .reproductions
70 .iter()
71 .map(|r| VisibleTest {
72 path: r.file.clone(),
73 sha256: r.digest.clone(),
74 cites: vec![r.statement.clone()],
75 })
76 .collect(),
77 held_out_sealed: bug.held_out_sealed.clone(),
78 producer: if basis == Basis::NamedTest {
79 Producer::PersonTest
80 } else {
81 Producer::BugConfirmation
82 },
83 decided: Decided {
84 by: by.to_owned(),
85 via,
86 at,
87 },
88 };
89 crate::pre_frozen::store(state, &record, &files)?;
90 bug.confirmation = Some(Confirmation {
91 digest: digest.clone(),
92 triad_digest: bug.triad.digest.clone(),
93 by: by.to_owned(),
94 via,
95 basis,
96 at,
97 });
98 bug.acceptance = Some(AcceptanceRef {
99 source: record.producer.source(),
100 pre_frozen: PreFrozenRef {
101 manifest: crate::pre_frozen::manifest_digest(&record),
102 approval: digest,
103 wording: record.wording_digest.clone(),
104 },
105 });
106 bug.stage = Stage::Fix;
107 bug.outcome = Outcome::Recorded;
108 bug.code = None;
109 bug.handoff = None;
110 super::state::save(state, bug)?;
111 Ok((record, files))
112}
113
114pub fn lapse(
117 state: &Path,
118 bug: &mut BugReport,
119 triad: speccoder_core::bug::Triad,
120) -> Result<(), String> {
121 crate::pre_frozen::remove(state, &bug.unit, &bug.requirement);
122 bug.triad = triad;
123 bug.confirmation = None;
124 bug.confirm_digest = None;
125 bug.acceptance = None;
126 bug.reproductions
127 .retain(|r| r.source == speccoder_core::bug::Source::Person);
128 bug.held_out.clear();
129 bug.held_out_sealed = None;
130 bug.tries.clear();
131 bug.stage = Stage::Reproduce;
132 bug.outcome = Outcome::Recorded;
133 super::state::save(state, bug)
134}