115 lines
4.2 KiB
Rust
115 lines
4.2 KiB
Rust
//! C1: the agent's checker compiles and runs the program a graph emits, to compare it with
|
|
//! the interpreter. The graph is a model's proposal, written for an unauthenticated
|
|
//! visitor, so two things must hold and are tested apart:
|
|
//!
|
|
//! - the public route never *runs* a graph whose generated code performs a real-world
|
|
//! effect (the side-effect filter, [`the_checker_does_not_run_a_graph_that_touches_files`]);
|
|
//! - any program that *is* run is confined to the checker's scratch directory (the sandbox,
|
|
//! [`the_sandbox_confines_a_program_the_checker_runs`]), which backstops the filter and
|
|
//! protects the per-widget harness.
|
|
//!
|
|
//! `audio.save` emits `std::fs::write(<path>, ..)` on a parameter-supplied path. Here the
|
|
//! path names a file in a directory the test owns, outside the checker's root.
|
|
|
|
use flowrs_core::graph::tier_check::{GraphCase, run_graphs};
|
|
use flowrs_core::graph::toolchain::Tiers;
|
|
use flowrs_core::graph::verify::{Native, Toolchain};
|
|
use flowrs_core::graph::widget::builtin_registry;
|
|
use flowrs_core::graph::{Graph, NodeId, WireMode};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
/// A fresh, empty directory under the system temp dir, resolved.
|
|
fn fresh(name: &str) -> PathBuf {
|
|
let d = std::env::temp_dir().join(format!("flowrs-confine-{name}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&d);
|
|
std::fs::create_dir_all(&d).unwrap();
|
|
d.canonicalize().unwrap()
|
|
}
|
|
|
|
/// `audio.tone → audio.save`, saving to `target` (a path the test owns, outside the root).
|
|
fn tone_to_file(target: &Path) -> (Graph, NodeId) {
|
|
let mut g = Graph::new();
|
|
let tone = g.add_node("audio.tone", 0.0, 0.0);
|
|
let save = g.add_node("audio.save", 0.0, 0.0);
|
|
g.get_node_mut(save)
|
|
.unwrap()
|
|
.properties
|
|
.insert("path".into(), target.display().to_string());
|
|
g.add_wire(tone, "audio", save, "audio", WireMode::Copy)
|
|
.unwrap();
|
|
(g, tone)
|
|
}
|
|
|
|
fn note_of(t: &Tiers) -> String {
|
|
match t {
|
|
Tiers::Agree(s) | Tiers::Unchecked(s) => s.clone(),
|
|
Tiers::Disagree(v) => v.join("; "),
|
|
}
|
|
}
|
|
|
|
/// The side-effect filter: `Native::tiers` (the agent's own path) does not run the whole
|
|
/// program of a graph that writes files. It says so, and the file is never written. (Before
|
|
/// the fix the program ran and the file appeared, agreeing on three values.)
|
|
#[test]
|
|
fn the_checker_does_not_run_a_graph_that_touches_files() {
|
|
let base = fresh("filter");
|
|
let outside = fresh("filter-out");
|
|
let target = outside.join("saved.wav");
|
|
let (g, _) = tone_to_file(&target);
|
|
|
|
let tiers = Native {
|
|
root: base.join("check"),
|
|
}
|
|
.tiers(&g, builtin_registry());
|
|
|
|
assert!(
|
|
note_of(&tiers).contains("not run here"),
|
|
"the whole program was run instead of filtered: {tiers:?}"
|
|
);
|
|
assert!(!target.exists(), "the checker wrote {}", target.display());
|
|
let _ = (
|
|
std::fs::remove_dir_all(&base),
|
|
std::fs::remove_dir_all(&outside),
|
|
);
|
|
}
|
|
|
|
/// The sandbox: a program the checker runs cannot write outside its scratch directory.
|
|
/// Driven through `run_graphs` directly, below the filter, so it exercises the confinement
|
|
/// of a program that really executes: the case is not skipped (it ran), and the outside
|
|
/// file is absent. (Before the fix the write succeeded and the file appeared.)
|
|
#[cfg(target_os = "macos")]
|
|
#[test]
|
|
fn the_sandbox_confines_a_program_the_checker_runs() {
|
|
let scratch = fresh("sandbox");
|
|
let outside = fresh("sandbox-out");
|
|
let target = outside.join("pwned.wav");
|
|
let (g, tone) = tone_to_file(&target);
|
|
|
|
let case = GraphCase {
|
|
name: "tone-save".into(),
|
|
graph: g,
|
|
probes: vec![(tone, "audio".into())],
|
|
files: Vec::new(),
|
|
console: false,
|
|
};
|
|
let report = run_graphs(&[case], &scratch).expect("the program compiles and runs");
|
|
|
|
assert!(
|
|
report.skipped.is_empty(),
|
|
"the program never ran, so confinement was not exercised: {:?}",
|
|
report.skipped
|
|
);
|
|
assert!(
|
|
report.compared >= 1,
|
|
"the probed program produced nothing: {report:?}"
|
|
);
|
|
assert!(
|
|
!target.exists(),
|
|
"a program the checker ran wrote {}",
|
|
target.display()
|
|
);
|
|
let _ = (
|
|
std::fs::remove_dir_all(&scratch),
|
|
std::fs::remove_dir_all(&outside),
|
|
);
|
|
}
|