237 lines
8.0 KiB
Rust
237 lines
8.0 KiB
Rust
use std::{
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[test]
|
|
fn exp14_project_exercises_released_standard_runtime_surface() {
|
|
let project = write_conformance_project();
|
|
|
|
let fmt_check = run_glagol(["fmt".as_ref(), "--check".as_ref(), project.as_os_str()]);
|
|
assert_success("exp-14 project fmt --check", &fmt_check);
|
|
|
|
let check = run_glagol(["check".as_ref(), project.as_os_str()]);
|
|
assert_success("exp-14 project check", &check);
|
|
|
|
let test = run_glagol(["test".as_ref(), project.as_os_str()]);
|
|
assert_success("exp-14 project test", &test);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&test.stdout),
|
|
concat!(
|
|
"test \"provider imported base\" ... ok\n",
|
|
"test \"concat parse result match\" ... ok\n",
|
|
"test \"vec i32 imported module value\" ... ok\n",
|
|
"test \"vec i64 imported runtime value\" ... ok\n",
|
|
"test \"payloadless enum match\" ... ok\n",
|
|
"test \"time sleep zero preserves score\" ... ok\n",
|
|
"6 test(s) passed\n",
|
|
)
|
|
);
|
|
|
|
let docs = unique_path("docs");
|
|
let doc = run_glagol([
|
|
"doc".as_ref(),
|
|
project.as_os_str(),
|
|
"-o".as_ref(),
|
|
docs.as_os_str(),
|
|
]);
|
|
assert_success("exp-14 project docs", &doc);
|
|
let doc_index = fs::read_to_string(docs.join("index.md")).expect("read exp-14 docs");
|
|
assert!(doc_index.contains("## Module math"));
|
|
assert!(doc_index.contains("## Module main"));
|
|
assert!(doc_index.contains("- `main"));
|
|
assert!(doc_index.contains("- `concat parse result match`"));
|
|
|
|
let binary = unique_path("bin");
|
|
let build = run_glagol([
|
|
"build".as_ref(),
|
|
project.as_os_str(),
|
|
"-o".as_ref(),
|
|
binary.as_os_str(),
|
|
]);
|
|
if build.status.success() {
|
|
let run = Command::new(&binary).output().expect("run exp-14 binary");
|
|
assert_success("exp-14 project binary", &run);
|
|
assert_eq!(String::from_utf8_lossy(&run.stdout), "85\n");
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"exp-14 project binary wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
} else {
|
|
assert!(
|
|
build.stdout.is_empty(),
|
|
"failed exp-14 build wrote stdout:\n{}",
|
|
String::from_utf8_lossy(&build.stdout)
|
|
);
|
|
assert_stderr_contains("exp-14 project build", &build, "ToolchainUnavailable");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn conformance_gate_script_exists_and_names_required_commands() {
|
|
let script = Path::new("../scripts/conformance-gate.sh");
|
|
let text = fs::read_to_string(script).expect("read conformance gate script");
|
|
|
|
assert!(text.starts_with("#!/usr/bin/env bash\nset -euo pipefail\n"));
|
|
assert!(text.contains("scripts/release-gate.sh remains the full"));
|
|
assert!(text.contains("git -C \"${repo_root}\" diff --check"));
|
|
assert!(text.contains("cargo fmt --check"));
|
|
assert!(text.contains("cargo test --test standard_runtime_conformance_alignment"));
|
|
assert!(text.contains("cargo test --test standard_time"));
|
|
assert!(text.contains("cargo test --test promotion_gate promotion_gate_artifacts_are_aligned"));
|
|
}
|
|
|
|
fn write_conformance_project() -> PathBuf {
|
|
let project = unique_path("project");
|
|
fs::create_dir_all(project.join("src")).expect("create exp-14 project src");
|
|
fs::write(
|
|
project.join("slovo.toml"),
|
|
format!(
|
|
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
project.file_name().unwrap().to_string_lossy()
|
|
),
|
|
)
|
|
.expect("write exp-14 manifest");
|
|
fs::write(
|
|
project.join("src/math.slo"),
|
|
concat!(
|
|
"(module math (export imported_base))\n",
|
|
"\n",
|
|
"(fn imported_base () -> i32\n",
|
|
" 20)\n",
|
|
"\n",
|
|
"(test \"provider imported base\"\n",
|
|
" (= (imported_base) 20))\n",
|
|
),
|
|
)
|
|
.expect("write exp-14 math module");
|
|
fs::write(
|
|
project.join("src/main.slo"),
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(import math (imported_base))\n",
|
|
"\n",
|
|
"(enum Signal Red Green)\n",
|
|
"\n",
|
|
"(fn concat_digits () -> string\n",
|
|
" (std.string.concat \"4\" \"2\"))\n",
|
|
"\n",
|
|
"(fn parse_digits () -> (result i32 i32)\n",
|
|
" (std.string.parse_i32_result (concat_digits)))\n",
|
|
"\n",
|
|
"(fn parsed_or_code () -> i32\n",
|
|
" (match (parse_digits)\n",
|
|
" ((ok value)\n",
|
|
" value)\n",
|
|
" ((err code)\n",
|
|
" code)))\n",
|
|
"\n",
|
|
"(fn pair () -> (vec i32)\n",
|
|
" (let values (vec i32) (std.vec.i32.empty))\n",
|
|
" (let first (vec i32) (std.vec.i32.append values (imported_base)))\n",
|
|
" (std.vec.i32.append first (parsed_or_code)))\n",
|
|
"\n",
|
|
"(fn pair_i64 () -> (vec i64)\n",
|
|
" (let values (vec i64) (std.vec.i64.empty))\n",
|
|
" (let first (vec i64) (std.vec.i64.append values 40i64))\n",
|
|
" (std.vec.i64.append first 41i64))\n",
|
|
"\n",
|
|
"(fn vec_i64_bonus () -> i32\n",
|
|
" (match (std.num.i64_to_i32_result (std.vec.i64.index (pair_i64) 1))\n",
|
|
" ((ok value)\n",
|
|
" value)\n",
|
|
" ((err code)\n",
|
|
" code)))\n",
|
|
"\n",
|
|
"(fn signal_code ((signal Signal)) -> i32\n",
|
|
" (match signal\n",
|
|
" ((Signal.Red)\n",
|
|
" 1)\n",
|
|
" ((Signal.Green)\n",
|
|
" 2)))\n",
|
|
"\n",
|
|
"(fn score () -> i32\n",
|
|
" (+ (+ (std.vec.i32.index (pair) 1) (signal_code (Signal.Green))) (vec_i64_bonus)))\n",
|
|
"\n",
|
|
"(fn sleep_zero_then_score () -> i32\n",
|
|
" (std.time.sleep_ms 0)\n",
|
|
" (score))\n",
|
|
"\n",
|
|
"(test \"concat parse result match\"\n",
|
|
" (= (parsed_or_code) 42))\n",
|
|
"\n",
|
|
"(test \"vec i32 imported module value\"\n",
|
|
" (= (std.vec.i32.index (pair) 0) 20))\n",
|
|
"\n",
|
|
"(test \"vec i64 imported runtime value\"\n",
|
|
" (= (vec_i64_bonus) 41))\n",
|
|
"\n",
|
|
"(test \"payloadless enum match\"\n",
|
|
" (= (signal_code (Signal.Green)) 2))\n",
|
|
"\n",
|
|
"(test \"time sleep zero preserves score\"\n",
|
|
" (= (sleep_zero_then_score) 85))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.io.print_i32 (sleep_zero_then_score))\n",
|
|
" 0)\n",
|
|
),
|
|
)
|
|
.expect("write exp-14 main module");
|
|
|
|
project
|
|
}
|
|
|
|
fn unique_path(name: &str) -> PathBuf {
|
|
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
|
let nanos = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.expect("system clock before UNIX_EPOCH")
|
|
.as_nanos();
|
|
std::env::temp_dir().join(format!(
|
|
"glagol-exp14-{}-{}-{}-{}",
|
|
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_stderr_contains(context: &str, output: &Output, needle: &str) {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
stderr.contains(needle),
|
|
"{} stderr did not contain `{}`:\n{}",
|
|
context,
|
|
needle,
|
|
stderr
|
|
);
|
|
}
|