slovo/compiler/tests/test_discovery_beta19.rs

385 lines
9.8 KiB
Rust

use std::{
fs,
path::PathBuf,
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn file_list_preserves_order_and_does_not_execute_bodies() {
let fixture = write_fixture(
"file-list",
r#"
(module main)
(test "alpha first"
true)
(test "beta would fail"
false)
"#,
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"file test list",
output,
"test \"alpha first\" ... selected\n\
test \"beta would fail\" ... selected\n\
2 test(s) selected (total_discovered 2, selected 2, passed 0, failed 0, skipped 0, filter none)\n",
);
}
#[test]
fn file_list_filter_marks_selected_and_skipped_in_order() {
let fixture = write_fixture(
"file-list-filter",
r#"
(module main)
(test "alpha first"
false)
(test "beta second"
false)
"#,
);
let output = run_glagol([
"test".as_ref(),
"--list".as_ref(),
fixture.as_os_str(),
"--filter".as_ref(),
"beta".as_ref(),
]);
assert_success_stdout(
"file filtered test list",
output,
"test \"alpha first\" ... skipped\n\
test \"beta second\" ... selected\n\
1 test(s) selected (total_discovered 2, selected 1, passed 0, failed 0, skipped 1, filter \"beta\")\n",
);
}
#[test]
fn legacy_run_tests_list_matches_test_subcommand_list() {
let fixture = write_fixture(
"legacy-list",
r#"
(module main)
(test "legacy first"
false)
(test "legacy second"
false)
"#,
);
let subcommand = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
let legacy = run_glagol([
"--run-tests".as_ref(),
"--list".as_ref(),
fixture.as_os_str(),
]);
assert_success("test --list", &subcommand);
assert_success("legacy --run-tests --list", &legacy);
assert_eq!(
legacy.stdout, subcommand.stdout,
"legacy list output differed from test subcommand"
);
assert!(legacy.stderr.is_empty(), "legacy list wrote stderr");
}
#[test]
fn project_list_preserves_topological_order_and_filter_counts() {
let project = write_project(
"project-list",
&[(
"provider",
"(module provider (export value))\n\n\
(fn value () -> i32\n 1)\n\n\
(test \"provider first\"\n false)\n",
)],
"(module main)\n\n\
(import provider (value))\n\n\
(fn main () -> i32\n (value))\n\n\
(test \"consumer second\"\n false)\n",
);
let output = run_glagol([
"test".as_ref(),
"--list".as_ref(),
project.as_os_str(),
"--filter".as_ref(),
"consumer".as_ref(),
]);
assert_success_stdout(
"project filtered list",
output,
"test \"provider first\" ... skipped\n\
test \"consumer second\" ... selected\n\
1 test(s) selected (total_discovered 2, selected 1, passed 0, failed 0, skipped 1, filter \"consumer\")\n",
);
}
#[test]
fn workspace_list_preserves_package_order_without_running_tests() {
let workspace = write_workspace(
"workspace-list",
"[workspace]\nmembers = [\"packages/app\", \"packages/util\"]\n",
&[
WorkspacePackageSpec {
member: "packages/util",
manifest: "[package]\nname = \"util\"\nversion = \"0.1.0\"\n",
modules: &[(
"util",
"(module util (export answer))\n\n\
(fn answer () -> i32\n 41)\n\n\
(test \"util provider first\"\n false)\n",
)],
},
WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\n",
modules: &[(
"main",
"(module main)\n\n\
(import util.util (answer))\n\n\
(fn main () -> i32\n (answer))\n\n\
(test \"app consumer second\"\n false)\n",
)],
},
],
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), workspace.as_os_str()]);
assert_success_stdout(
"workspace list",
output,
"test \"util provider first\" ... selected\n\
test \"app consumer second\" ... selected\n\
2 test(s) selected (total_discovered 2, selected 2, passed 0, failed 0, skipped 0, filter none)\n",
);
}
#[test]
fn ordinary_test_output_stays_byte_stable() {
let fixture = write_fixture(
"ordinary-test",
r#"
(module main)
(test "alpha first"
true)
(test "beta second"
true)
"#,
);
let subcommand = run_glagol(["test".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"ordinary test",
subcommand,
"test \"alpha first\" ... ok\n\
test \"beta second\" ... ok\n\
2 test(s) passed\n",
);
let legacy = run_glagol(["--run-tests".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"ordinary legacy run-tests",
legacy,
"test \"alpha first\" ... ok\n\
test \"beta second\" ... ok\n\
2 test(s) passed\n",
);
}
#[test]
fn list_mode_reuses_checked_diagnostics_for_invalid_tests() {
let fixture = write_fixture(
"invalid-list",
r#"
(module main)
(test "not bool"
1)
"#,
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_failure_stderr_contains(
"invalid list",
&output,
&[
"TestExpressionNotBool",
"Expected:",
"bool",
"Found:",
"i32",
],
);
assert!(
output.stdout.is_empty(),
"invalid list wrote stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn list_flag_is_rejected_outside_test_mode() {
let fixture = write_fixture(
"list-outside-test",
r#"
(module main)
(fn main () -> i32
0)
"#,
);
let output = run_glagol(["check".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_failure_stderr_contains(
"check --list",
&output,
&["`--list` is only supported with `test` and `--run-tests`"],
);
}
fn write_fixture(name: &str, source: &str) -> PathBuf {
let path = unique_path(name).with_extension("slo");
fs::write(&path, source).expect("write fixture");
path
}
fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf {
let root = unique_path(name);
let src = root.join("src");
fs::create_dir_all(&src).expect("create project src");
fs::write(
root.join("slovo.toml"),
format!(
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
name
),
)
.expect("write project manifest");
fs::write(src.join("main.slo"), main).expect("write project main");
for (module, source) in modules {
fs::write(src.join(format!("{}.slo", module)), source).expect("write project module");
}
root
}
struct WorkspacePackageSpec<'a> {
member: &'a str,
manifest: &'a str,
modules: &'a [(&'a str, &'a str)],
}
fn write_workspace(
name: &str,
workspace_manifest: &str,
packages: &[WorkspacePackageSpec<'_>],
) -> PathBuf {
let root = unique_path(name);
fs::create_dir_all(&root).expect("create workspace root");
fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest");
for package in packages {
let package_root = root.join(package.member);
let src = package_root.join("src");
fs::create_dir_all(&src).expect("create workspace package src");
fs::write(package_root.join("slovo.toml"), package.manifest)
.expect("write workspace package manifest");
for (module, source) in package.modules {
fs::write(src.join(format!("{}.slo", module)), source)
.expect("write workspace package module");
}
}
root
}
fn unique_path(name: &str) -> PathBuf {
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::SeqCst);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!(
"glagol-test-discovery-beta19-{}-{}-{}-{}",
std::process::id(),
nanos,
id,
name
))
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.output()
.expect("run glagol")
}
fn assert_success(context: &str, output: &Output) {
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_success_stdout(context: &str, output: Output, expected: &str) {
assert_success(context, &output);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
expected,
"{} stdout mismatch",
context
);
assert!(
output.stderr.is_empty(),
"{} wrote stderr:\n{}",
context,
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_failure_stderr_contains(context: &str, output: &Output, expected: &[&str]) {
assert!(
!output.status.success(),
"{} unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
for needle in expected {
assert!(
stderr.contains(needle),
"{} stderr missing `{}`:\n{}",
context,
needle,
stderr
);
}
}