use crate::config::{Action, Event, Workflow};
use crate::prompt::Error;
use crate::prompt::workflow_actions::execute;
use crate::template::{GitRunner, TEMPLATE};
use std::cell::RefCell;
use std::collections::BTreeSet;
use std::io;
use std::path::{Path, PathBuf};
const DEFERRED: &[&str] = &["DeliverResult", "Dispatch", "LandCompaction"];
fn shipped_workflows() -> Vec<(PathBuf, String)> {
let template = TEMPLATE
.get_file("workflow.yaml")
.expect("the template ships a workflow.yaml");
let mut out = vec![(
PathBuf::from("template/workflow.yaml"),
String::from_utf8(template.contents().to_vec()).expect("utf8"),
)];
let experiments = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("experiments");
for entry in std::fs::read_dir(experiments).expect("experiments/ exists") {
let path = entry.expect("readable entry").path().join("workflow.yaml");
if let Ok(raw) = std::fs::read_to_string(&path) {
out.push((path, raw));
}
}
out
}
fn variant(action: &Action) -> String {
let debug = format!("{action:?}");
debug
.split([' ', '('])
.next()
.expect("split yields a head")
.to_string()
}
#[derive(Default)]
struct RecGit(RefCell<usize>);
impl GitRunner for RecGit {
fn run(&self, _dest: &Path, _args: &[&str]) -> io::Result<()> {
*self.0.borrow_mut() += 1;
Ok(())
}
fn run_capture(&self, _dest: &Path, _args: &[&str]) -> io::Result<String> {
unreachable!("ref marks never capture")
}
}
#[test]
fn shipped_configs_declare_only_interpretable_vocabulary() {
let git = RecGit::default();
let mut deferred = BTreeSet::new();
let mut events_swept = BTreeSet::<Event>::new();
for (origin, raw) in shipped_workflows() {
let workflow = Workflow::parse(&raw, &origin)
.unwrap_or_else(|e| panic!("{} does not parse: {e}", origin.display()));
for (event, actions) in workflow.typed_events() {
events_swept.insert(event);
assert!(
!actions.is_empty(),
"{}: {} is bound to nothing",
origin.display(),
event.as_str()
);
for action in &actions {
match execute(action, event, Path::new("/wt"), "a-b", &git) {
Ok(()) => {}
Err(Error::ActionUnsupported { .. }) => {
deferred.insert(variant(action));
}
Err(other) => panic!(
"{}: {} bound {action:?}, which the interpreter answered with {other:?}",
origin.display(),
event.as_str()
),
}
}
}
}
assert_eq!(
deferred,
DEFERRED.iter().map(|s| s.to_string()).collect(),
"the shipped configs' deferred vocabulary changed; every entry needs a \
tracked executor behind it (ARCH §6 shipped-state note)"
);
assert!(
*git.0.borrow() >= 2,
"no shipped executor ran — the sweep is vacuous"
);
assert!(
events_swept.contains(&Event::UserMessage),
"user_message is unbound in every shipped config"
);
}