use noxid_runtime::NODE_TEST_DOM;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};
const APP_COMMIT: &str = "b88e2734ba368cf0562a60fe7c148603716e2f2a";
static NEXT_TEMP: AtomicU64 = AtomicU64::new(0);
struct TempTree {
root: PathBuf,
}
impl TempTree {
fn new(label: &str) -> Self {
let ordinal = NEXT_TEMP.fetch_add(1, Ordering::Relaxed);
let root = std::env::temp_dir().join(format!(
"noxid-t3-3-qa2-{label}-{}-{ordinal}",
std::process::id()
));
fs::create_dir_all(&root).expect("create T3-3 QA2 scratch directory");
Self { root }
}
}
impl Drop for TempTree {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
fn repository_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn assert_success(output: &Output, context: &str) {
assert!(
output.status.success(),
"{context}:\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn archived_app(scratch: &TempTree) -> PathBuf {
let root = repository_root();
let mut git = Command::new("git")
.args([
"archive",
"--format=tar",
APP_COMMIT,
"--",
"benchmarks/js-framework-benchmark/Nuu.toml",
"benchmarks/js-framework-benchmark/src/benchmark.client.js",
"benchmarks/js-framework-benchmark/src/benchmark.client.js.nuu-contract",
"benchmarks/js-framework-benchmark/src/global.css",
"benchmarks/js-framework-benchmark/src/routes/index.html/+page.nuu",
])
.current_dir(&root)
.stdout(Stdio::piped())
.spawn()
.expect("archive exact T3-3 app commit");
let mut tar = Command::new("tar")
.args(["-xf", "-", "-C"])
.arg(&scratch.root)
.stdin(Stdio::piped())
.spawn()
.expect("extract exact T3-3 app commit");
std::io::copy(
&mut git.stdout.take().expect("Git archive stdout"),
&mut tar.stdin.take().expect("tar stdin"),
)
.expect("pipe exact T3-3 archive");
assert!(git.wait().expect("wait for Git archive").success());
assert!(tar.wait().expect("wait for tar extraction").success());
let app = scratch.root.join("benchmarks/js-framework-benchmark");
for (historical, current) in [
("Nuu.toml", "Noxid.toml"),
(
"src/benchmark.client.js.nuu-contract",
"src/benchmark.client.js.nox-contract",
),
(
"src/routes/index.html/+page.nuu",
"src/routes/index.html/+page.nox",
),
] {
fs::rename(app.join(historical), app.join(current))
.expect("translate archived app file to renamed layout");
}
app
}
fn all_text(root: &Path) -> String {
fn visit(path: &Path, output: &mut String) {
for entry in fs::read_dir(path).expect("read QA2 output directory") {
let path = entry.expect("read QA2 output entry").path();
if path.is_dir() {
visit(&path, output);
} else if let Ok(text) = fs::read_to_string(path) {
output.push_str(&text);
}
}
}
let mut output = String::new();
visit(root, &mut output);
output
}
#[test]
fn qa_t3_3_round2_reconstructs_and_falsifies_replacement_provenance() {
let root = repository_root();
let output = Command::new("node")
.args([
"--test",
"benchmarks/js-framework-benchmark/tools/qa2-provenance.test.mjs",
])
.current_dir(&root)
.output()
.expect("Node.js is required for T3-3 QA2 provenance tests");
assert_success(&output, "T3-3 QA2 provenance and corruption suite");
}
#[test]
fn qa_t3_3_round2_executes_official_ids_operations_and_key_identity() {
let scratch = TempTree::new("keyed-dom");
let project = archived_app(&scratch);
let output = scratch.root.join("build");
let build = Command::new(env!("CARGO_BIN_EXE_noxid"))
.arg("build")
.arg(&project)
.arg("--out-dir")
.arg(&output)
.current_dir(repository_root())
.output()
.expect("build exact committed benchmark app for QA2");
assert_success(&build, "T3-3 QA2 exact-commit build");
let script = output.join("qa2-keyed-dom.mjs");
fs::write(
&script,
format!(
r#"{NODE_TEST_DOM}
Math.random = () => 0;
const {{ mountBenchmarkPage }} = await import("./assets/BenchmarkPage.js");
const host = document.createElement("div");
const app = mountBenchmarkPage(host);
const byId = (id) => host.querySelector(`[id="${{id}}"]`);
const click = (node) => node.dispatchEvent({{ type: "click", defaultPrevented: false }});
const text = (node) => node.nodeType === 3 ? node.data : node.childNodes.map(text).join("");
const rows = () => byId("tbody").querySelectorAll("tr");
const id = (row) => Number(text(row.querySelectorAll("td")[0]));
for (const expected of ["main", "run", "runlots", "add", "update", "clear", "swaprows", "tbody"]) {{
if (!byId(expected)) throw new Error(`missing official id ${{expected}}`);
}}
click(byId("run"));
if (rows().length !== 1000 || id(rows()[0]) !== 1 || id(rows()[999]) !== 1000) throw new Error("create 1k boundary");
const first = rows()[0];
const second = rows()[1];
const nineNinetyNine = rows()[998];
click(byId("update"));
if (rows()[0] !== first || !text(rows()[0]).includes(" !!!") || text(rows()[9]).includes(" !!!") || !text(rows()[10]).includes(" !!!")) throw new Error("update/key identity boundary");
click(rows()[0].querySelectorAll("a")[0]);
if (rows()[0].getAttribute("class") !== "danger") throw new Error("select operation");
click(byId("swaprows"));
if (rows()[1] !== nineNinetyNine || rows()[998] !== second || id(rows()[1]) !== 999 || id(rows()[998]) !== 2) throw new Error("swap/key identity boundary");
click(rows()[0].querySelectorAll("a")[1]);
if (rows().length !== 999 || rows().some((row) => id(row) === 1) || rows().some((row) => row.getAttribute("class") === "danger")) throw new Error("remove operation");
click(byId("clear"));
if (rows().length !== 0) throw new Error("clear operation");
click(byId("runlots"));
if (rows().length !== 10000 || id(rows()[0]) !== 1001 || id(rows()[9999]) !== 11000) throw new Error("create 10k boundary");
click(byId("add"));
if (rows().length !== 11000 || id(rows()[10000]) !== 11001 || id(rows()[10999]) !== 12000) throw new Error("append 1k boundary");
app.dispose();
console.log("t3-3-qa2-keyed-dom-ok");
"#
),
)
.expect("write QA2 generated DOM script");
let execution = Command::new("node")
.arg(script.file_name().expect("QA2 DOM script name"))
.current_dir(&output)
.output()
.expect("execute QA2 generated DOM script");
assert_success(&execution, "T3-3 QA2 generated DOM behavior");
}
#[test]
fn qa_t3_3_round2_production_base_and_same_output_rebuild_are_fresh() {
let scratch = TempTree::new("production-rebuild");
let project = archived_app(&scratch);
let output = scratch.root.join("dist");
let bundle = || {
Command::new(env!("CARGO_BIN_EXE_noxid"))
.arg("bundle")
.arg(&project)
.arg("--out-dir")
.arg(&output)
.current_dir(repository_root())
.output()
.expect("bundle exact committed benchmark app for QA2")
};
assert_success(&bundle(), "T3-3 QA2 initial production bundle");
let index = fs::read_to_string(output.join("index.html")).expect("read QA2 production index");
assert!(index.contains("setPublicPaths([\"/frameworks/keyed/nuulang/dist/\"])"));
assert!(index.contains("data-farm-resource=true"));
let first = all_text(&output);
assert!(first.contains("NuuLang-keyed"));
assert!(!first.contains(&repository_root().to_string_lossy().to_string()));
let page = project.join("src/routes/index.html/+page.nox");
let edited = fs::read_to_string(&page)
.expect("read archived QA2 page")
.replacen("<h1>NuuLang-keyed</h1>", "<h1>QA2-fresh-output</h1>", 1);
let mut file = fs::File::create(&page).expect("open archived QA2 page for edit");
file.write_all(edited.as_bytes())
.expect("edit archived QA2 page");
assert_success(&bundle(), "T3-3 QA2 same-output rebuild");
let rebuilt = all_text(&output);
assert!(rebuilt.contains("QA2-fresh-output"));
assert!(!rebuilt.contains(">NuuLang-keyed<"));
let rebuilt_index =
fs::read_to_string(output.join("index.html")).expect("read rebuilt QA2 index");
assert!(rebuilt_index.contains("/frameworks/keyed/nuulang/dist/assets/"));
}