slovo/compiler/tests/user_project_conformance_beta25.rs

540 lines
17 KiB
Rust

use std::{
ffi::OsStr,
fs,
path::{Path, PathBuf},
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0);
struct MatrixEntry {
path: &'static str,
expected_tests: usize,
run_tests: bool,
}
const MATRIX: &[MatrixEntry] = &[
MatrixEntry {
path: "examples/projects/basic",
expected_tests: 2,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/enum-imports",
expected_tests: 9,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-cli",
expected_tests: 17,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-env",
expected_tests: 23,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-fs",
expected_tests: 35,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-io",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-json",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-math",
expected_tests: 4,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-net",
expected_tests: 9,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-num",
expected_tests: 5,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-option",
expected_tests: 36,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-process",
expected_tests: 21,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-random",
expected_tests: 2,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-result",
expected_tests: 26,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-string",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-time",
expected_tests: 3,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-vec_bool",
expected_tests: 19,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-vec_f64",
expected_tests: 20,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-vec_i32",
expected_tests: 22,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-vec_i64",
expected_tests: 20,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-import-vec_string",
expected_tests: 19,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-cli",
expected_tests: 17,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-env",
expected_tests: 23,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-fs",
expected_tests: 35,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-io",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-json",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-math",
expected_tests: 7,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-net",
expected_tests: 9,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-num",
expected_tests: 5,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-option",
expected_tests: 36,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-process",
expected_tests: 21,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-random",
expected_tests: 3,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-result",
expected_tests: 26,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-string",
expected_tests: 12,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-time",
expected_tests: 3,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-vec_bool",
expected_tests: 19,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-vec_f64",
expected_tests: 20,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-vec_i32",
expected_tests: 22,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-vec_i64",
expected_tests: 20,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/std-layout-local-vec_string",
expected_tests: 19,
run_tests: true,
},
MatrixEntry {
path: "examples/projects/stdlib-composition",
expected_tests: 3,
run_tests: true,
},
MatrixEntry {
path: "examples/workspaces/exp-5-local",
expected_tests: 2,
run_tests: true,
},
MatrixEntry {
path: "examples/workspaces/std-import-option",
expected_tests: 1,
run_tests: true,
},
];
#[test]
fn user_project_conformance_matrix_checks_lists_and_runs_stable_tests() {
assert_matrix_inventory();
let repo_root = repo_root();
for entry in MATRIX {
let fixture = repo_root.join(entry.path);
assert!(
fixture.join("slovo.toml").is_file(),
"{} must remain an existing project or workspace fixture",
entry.path
);
let check = run_glagol(
[OsStr::new("check"), fixture.as_os_str()],
&temp_root(entry.path, "check"),
);
assert_success_no_stderr(entry.path, "check", &check);
assert_stdout_eq(entry.path, "check", &check, "");
let list = run_glagol(
[
OsStr::new("test"),
OsStr::new("--list"),
fixture.as_os_str(),
],
&temp_root(entry.path, "list"),
);
assert_success_no_stderr(entry.path, "test --list", &list);
assert_stdout_contains(
entry.path,
"test --list",
&list,
&format!(
"{} test(s) selected (total_discovered {}, selected {}, passed 0, failed 0, skipped 0, filter none)",
entry.expected_tests, entry.expected_tests, entry.expected_tests
),
);
if entry.run_tests {
let test = run_glagol(
[OsStr::new("test"), fixture.as_os_str()],
&temp_root(entry.path, "test"),
);
assert_success_no_stderr(entry.path, "test", &test);
assert_stdout_contains(
entry.path,
"test",
&test,
&format!("{} test(s) passed", entry.expected_tests),
);
}
}
}
fn assert_matrix_inventory() {
assert_eq!(MATRIX.len(), 43, "beta25 fixture-root count changed");
assert_eq!(
MATRIX
.iter()
.map(|entry| entry.expected_tests)
.sum::<usize>(),
655,
"beta25 discovered-test count changed"
);
let matrix_paths = MATRIX
.iter()
.map(|entry| entry.path.to_owned())
.collect::<Vec<_>>();
let mut sorted_matrix_paths = matrix_paths.clone();
sorted_matrix_paths.sort();
assert_eq!(
matrix_paths, sorted_matrix_paths,
"beta25 user-project conformance matrix must remain sorted by repository-relative path"
);
let discovered_paths = discover_fixture_paths(&repo_root());
assert_eq!(
discovered_paths, matrix_paths,
"beta25 user-project conformance matrix must cover every top-level example project and workspace fixture"
);
let inventory = MATRIX
.iter()
.map(|entry| {
format!(
"{}|tests={}|run_tests={}",
entry.path, entry.expected_tests, entry.run_tests
)
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(
inventory,
concat!(
"examples/projects/basic|tests=2|run_tests=true\n",
"examples/projects/enum-imports|tests=9|run_tests=true\n",
"examples/projects/std-import-cli|tests=17|run_tests=true\n",
"examples/projects/std-import-env|tests=23|run_tests=true\n",
"examples/projects/std-import-fs|tests=35|run_tests=true\n",
"examples/projects/std-import-io|tests=12|run_tests=true\n",
"examples/projects/std-import-json|tests=12|run_tests=true\n",
"examples/projects/std-import-math|tests=4|run_tests=true\n",
"examples/projects/std-import-net|tests=9|run_tests=true\n",
"examples/projects/std-import-num|tests=5|run_tests=true\n",
"examples/projects/std-import-option|tests=36|run_tests=true\n",
"examples/projects/std-import-process|tests=21|run_tests=true\n",
"examples/projects/std-import-random|tests=2|run_tests=true\n",
"examples/projects/std-import-result|tests=26|run_tests=true\n",
"examples/projects/std-import-string|tests=12|run_tests=true\n",
"examples/projects/std-import-time|tests=3|run_tests=true\n",
"examples/projects/std-import-vec_bool|tests=19|run_tests=true\n",
"examples/projects/std-import-vec_f64|tests=20|run_tests=true\n",
"examples/projects/std-import-vec_i32|tests=22|run_tests=true\n",
"examples/projects/std-import-vec_i64|tests=20|run_tests=true\n",
"examples/projects/std-import-vec_string|tests=19|run_tests=true\n",
"examples/projects/std-layout-local-cli|tests=17|run_tests=true\n",
"examples/projects/std-layout-local-env|tests=23|run_tests=true\n",
"examples/projects/std-layout-local-fs|tests=35|run_tests=true\n",
"examples/projects/std-layout-local-io|tests=12|run_tests=true\n",
"examples/projects/std-layout-local-json|tests=12|run_tests=true\n",
"examples/projects/std-layout-local-math|tests=7|run_tests=true\n",
"examples/projects/std-layout-local-net|tests=9|run_tests=true\n",
"examples/projects/std-layout-local-num|tests=5|run_tests=true\n",
"examples/projects/std-layout-local-option|tests=36|run_tests=true\n",
"examples/projects/std-layout-local-process|tests=21|run_tests=true\n",
"examples/projects/std-layout-local-random|tests=3|run_tests=true\n",
"examples/projects/std-layout-local-result|tests=26|run_tests=true\n",
"examples/projects/std-layout-local-string|tests=12|run_tests=true\n",
"examples/projects/std-layout-local-time|tests=3|run_tests=true\n",
"examples/projects/std-layout-local-vec_bool|tests=19|run_tests=true\n",
"examples/projects/std-layout-local-vec_f64|tests=20|run_tests=true\n",
"examples/projects/std-layout-local-vec_i32|tests=22|run_tests=true\n",
"examples/projects/std-layout-local-vec_i64|tests=20|run_tests=true\n",
"examples/projects/std-layout-local-vec_string|tests=19|run_tests=true\n",
"examples/projects/stdlib-composition|tests=3|run_tests=true\n",
"examples/workspaces/exp-5-local|tests=2|run_tests=true\n",
"examples/workspaces/std-import-option|tests=1|run_tests=true",
),
"beta25 user-project conformance matrix changed without updating the inventory assertion"
);
}
fn discover_fixture_paths(repo_root: &Path) -> Vec<String> {
let mut paths = Vec::new();
for parent in ["examples/projects", "examples/workspaces"] {
let parent_path = repo_root.join(parent);
for entry in fs::read_dir(&parent_path).unwrap_or_else(|err| {
panic!(
"read fixture inventory `{}`: {}",
parent_path.display(),
err
);
}) {
let entry = entry.unwrap_or_else(|err| {
panic!(
"read fixture entry under `{}`: {}",
parent_path.display(),
err
);
});
let path = entry.path();
if path.join("slovo.toml").is_file() {
paths.push(
path.strip_prefix(repo_root)
.expect("fixture path under repository root")
.to_string_lossy()
.replace('\\', "/"),
);
}
}
}
paths.sort();
paths
}
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("compiler crate has repository parent")
.to_path_buf()
}
fn run_glagol<I, S>(args: I, cwd: &Path) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
fs::create_dir_all(cwd).unwrap_or_else(|err| {
panic!("create command cwd `{}`: {}", cwd.display(), err);
});
let mut command = Command::new(env!("CARGO_BIN_EXE_glagol"));
configure_conformance_env(&mut command);
command
.args(args)
.current_dir(cwd)
.output()
.expect("run glagol")
}
fn configure_conformance_env(command: &mut Command) {
for key in [
"GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING",
"GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_UNLIKELY_MISSING",
] {
command.env_remove(key);
}
for (key, value) in [
(
"GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT",
"glagol-std-import-env-alpha-value",
),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32", "42"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64", "42000000000"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32", "42"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64", "4294967296"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64", "42.5"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL", "true"),
("GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID", "not-a-number"),
(
"GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT",
"glagol-std-layout-local-env-alpha-value",
),
("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I32", "42"),
(
"GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I64",
"42000000000",
),
("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U32", "42"),
(
"GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U64",
"4294967296",
),
("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_F64", "42.5"),
("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_BOOL", "true"),
("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_INVALID", "not-a-number"),
] {
command.env(key, value);
}
}
fn temp_root(path: &str, command: &str) -> PathBuf {
let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock before UNIX_EPOCH")
.as_nanos();
let fixture = path
.chars()
.map(|ch| if ch == '/' || ch == '-' { '_' } else { ch })
.collect::<String>();
std::env::temp_dir().join(format!(
"glagol-user-project-conformance-beta25-{}-{}-{}-{}-{}",
std::process::id(),
nanos,
id,
fixture,
command
))
}
fn assert_success_no_stderr(fixture: &str, command: &str, output: &Output) {
assert!(
output.status.success(),
"{} `{}` failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
fixture,
command,
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
output.stderr.is_empty(),
"{} `{}` wrote stderr:\n{}",
fixture,
command,
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_stdout_eq(fixture: &str, command: &str, output: &Output, expected: &str) {
assert_eq!(
String::from_utf8_lossy(&output.stdout),
expected,
"{} `{}` stdout mismatch",
fixture,
command
);
}
fn assert_stdout_contains(fixture: &str, command: &str, output: &Output, needle: &str) {
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(needle),
"{} `{}` stdout did not contain `{}`:\n{}",
fixture,
command,
needle,
stdout
);
}