use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use std::process::Command;
use super::{Fixtures, Project, Scenario, Verdict, scenarios};
use crate::common::{skip_without_host_binary, text};
fn binary() -> Option<PathBuf> {
std::env::var_os("MOAT_CODEX_BIN")
.filter(|v| !v.is_empty())
.map(PathBuf::from)
}
impl Fixtures {
fn sync_with_codex(&self, codex: &Path) {
let path = std::env::var_os("PATH").unwrap_or_default();
let dirs = std::iter::once(codex.parent().unwrap().to_path_buf())
.chain(std::env::split_paths(&path));
let synced = self
.sb
.command()
.args(["sandbox", "sync"])
.env("MOAT_ASSUME_TTY", "1")
.env("PATH", std::env::join_paths(dirs).unwrap())
.output()
.unwrap();
assert_eq!(synced.status.code(), Some(0), "{}", text(&synced));
}
fn codex_verdict(&self, codex: &Path, proxy_port: u16, scenario: &Scenario) -> Verdict {
let proxy = format!("http://127.0.0.1:{proxy_port}");
let codex = Command::new(codex);
let mut cmd = self.codex_command(codex, scenario.project, &scenario.command);
let out = cmd
.env("HTTP_PROXY", &proxy)
.env("HTTPS_PROXY", &proxy)
.output()
.expect("running codex sandbox");
if out.status.success() {
Verdict::Allow
} else {
Verdict::Deny
}
}
fn codex_command(&self, mut cmd: Command, project: Project, command: &str) -> Command {
let project = self.tree(project);
let path = format!("{}/bin:/usr/bin:/bin", project.display());
let script = format!("set -eo pipefail; {command}");
cmd.args(["sandbox", "-P", "moat", "-C"])
.arg(project)
.args(["--", "/bin/bash", "-c", &script])
.env_clear()
.env("PATH", path)
.env("HOME", &self.sb.home)
.env("CODEX_HOME", self.sb.home.join(".codex"));
cmd
}
}
#[cfg(any(target_os = "macos", target_os = "linux"))]
#[test]
fn hostile_scripts_meet_the_codex_profile() {
use super::scripts::executing::{NPM_TEST, OS, Run, fixtures, meet};
use super::start;
let Some(codex) = binary() else {
skip_without_host_binary("hostile scripts under codex (set MOAT_CODEX_BIN)");
return;
};
let fx = fixtures();
fx.sync_with_codex(&codex);
meet(&format!("codex-{OS}"), &fx, |terminal| {
let out = fx
.codex_command(start(&codex, terminal), Project::Evil, NPM_TEST)
.output()
.expect("running codex sandbox");
Some(Run {
completed: out.status.success(),
shown: text(&out),
})
});
}
#[test]
fn codex_layer_agrees_with_every_scenario() {
let Some(codex) = binary() else {
skip_without_host_binary("codex layer (set MOAT_CODEX_BIN); the hook layer still ran");
return;
};
let fx = Fixtures::build();
let port = fx.sb.use_free_proxy_port();
fx.sb.serve_proxy_without_credential();
fx.sync_with_codex(&codex);
let _proxy = fx.sb.start_proxy();
let mut matrix = String::from("\ndifferential matrix (codex sandbox):\n");
let mut mismatches = Vec::new();
for s in &scenarios() {
let got = fx.codex_verdict(&codex, port, s);
if let Some(gap) = s.gap_at("codex") {
let _ = writeln!(
matrix,
" {:<30} codex={} GAP(#{}: {})",
s.id,
got.symbol(),
gap.issue,
gap.why
);
continue;
}
let _ = writeln!(matrix, " {:<30} codex={}", s.id, got.symbol());
if got != s.codex {
mismatches.push(format!(
"{}: codex {} but scenarios.yaml expects {} ({})",
s.id,
got.symbol().trim(),
s.codex.symbol().trim(),
s.why
));
}
}
eprintln!("{matrix}");
assert!(
mismatches.is_empty(),
"codex disagreements:\n{}",
mismatches.join("\n")
);
}