slovo/compiler/tests/standard_json_source_facade_alpha.rs

250 lines
7.3 KiB
Rust

use std::{
ffi::OsStr,
fs,
path::Path,
process::{Command, Output},
};
const EXPECTED_LOCAL_TEST_OUTPUT: &str = concat!(
"test \"explicit local json quote escapes facade\" ... ok\n",
"test \"explicit local json scalar values facade\" ... ok\n",
"test \"explicit local json fields facade\" ... ok\n",
"test \"explicit local json arrays objects facade\" ... ok\n",
"test \"explicit local json facade all\" ... ok\n",
"5 test(s) passed\n",
);
const EXPECTED_STD_IMPORT_TEST_OUTPUT: &str = concat!(
"test \"explicit std json quote escapes facade\" ... ok\n",
"test \"explicit std json scalar values facade\" ... ok\n",
"test \"explicit std json fields facade\" ... ok\n",
"test \"explicit std json arrays objects facade\" ... ok\n",
"test \"explicit std json facade all\" ... ok\n",
"5 test(s) passed\n",
);
const STANDARD_JSON_SOURCE_FACADE_ALPHA: &[&str] = &[
"quote_string",
"null_value",
"bool_value",
"i32_value",
"u32_value",
"i64_value",
"u64_value",
"f64_value",
"field_string",
"field_bool",
"field_i32",
"field_u32",
"field_i64",
"field_u64",
"field_f64",
"field_null",
"array0",
"array1",
"array2",
"array3",
"object0",
"object1",
"object2",
"object3",
];
const STANDARD_JSON_RUNTIME_NAMES: &[&str] = &[
"std.json.quote_string",
"std.string.concat",
"std.num.i32_to_string",
"std.num.u32_to_string",
"std.num.i64_to_string",
"std.num.u64_to_string",
"std.num.f64_to_string",
];
#[test]
fn standard_json_source_facade_project_checks_formats_and_tests() {
let project =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-layout-local-json");
assert_local_json_fixture_is_source_authored(&project);
let fmt = run_glagol([
OsStr::new("fmt"),
OsStr::new("--check"),
project.as_os_str(),
]);
assert_success("std layout local json fmt --check", &fmt);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std layout local json check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(
test,
EXPECTED_LOCAL_TEST_OUTPUT,
"std layout local json test output",
);
}
#[test]
fn standard_json_std_import_project_checks_formats_and_tests() {
let project =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/projects/std-import-json");
assert_std_import_json_fixture_uses_repo_std(&project);
let fmt = run_glagol([
OsStr::new("fmt"),
OsStr::new("--check"),
project.as_os_str(),
]);
assert_success("std import json fmt --check", &fmt);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std import json check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(
test,
EXPECTED_STD_IMPORT_TEST_OUTPUT,
"std import json test output",
);
}
fn assert_local_json_fixture_is_source_authored(project: &Path) {
let json = read(&project.join("src/json.slo"));
let main = read(&project.join("src/main.slo"));
assert!(
json.starts_with("(module json (export "),
"json.slo must stay an explicit local module export"
);
assert!(
main.starts_with("(module main)\n\n(import json ("),
"main.slo must stay an explicit local json import"
);
assert!(
!main.contains("(import std") && !main.contains("(import slovo.std"),
"json fixture must not depend on automatic or package std imports"
);
assert_json_source_shape(&json, &main, "local json fixture");
assert!(
!main.contains("std."),
"local json main fixture must use only local imports"
);
}
fn assert_std_import_json_fixture_uses_repo_std(project: &Path) {
let std_json = read(&Path::new(env!("CARGO_MANIFEST_DIR")).join("../lib/std/json.slo"));
let main = read(&project.join("src/main.slo"));
assert!(
!project.join("src/json.slo").exists(),
"std import json fixture must use repo-root std/json.slo, not a local copy"
);
assert!(
main.starts_with("(module main)\n\n(import std.json ("),
"std import json fixture must use explicit `std.json` import syntax"
);
assert_json_source_shape(&std_json, &main, "repo std.json fixture");
}
fn assert_json_source_shape(json: &str, main: &str, context: &str) {
for runtime_name in STANDARD_JSON_RUNTIME_NAMES {
assert!(
json.contains(runtime_name),
"{} must wrap or compose `{}`",
context,
runtime_name
);
}
assert_std_only_contains(json, STANDARD_JSON_RUNTIME_NAMES, context);
assert!(
!json.contains("parse")
&& !json.contains("token")
&& !json.contains("map")
&& !json.contains("unicode")
&& !json.contains("schema")
&& !json.contains("stream")
&& !main.contains("parse")
&& !main.contains("token")
&& !main.contains("map")
&& !main.contains("unicode")
&& !main.contains("schema")
&& !main.contains("stream"),
"{} must not claim deferred JSON parsing or richer data APIs",
context
);
for helper in STANDARD_JSON_SOURCE_FACADE_ALPHA {
assert!(
json.contains(&format!("(fn {} ", helper)),
"{} is missing source facade `{}`",
context,
helper
);
assert!(
main.contains(helper),
"{} main fixture import/use is missing `{}`",
context,
helper
);
}
}
fn assert_std_only_contains(source: &str, allowed: &[&str], context: &str) {
let mut remaining = source.to_string();
for name in allowed {
remaining = remaining.replace(name, "");
}
assert!(
!remaining.contains("std."),
"{} introduced unexpected compiler-known std names",
context
);
}
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)
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
.output()
.expect("run glagol")
}
fn read(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(output: Output, expected: &str, context: &str) {
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_eq!(stdout, expected, "{} stdout drifted", context);
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}