forked from 1b-agent/fieldcraft-upstream
ModelCraft foundation: workspace, quality gates, FE model, command engine, geometry boundary
A pure-Rust CAE modeling workspace following the SolidCraft conventions: - modelcraft-model: nodes and elements by label with hostile-input validation - modelcraft-geometry: SolidCraft's kernel (pinned git dependency) behind our own API; tessellation into a face-tagged boundary surface for volume meshing - modelcraft-engine: CommandSpec registry, Session::execute with rollback and panic guard, scripts and a journal - modelcraft-cli: list commands, run scripts - xtask: layers (only geometry may use solidcraft-*), assets, licences, tests, wasm, ci - AGENTS.md, architecture, ADR 0001 (libraries and licences), roadmap, CI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
commit
b64ec3f698
38 files changed
+3931
No files matched your search
@@ -0,0 +1,105 @@
|
||||
//! `cargo xtask test`: build the workspace's tests once, then run every test binary at the same
|
||||
//! time. `cargo test` runs them one after another, so one slow binary held up all the others;
|
||||
//! the doc tests and any other `side` jobs run alongside on their own thread.
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::{cargo, root};
|
||||
|
||||
/// A test executable from `cargo test --no-run`.
|
||||
struct TestBin {
|
||||
label: String,
|
||||
exe: String,
|
||||
dir: String,
|
||||
}
|
||||
|
||||
/// Build the tests with `cargo_args` (e.g. `--locked`), run each binary with `test_args`, and run
|
||||
/// the `side` jobs one after another meanwhile. Each prints a line when it ends; a failure prints
|
||||
/// its whole output.
|
||||
pub fn run(cargo_args: &[&str], test_args: &[&str], side: Vec<(String, Command)>) -> Result<(), String> {
|
||||
let bins = build(cargo_args)?;
|
||||
let start = Instant::now();
|
||||
let (tx, rx) = mpsc::channel::<(String, Result<String, String>, f64)>();
|
||||
let mut failed = Vec::new();
|
||||
std::thread::scope(|scope| {
|
||||
for b in &bins {
|
||||
let tx = tx.clone();
|
||||
scope.spawn(move || {
|
||||
let t0 = Instant::now();
|
||||
let mut c = Command::new(&b.exe);
|
||||
c.args(test_args).current_dir(&b.dir).env("CARGO_MANIFEST_DIR", &b.dir);
|
||||
let _ = tx.send((b.label.clone(), finish(c), t0.elapsed().as_secs_f64()));
|
||||
});
|
||||
}
|
||||
let tx_side = tx.clone();
|
||||
scope.spawn(move || {
|
||||
for (label, c) in side {
|
||||
let t0 = Instant::now();
|
||||
let _ = tx_side.send((label, finish(c), t0.elapsed().as_secs_f64()));
|
||||
}
|
||||
});
|
||||
drop(tx);
|
||||
for (label, result, secs) in rx {
|
||||
match result {
|
||||
Ok(summary) => println!("ok {label:<44} {secs:6.1}s {summary}"),
|
||||
Err(output) => {
|
||||
println!("FAIL {label:<44} {secs:6.1}s\n{output}");
|
||||
failed.push(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
println!("test: {} binaries in {:.1}s", bins.len(), start.elapsed().as_secs_f64());
|
||||
if failed.is_empty() { Ok(()) } else { Err(format!("failed: {}", failed.join(", "))) }
|
||||
}
|
||||
|
||||
/// Run to the end: the last `test result:` line on success, all output on failure.
|
||||
fn finish(mut c: Command) -> Result<String, String> {
|
||||
let out = c.stdin(Stdio::null()).output().map_err(|e| format!("failed to spawn: {e}"))?;
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
if out.status.success() {
|
||||
Ok(stdout.lines().rev().find(|l| l.starts_with("test result:")).unwrap_or("").trim_start_matches("test result: ").to_string())
|
||||
} else {
|
||||
Err(format!("{stdout}{}\n({})", String::from_utf8_lossy(&out.stderr), out.status))
|
||||
}
|
||||
}
|
||||
|
||||
/// `cargo test --workspace --profile ci --no-run`, and the executables it built.
|
||||
fn build(cargo_args: &[&str]) -> Result<Vec<TestBin>, String> {
|
||||
let mut c = cargo();
|
||||
c.args(["test", "--workspace", "--profile", "ci", "--no-run", "--message-format=json-render-diagnostics"]).args(cargo_args);
|
||||
eprintln!("$ cargo test --workspace --profile ci --no-run");
|
||||
let out = c.stderr(Stdio::inherit()).output().map_err(|e| format!("cargo test --no-run: failed to spawn: {e}"))?;
|
||||
if !out.status.success() {
|
||||
return Err(format!("cargo test --no-run: exited with {}", out.status));
|
||||
}
|
||||
let mut bins: Vec<TestBin> = Vec::new();
|
||||
for line in String::from_utf8_lossy(&out.stdout).lines() {
|
||||
let Ok(m) = serde_json::from_str::<serde_json::Value>(line) else { continue };
|
||||
let Some(exe) = m["executable"].as_str().filter(|_| m["reason"] == "compiler-artifact" && m["profile"]["test"] == true) else {
|
||||
continue;
|
||||
};
|
||||
if bins.iter().any(|b| b.exe == exe) {
|
||||
continue;
|
||||
}
|
||||
let manifest = std::path::Path::new(m["manifest_path"].as_str().unwrap_or_default()).to_path_buf();
|
||||
let dir = manifest.parent().map(|d| d.to_path_buf()).unwrap_or_else(root);
|
||||
let pkg = dir.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
|
||||
let kind = m["target"]["kind"][0].as_str().unwrap_or("");
|
||||
let label = format!("{pkg} {kind} {}", m["target"]["name"].as_str().unwrap_or(""));
|
||||
bins.push(TestBin { label, exe: exe.to_string(), dir: dir.to_string_lossy().into_owned() });
|
||||
}
|
||||
if bins.is_empty() {
|
||||
return Err("cargo test --no-run built no test executables".into());
|
||||
}
|
||||
Ok(bins)
|
||||
}
|
||||
|
||||
/// The doc tests (`cargo test --doc`), to run as a side job.
|
||||
pub fn doc_tests(cargo_args: &[&str], test_args: &[&str]) -> (String, Command) {
|
||||
let mut c = cargo();
|
||||
c.args(["test", "--workspace", "--profile", "ci", "--doc"]).args(cargo_args).arg("--").args(test_args);
|
||||
("doc tests".into(), c)
|
||||
}
|
||||
Reference in new issue
Block a user