785 lines
22 KiB
Rust
785 lines
22 KiB
Rust
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[test]
|
|
fn check_subcommand_accepts_supported_source_without_primary_output() {
|
|
let fixture = write_fixture(
|
|
"check",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol(["check".as_ref(), fixture.as_os_str()]);
|
|
|
|
assert_success_stdout("check", output, "");
|
|
}
|
|
|
|
#[test]
|
|
fn check_subcommand_rejects_backend_boundary_without_primary_output() {
|
|
let fixture = write_fixture(
|
|
"check-backend-gap",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn id ((value (ptr i32))) -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol(["check".as_ref(), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_exit_code("check backend boundary", &output, 1);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"failed check wrote primary stdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedBackendFeature"),
|
|
"backend boundary diagnostic mismatch:\n{}",
|
|
stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fmt_subcommand_matches_legacy_format_flag() {
|
|
let fixture = write_fixture(
|
|
"fmt-alias",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32 0)
|
|
"#,
|
|
);
|
|
|
|
let legacy = run_glagol(["--format".as_ref(), fixture.as_os_str()]);
|
|
let alias = run_glagol(["fmt".as_ref(), fixture.as_os_str()]);
|
|
|
|
assert_success("legacy --format", &legacy);
|
|
assert_success("fmt", &alias);
|
|
assert_eq!(alias.stdout, legacy.stdout, "fmt alias output mismatch");
|
|
}
|
|
|
|
#[test]
|
|
fn test_subcommand_matches_legacy_run_tests_flag() {
|
|
let fixture = write_fixture(
|
|
"test-alias",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "true"
|
|
true)
|
|
"#,
|
|
);
|
|
|
|
let legacy = run_glagol(["--run-tests".as_ref(), fixture.as_os_str()]);
|
|
let alias = run_glagol(["test".as_ref(), fixture.as_os_str()]);
|
|
|
|
assert_success("legacy --run-tests", &legacy);
|
|
assert_success("test", &alias);
|
|
assert_eq!(alias.stdout, legacy.stdout, "test alias output mismatch");
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_emit_one_object_per_line_without_human_preamble() {
|
|
let fixture = write_fixture(
|
|
"json-type-mismatch",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn id ((value i32)) -> i32
|
|
value)
|
|
|
|
(fn main () -> i32
|
|
(id true))
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_exit_code("json diagnostics", &output, 1);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"json diagnostic failure wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr
|
|
.lines()
|
|
.all(|line| line.starts_with('{') && line.ends_with('}')),
|
|
"stderr contained non-JSON diagnostic text:\n{}",
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains(r#""schema":"slovo.diagnostic""#)
|
|
&& stderr.contains(r#""version":1"#)
|
|
&& stderr.contains(r#""code":"TypeMismatch""#)
|
|
&& stderr.contains(r#""file":"#)
|
|
&& stderr.contains(r#""span":{"byte_start":"#),
|
|
"json diagnostic did not contain expected fields:\n{}",
|
|
stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_parse_error() {
|
|
let fixture = write_fixture("json-parse", "(module main");
|
|
|
|
let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]);
|
|
|
|
assert_exit_code("json parse error", &output, 1);
|
|
assert_json_diagnostic("json parse error", &output, "UnclosedList");
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_lowering_error() {
|
|
let fixture = write_fixture(
|
|
"json-lowering",
|
|
r#"
|
|
(module main)
|
|
|
|
(bogus top level)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol(["--json-diagnostics".as_ref(), fixture.as_os_str()]);
|
|
|
|
assert_exit_code("json lowering error", &output, 1);
|
|
assert_json_diagnostic("json lowering error", &output, "UnknownTopLevelForm");
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_formatter_error() {
|
|
let fixture = write_fixture(
|
|
"json-formatter",
|
|
r#"
|
|
(module main) ; trailing comments are outside the formatter subset
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol([
|
|
"--json-diagnostics".as_ref(),
|
|
"fmt".as_ref(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
|
|
assert_exit_code("json formatter error", &output, 1);
|
|
assert_json_diagnostic(
|
|
"json formatter error",
|
|
&output,
|
|
"UnsupportedFormatterComment",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_failed_test() {
|
|
let fixture = write_fixture(
|
|
"json-failed-test",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "false"
|
|
false)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol([
|
|
"--json-diagnostics".as_ref(),
|
|
"test".as_ref(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_exit_code("json failed test", &output, 1);
|
|
assert_json_diagnostic("json failed test", &output, "TestFailed");
|
|
assert!(
|
|
stderr.contains(r#""expected":"true""#) && stderr.contains(r#""found":"false""#),
|
|
"failed-test JSON diagnostic did not include expected/found:\n{}",
|
|
stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_backend_boundary_through_check() {
|
|
let fixture = write_fixture(
|
|
"json-check-backend-gap",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn id ((value (ptr i32))) -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
|
|
let output = run_glagol([
|
|
"--json-diagnostics".as_ref(),
|
|
"check".as_ref(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
|
|
assert_exit_code("json check backend boundary", &output, 1);
|
|
assert_json_diagnostic(
|
|
"json check backend boundary",
|
|
&output,
|
|
"UnsupportedBackendFeature",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_usage_error_with_manifest() {
|
|
let manifest_path = temp_path("json-usage", "manifest.slo");
|
|
|
|
let output = run_glagol([
|
|
"--json-diagnostics".as_ref(),
|
|
"--manifest".as_ref(),
|
|
manifest_path.as_os_str(),
|
|
]);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_exit_code("json usage error", &output, 2);
|
|
assert_json_diagnostic("json usage error", &output, "UsageError");
|
|
assert!(
|
|
stderr.contains(r#""file":null"#) && stderr.contains(r#""span":null"#),
|
|
"usage JSON diagnostic should not claim a source span:\n{}",
|
|
stderr
|
|
);
|
|
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
manifest.contains(" (mode usage-error)\n")
|
|
&& manifest.contains(" (success false)\n")
|
|
&& manifest.contains(" (diagnostics-encoding json)\n")
|
|
&& manifest.contains("UsageError"),
|
|
"usage JSON manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn json_diagnostics_cover_hosted_build_missing_clang() {
|
|
let fixture = write_fixture(
|
|
"json-missing-clang",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
let output_path = temp_path("json-missing-clang", "bin");
|
|
let manifest_path = temp_path("json-missing-clang", "manifest.slo");
|
|
let missing_clang = temp_path("json-missing-clang", "not-a-clang");
|
|
|
|
let output = Command::new(compiler_path())
|
|
.arg("--json-diagnostics")
|
|
.arg("build")
|
|
.arg(&fixture)
|
|
.arg("-o")
|
|
.arg(&output_path)
|
|
.arg("--manifest")
|
|
.arg(&manifest_path)
|
|
.env("GLAGOL_CLANG", &missing_clang)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run glagol build: {}", err));
|
|
|
|
assert_exit_code("json missing clang", &output, 3);
|
|
assert_json_diagnostic("json missing clang", &output, "ToolchainUnavailable");
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
manifest.contains(" (mode build)\n")
|
|
&& manifest.contains(" (success false)\n")
|
|
&& manifest.contains(" (diagnostics-encoding json)\n")
|
|
&& manifest.contains("ToolchainUnavailable"),
|
|
"missing clang JSON manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_records_successful_check_fmt_and_test_v1_1_commands() {
|
|
let source = r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#;
|
|
let fixture = write_fixture("manifest-success-commands", source);
|
|
|
|
let check_manifest = temp_path("manifest-check-success", "manifest.slo");
|
|
let check = run_glagol([
|
|
"check".as_ref(),
|
|
"--manifest".as_ref(),
|
|
check_manifest.as_os_str(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success_stdout("manifest check success", check, "");
|
|
let manifest = read_manifest(&check_manifest);
|
|
assert!(
|
|
manifest.contains(" (mode check)\n")
|
|
&& manifest.contains(" (success true)\n")
|
|
&& manifest.contains(" (kind no-output)\n")
|
|
&& !manifest.contains(" (stdout "),
|
|
"check success manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
|
|
let fmt_manifest = temp_path("manifest-fmt-success", "manifest.slo");
|
|
let fmt = run_glagol([
|
|
"fmt".as_ref(),
|
|
"--manifest".as_ref(),
|
|
fmt_manifest.as_os_str(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("manifest fmt success", &fmt);
|
|
let manifest = read_manifest(&fmt_manifest);
|
|
assert!(
|
|
manifest.contains(" (mode format)\n")
|
|
&& manifest.contains(" (success true)\n")
|
|
&& manifest.contains(" (kind formatted-source)\n")
|
|
&& manifest.contains("(fn main () -> i32"),
|
|
"fmt success manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
|
|
let test_fixture = write_fixture(
|
|
"manifest-test-success",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "true"
|
|
true)
|
|
"#,
|
|
);
|
|
let test_manifest = temp_path("manifest-test-success", "manifest.slo");
|
|
let test = run_glagol([
|
|
"test".as_ref(),
|
|
"--manifest".as_ref(),
|
|
test_manifest.as_os_str(),
|
|
test_fixture.as_os_str(),
|
|
]);
|
|
assert_success("manifest test success", &test);
|
|
let manifest = read_manifest(&test_manifest);
|
|
assert_manifest_test_report(&manifest, 1, 1, 0, 0);
|
|
assert!(
|
|
manifest.contains(" (mode test)\n") && manifest.contains(" (success true)\n"),
|
|
"test success manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_records_failed_test_counts_after_execution_begins() {
|
|
let fixture = write_fixture(
|
|
"manifest-failed-test",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "passes"
|
|
true)
|
|
|
|
(test "fails"
|
|
false)
|
|
"#,
|
|
);
|
|
let manifest_path = temp_path("manifest-failed-test", "manifest.slo");
|
|
|
|
let output = run_glagol([
|
|
"test".as_ref(),
|
|
"--manifest".as_ref(),
|
|
manifest_path.as_os_str(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_exit_code("manifest failed test", &output, 1);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"failed test invocation wrote primary stdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestFailed"),
|
|
"failed test diagnostic mismatch:\n{}",
|
|
stderr
|
|
);
|
|
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert_manifest_test_report(&manifest, 2, 1, 1, 0);
|
|
assert!(
|
|
manifest.contains(" (mode test)\n")
|
|
&& manifest.contains(" (success false)\n")
|
|
&& manifest.contains("TestFailed"),
|
|
"failed test manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn manifest_records_source_diagnostic_failure() {
|
|
let fixture = write_fixture(
|
|
"manifest-source-failure",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn id ((value i32)) -> i32
|
|
value)
|
|
|
|
(fn main () -> i32
|
|
(id true))
|
|
"#,
|
|
);
|
|
let manifest_path = temp_path("manifest-source-failure", "manifest.slo");
|
|
|
|
let output = run_glagol([
|
|
"check".as_ref(),
|
|
"--manifest".as_ref(),
|
|
manifest_path.as_os_str(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
|
|
assert_exit_code("manifest source failure", &output, 1);
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
manifest.contains(" (mode check)\n")
|
|
&& manifest.contains(" (success false)\n")
|
|
&& manifest.contains(" (kind diagnostics)\n")
|
|
&& manifest.contains("TypeMismatch"),
|
|
"source diagnostic manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn usage_failure_writes_manifest_when_manifest_path_was_parsed() {
|
|
let fixture = write_fixture(
|
|
"usage-manifest",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
let manifest_path = temp_path("usage-manifest", "manifest.slo");
|
|
|
|
let output = run_glagol([
|
|
"build".as_ref(),
|
|
"--manifest".as_ref(),
|
|
manifest_path.as_os_str(),
|
|
fixture.as_os_str(),
|
|
]);
|
|
|
|
assert_exit_code("build missing output", &output, 2);
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
manifest.contains(" (success false)\n")
|
|
&& manifest.contains(" (mode usage-error)\n")
|
|
&& manifest.contains("`build` requires `-o <binary>`"),
|
|
"usage failure manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn build_reports_missing_clang_as_toolchain_failure_with_manifest() {
|
|
let fixture = write_fixture(
|
|
"missing-clang",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
0)
|
|
"#,
|
|
);
|
|
let output_path = temp_path("missing-clang", "bin");
|
|
let manifest_path = temp_path("missing-clang", "manifest.slo");
|
|
let missing_clang = temp_path("missing-clang", "not-a-clang");
|
|
|
|
let output = Command::new(compiler_path())
|
|
.arg("build")
|
|
.arg(&fixture)
|
|
.arg("-o")
|
|
.arg(&output_path)
|
|
.arg("--manifest")
|
|
.arg(&manifest_path)
|
|
.env("GLAGOL_CLANG", &missing_clang)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run glagol build: {}", err));
|
|
|
|
assert_exit_code("missing clang", &output, 3);
|
|
assert!(
|
|
!output_path.exists(),
|
|
"build left output behind after missing clang"
|
|
);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains("cannot find Clang executable"),
|
|
"missing clang diagnostic mismatch:\n{}",
|
|
stderr
|
|
);
|
|
let manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
manifest.contains(" (mode build)\n")
|
|
&& manifest.contains(" (success false)\n")
|
|
&& manifest.contains("ToolchainUnavailable"),
|
|
"missing clang manifest mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires hosted clang and system linker"]
|
|
fn hosted_build_smoke_builds_and_runs_promoted_v1_1_examples() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping hosted build smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let cases = [
|
|
("add", "add.slo", Some(0), "42\n"),
|
|
("while", "while.slo", Some(4), ""),
|
|
("struct-value-flow", "struct-value-flow.slo", Some(42), ""),
|
|
("array-value-flow", "array-value-flow.slo", Some(11), ""),
|
|
(
|
|
"option-result-payload",
|
|
"option-result-payload.slo",
|
|
Some(0),
|
|
"",
|
|
),
|
|
(
|
|
"option-result-match",
|
|
"option-result-match.slo",
|
|
Some(0),
|
|
"",
|
|
),
|
|
(
|
|
"string-print",
|
|
"string-print.slo",
|
|
Some(0),
|
|
"hello\nline\nquote\"slash\\tab\t\n",
|
|
),
|
|
];
|
|
|
|
for (name, fixture_name, expected_status, expected_stdout) in cases {
|
|
let fixture = manifest.join("../examples").join(fixture_name);
|
|
let output_path = temp_path(&format!("hosted-build-{}", name), "bin");
|
|
let manifest_path = temp_path(&format!("hosted-build-{}", name), "manifest.slo");
|
|
|
|
let mut build_command = Command::new(compiler_path());
|
|
build_command
|
|
.arg("build")
|
|
.arg(&fixture)
|
|
.arg("-o")
|
|
.arg(&output_path)
|
|
.arg("--manifest")
|
|
.arg(&manifest_path)
|
|
.env("GLAGOL_CLANG", &clang);
|
|
configure_clang_runtime_env(&mut build_command, &clang);
|
|
let build = build_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run glagol build for `{}`: {}", name, err));
|
|
assert_success(&format!("hosted build {}", name), &build);
|
|
let build_manifest = read_manifest(&manifest_path);
|
|
assert!(
|
|
build_manifest.contains(" (mode build)\n")
|
|
&& build_manifest.contains(" (success true)\n")
|
|
&& build_manifest.contains(" (kind native-executable)\n")
|
|
&& build_manifest.contains(" (hosted-build\n"),
|
|
"hosted build manifest mismatch for `{}`:\n{}",
|
|
name,
|
|
build_manifest
|
|
);
|
|
|
|
let run = Command::new(&output_path)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", output_path.display(), err));
|
|
assert_eq!(
|
|
run.status.code(),
|
|
expected_status,
|
|
"{} exit status mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
expected_stdout,
|
|
"{} stdout mismatch",
|
|
name
|
|
);
|
|
}
|
|
}
|
|
|
|
fn run_glagol<const N: usize>(args: [&std::ffi::OsStr; N]) -> Output {
|
|
Command::new(compiler_path())
|
|
.args(args)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run glagol: {}", err))
|
|
}
|
|
|
|
fn compiler_path() -> &'static str {
|
|
env!("CARGO_BIN_EXE_glagol")
|
|
}
|
|
|
|
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
let path = temp_path(&format!("{}-{}", name, id), "slo");
|
|
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
path
|
|
}
|
|
|
|
fn temp_path(name: &str, extension: &str) -> PathBuf {
|
|
let mut path = std::env::temp_dir();
|
|
path.push(format!(
|
|
"glagol-v1-1-{}-{}.{}",
|
|
std::process::id(),
|
|
name,
|
|
extension
|
|
));
|
|
path
|
|
}
|
|
|
|
fn read_manifest(path: &Path) -> String {
|
|
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
|
|
}
|
|
|
|
fn assert_success(context: &str, output: &Output) {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
output.status.success(),
|
|
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
context,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
}
|
|
|
|
fn assert_success_stdout(context: &str, output: Output, expected: &str) {
|
|
assert_success(context, &output);
|
|
let stdout = String::from_utf8(output.stdout).expect("stdout is UTF-8");
|
|
assert_eq!(stdout, expected, "{} stdout mismatch", context);
|
|
}
|
|
|
|
fn assert_exit_code(context: &str, output: &Output, expected: i32) {
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(expected),
|
|
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
context,
|
|
String::from_utf8_lossy(&output.stdout),
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
fn assert_json_diagnostic(context: &str, output: &Output, expected_code: &str) {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"{} wrote stdout on diagnostic failure:\n{}",
|
|
context,
|
|
stdout
|
|
);
|
|
assert!(
|
|
!stderr.trim().is_empty(),
|
|
"{} did not write a JSON diagnostic",
|
|
context
|
|
);
|
|
assert!(
|
|
stderr
|
|
.lines()
|
|
.all(|line| line.starts_with('{') && line.ends_with('}')),
|
|
"{} stderr contained non-JSON diagnostic text:\n{}",
|
|
context,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains(r#""schema":"slovo.diagnostic""#)
|
|
&& stderr.contains(r#""version":1"#)
|
|
&& stderr.contains(&format!(r#""code":"{}""#, expected_code))
|
|
&& stderr.contains(r#""severity":"error""#)
|
|
&& stderr.contains(r#""file":"#)
|
|
&& stderr.contains(r#""span":"#),
|
|
"{} JSON diagnostic did not contain expected fields:\n{}",
|
|
context,
|
|
stderr
|
|
);
|
|
}
|
|
|
|
fn assert_manifest_test_report(
|
|
manifest: &str,
|
|
total: usize,
|
|
passed: usize,
|
|
failed: usize,
|
|
skipped: usize,
|
|
) {
|
|
assert!(
|
|
manifest.contains(" (test-report\n")
|
|
&& manifest.contains(&format!(" (total {})\n", total))
|
|
&& manifest.contains(&format!(" (passed {})\n", passed))
|
|
&& manifest.contains(&format!(" (failed {})\n", failed))
|
|
&& manifest.contains(&format!(" (skipped {})\n", skipped)),
|
|
"manifest test report mismatch:\n{}",
|
|
manifest
|
|
);
|
|
}
|
|
|
|
fn find_clang() -> Option<PathBuf> {
|
|
if let Some(path) = std::env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) {
|
|
return Some(PathBuf::from(path));
|
|
}
|
|
|
|
let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang");
|
|
if hermetic_clang.is_file() {
|
|
return Some(hermetic_clang);
|
|
}
|
|
|
|
find_on_path("clang")
|
|
}
|
|
|
|
fn find_on_path(program: &str) -> Option<PathBuf> {
|
|
let path = std::env::var_os("PATH")?;
|
|
std::env::split_paths(&path)
|
|
.map(|dir| dir.join(program))
|
|
.find(|candidate| candidate.is_file())
|
|
}
|
|
|
|
fn configure_clang_runtime_env(command: &mut Command, clang: &Path) {
|
|
if !clang.starts_with("/tmp/glagol-clang-root") {
|
|
return;
|
|
}
|
|
|
|
let root = Path::new("/tmp/glagol-clang-root");
|
|
let lib64 = root.join("usr/lib64");
|
|
let lib = root.join("usr/lib");
|
|
let mut paths = vec![lib64, lib];
|
|
|
|
if let Some(existing) = std::env::var_os("LD_LIBRARY_PATH") {
|
|
paths.extend(std::env::split_paths(&existing));
|
|
}
|
|
|
|
let joined = std::env::join_paths(paths).expect("join LD_LIBRARY_PATH");
|
|
command.env("LD_LIBRARY_PATH", joined);
|
|
}
|