refactor(test): hoist duplicated test helpers into ailang-test-support
closes #60 The fixture-corpus filter (NON_PARSEABLE_FIXTURES + list_ail_fixtures) was copy-pasted across three test crates and drifted out of sync as new reject fixtures landed; the examples_dir / workspace_root path walk was reimplemented ~30 more times across the suite, under two spellings (workspace_root / ws_root). Introduce crates/ailang-test-support — a zero-dependency, dev-only leaf crate — as the single home for these helpers: - NON_PARSEABLE_FIXTURES, list_ail_fixtures - workspace_root, examples_dir - canonical_workspace_root (symlink-resolved variant, for the tsan/ race tests that feed the root into native build steps) All integration-test copies across ailang-core, ailang-surface, ailang-prose, ailang-check, and ail now import from the shared crate; the local definitions and their now-orphaned path imports are removed. Path helpers resolve via the support crate's own CARGO_MANIFEST_DIR, so they return the correct absolute paths from any caller. ail_bin helpers are intentionally left in place: they depend on env!("CARGO_BIN_EXE_ail"), which Cargo only defines when compiling the ail crate's own integration tests, so they cannot move to a shared crate. Behaviour-identical: same paths, same fixture lists; full workspace test suite green. Net -177 LOC.
This commit is contained in:
Generated
+9
@@ -11,6 +11,7 @@ dependencies = [
|
|||||||
"ailang-core",
|
"ailang-core",
|
||||||
"ailang-prose",
|
"ailang-prose",
|
||||||
"ailang-surface",
|
"ailang-surface",
|
||||||
|
"ailang-test-support",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"blake3",
|
"blake3",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -25,6 +26,7 @@ dependencies = [
|
|||||||
"ailang-core",
|
"ailang-core",
|
||||||
"ailang-mir",
|
"ailang-mir",
|
||||||
"ailang-surface",
|
"ailang-surface",
|
||||||
|
"ailang-test-support",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -48,6 +50,7 @@ name = "ailang-core"
|
|||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-surface",
|
"ailang-surface",
|
||||||
|
"ailang-test-support",
|
||||||
"blake3",
|
"blake3",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -73,6 +76,7 @@ version = "0.0.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-core",
|
"ailang-core",
|
||||||
"ailang-surface",
|
"ailang-surface",
|
||||||
|
"ailang-test-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -81,11 +85,16 @@ version = "0.0.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"ailang-core",
|
"ailang-core",
|
||||||
"ailang-kernel",
|
"ailang-kernel",
|
||||||
|
"ailang-test-support",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ailang-test-support"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ members = [
|
|||||||
"crates/ailang-kernel",
|
"crates/ailang-kernel",
|
||||||
"crates/ailang-surface",
|
"crates/ailang-surface",
|
||||||
"crates/ailang-prose",
|
"crates/ailang-prose",
|
||||||
|
"crates/ailang-test-support",
|
||||||
"crates/ail",
|
"crates/ail",
|
||||||
]
|
]
|
||||||
# `ail-embed/` is intentionally NOT a member: it depends on the
|
# `ail-embed/` is intentionally NOT a member: it depends on the
|
||||||
@@ -39,6 +40,7 @@ ailang-codegen = { path = "crates/ailang-codegen" }
|
|||||||
ailang-kernel = { path = "crates/ailang-kernel" }
|
ailang-kernel = { path = "crates/ailang-kernel" }
|
||||||
ailang-surface = { path = "crates/ailang-surface" }
|
ailang-surface = { path = "crates/ailang-surface" }
|
||||||
ailang-prose = { path = "crates/ailang-prose" }
|
ailang-prose = { path = "crates/ailang-prose" }
|
||||||
|
ailang-test-support = { path = "crates/ailang-test-support" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ anyhow.workspace = true
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
blake3.workspace = true
|
blake3.workspace = true
|
||||||
|
ailang-test-support.workspace = true
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
//! someone reverts the migration (or breaks the canonical-form
|
//! someone reverts the migration (or breaks the canonical-form
|
||||||
//! acceptance path in the registry), that test goes red.
|
//! acceptance path in the registry), that test goes red.
|
||||||
|
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -19,15 +20,6 @@ fn ail_bin() -> PathBuf {
|
|||||||
PathBuf::from(env!("CARGO_BIN_EXE_ail"))
|
PathBuf::from(env!("CARGO_BIN_EXE_ail"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
// CARGO_MANIFEST_DIR points at `crates/ail`; examples/ sits at the
|
|
||||||
// workspace root, two levels up.
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.join("..")
|
|
||||||
.join("..")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Property: a workspace whose entry module contains a bare
|
/// Property: a workspace whose entry module contains a bare
|
||||||
/// cross-module Type::Con / Term::Ctor reference — here `Mystery_Type`,
|
/// cross-module Type::Con / Term::Ctor reference — here `Mystery_Type`,
|
||||||
/// a name no module in the workspace declares (post-prep.1: in-scope
|
/// a name no module in the workspace declares (post-prep.1: in-scope
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
//! This test guards the most important property of the entire pipeline:
|
//! This test guards the most important property of the entire pipeline:
|
||||||
//! AST → typecheck → LLVM IR → clang → binary → correct stdout.
|
//! AST → typecheck → LLVM IR → clang → binary → correct stdout.
|
||||||
|
|
||||||
|
use ailang_test_support::workspace_root;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -2801,15 +2802,6 @@ fn merge_prose_prints_framed_prompt() {
|
|||||||
|
|
||||||
// ---------- ext-cli.1: CLI accepts `.ail` (Form A) source files ----------
|
// ---------- ext-cli.1: CLI accepts `.ail` (Form A) source files ----------
|
||||||
|
|
||||||
fn workspace_root() -> std::path::PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Property: `ail check` accepts a Form-A `.ail` source file (not just
|
/// Property: `ail check` accepts a Form-A `.ail` source file (not just
|
||||||
/// the Form-B `.ail.json` canonical form) and exits zero on a
|
/// the Form-B `.ail.json` canonical form) and exits zero on a
|
||||||
/// well-formed program. Guards against the post-rename misleading-JSON
|
/// well-formed program. Guards against the post-rename misleading-JSON
|
||||||
|
|||||||
@@ -4,18 +4,16 @@
|
|||||||
//! + `libailang_rt.a`, run it, assert the `s == 25` assertion holds
|
//! + `libailang_rt.a`, run it, assert the `s == 25` assertion holds
|
||||||
//! (exit 0). This is the whole-milestone existence proof.
|
//! (exit 0). This is the whole-milestone existence proof.
|
||||||
|
|
||||||
|
use ailang_test_support::workspace_root;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn c_host_calls_exported_scalar_kernel() {
|
fn c_host_calls_exported_scalar_kernel() {
|
||||||
let fixture = ws_root().join("examples").join("embed_backtest_step.ail");
|
let fixture = workspace_root().join("examples").join("embed_backtest_step.ail");
|
||||||
let host_c = manifest().join("tests").join("embed").join("host.c");
|
let host_c = manifest().join("tests").join("embed").join("host.c");
|
||||||
let outdir = std::env::temp_dir()
|
let outdir = std::env::temp_dir()
|
||||||
.join(format!("ail-embed-e2e-{}", std::process::id()));
|
.join(format!("ail-embed-e2e-{}", std::process::id()));
|
||||||
|
|||||||
@@ -9,18 +9,13 @@
|
|||||||
//!
|
//!
|
||||||
//! Pure reader: writes nothing to the repo.
|
//! Pure reader: writes nothing to the repo.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::workspace_root;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str {
|
fn ail_bin() -> &'static str {
|
||||||
env!("CARGO_BIN_EXE_ail")
|
env!("CARGO_BIN_EXE_ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_root() -> PathBuf {
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ail_build_on_main_free_module_is_rejected_at_cli() {
|
fn ail_build_on_main_free_module_is_rejected_at_cli() {
|
||||||
let fixture = workspace_root()
|
let fixture = workspace_root()
|
||||||
|
|||||||
@@ -4,11 +4,7 @@
|
|||||||
//! Per-thread-ctx build: tsan-clean + exit 0. RED until rc.c gains
|
//! Per-thread-ctx build: tsan-clean + exit 0. RED until rc.c gains
|
||||||
//! ailang_ctx_* + __ail_tls_ctx (link error: undefined symbols).
|
//! ailang_ctx_* + __ail_tls_ctx (link error: undefined symbols).
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cc(args: &[&str], ctx: &str) {
|
fn cc(args: &[&str], ctx: &str) {
|
||||||
let st = Command::new("clang").args(args).status().expect("spawn clang");
|
let st = Command::new("clang").args(args).status().expect("spawn clang");
|
||||||
@@ -17,7 +13,7 @@ fn cc(args: &[&str], ctx: &str) {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rc_accounting_per_ctx_is_tsan_clean() {
|
fn rc_accounting_per_ctx_is_tsan_clean() {
|
||||||
let root = ws_root();
|
let root = ailang_test_support::canonical_workspace_root();
|
||||||
let tmp = std::env::temp_dir().join("ail_m2_rcacct");
|
let tmp = std::env::temp_dir().join("ail_m2_rcacct");
|
||||||
std::fs::create_dir_all(&tmp).unwrap();
|
std::fs::create_dir_all(&tmp).unwrap();
|
||||||
let rc_o = tmp.join("rc.o");
|
let rc_o = tmp.join("rc.o");
|
||||||
@@ -48,7 +44,7 @@ fn rc_accounting_shared_ctx_is_flagged_by_tsan() {
|
|||||||
// calling ailang_rc_alloc/ailang_rc_dec → concurrent
|
// calling ailang_rc_alloc/ailang_rc_dec → concurrent
|
||||||
// ctx->alloc_count++ is a genuine data race tsan MUST report.
|
// ctx->alloc_count++ is a genuine data race tsan MUST report.
|
||||||
// Proves the de-globalisation property is not vacuous.
|
// Proves the de-globalisation property is not vacuous.
|
||||||
let root = ws_root();
|
let root = ailang_test_support::canonical_workspace_root();
|
||||||
let tmp = std::env::temp_dir().join("ail_m2_rcacct_neg");
|
let tmp = std::env::temp_dir().join("ail_m2_rcacct_neg");
|
||||||
std::fs::create_dir_all(&tmp).unwrap();
|
std::fs::create_dir_all(&tmp).unwrap();
|
||||||
let rc_o = tmp.join("rc.o");
|
let rc_o = tmp.join("rc.o");
|
||||||
|
|||||||
@@ -28,12 +28,8 @@
|
|||||||
//! build of any kernel emits `libailang_rt.a`; the C host links the
|
//! build of any kernel emits `libailang_rt.a`; the C host links the
|
||||||
//! runtime archive directly and never calls a kernel.
|
//! runtime archive directly and never calls a kernel.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
|
||||||
}
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
|
|
||||||
/// 8 threads * 2_000_000 cycles — must match the `#define`s in
|
/// 8 threads * 2_000_000 cycles — must match the `#define`s in
|
||||||
@@ -47,7 +43,7 @@ fn global_rc_stats_counters_are_exact_under_thread_contention() {
|
|||||||
let outdir = std::env::temp_dir().join(format!(
|
let outdir = std::env::temp_dir().join(format!(
|
||||||
"ail-rc-global-stats-race-{}", std::process::id()));
|
"ail-rc-global-stats-race-{}", std::process::id()));
|
||||||
std::fs::create_dir_all(&outdir).unwrap();
|
std::fs::create_dir_all(&outdir).unwrap();
|
||||||
let root = ws_root();
|
let root = ailang_test_support::canonical_workspace_root();
|
||||||
|
|
||||||
// Any kernel staticlib build emits libailang_rt.a alongside it.
|
// Any kernel staticlib build emits libailang_rt.a alongside it.
|
||||||
let st = Command::new(ail_bin())
|
let st = Command::new(ail_bin())
|
||||||
|
|||||||
@@ -23,14 +23,12 @@
|
|||||||
//! `e2e.rs::build_and_run_with_rc_stats` (the runtime's fixed
|
//! `e2e.rs::build_and_run_with_rc_stats` (the runtime's fixed
|
||||||
//! `ailang_rc_stats: allocs=N frees=M live=K` shape).
|
//! `ailang_rc_stats: allocs=N frees=M live=K` shape).
|
||||||
|
|
||||||
|
use ailang_test_support::workspace_root;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RcStats {
|
struct RcStats {
|
||||||
@@ -44,7 +42,7 @@ struct RcStats {
|
|||||||
/// `AILANG_RC_STATS=1`, and return the `ailang_ctx_free` readback.
|
/// `AILANG_RC_STATS=1`, and return the `ailang_ctx_free` readback.
|
||||||
fn build_link_run_embed(fixture: &str, host_rel: &str, extra_cc: &[&str]) -> RcStats {
|
fn build_link_run_embed(fixture: &str, host_rel: &str, extra_cc: &[&str]) -> RcStats {
|
||||||
let module = fixture.strip_suffix(".ail").expect("fixture is a .ail file");
|
let module = fixture.strip_suffix(".ail").expect("fixture is a .ail file");
|
||||||
let fixture_path = ws_root().join("examples").join(fixture);
|
let fixture_path = workspace_root().join("examples").join(fixture);
|
||||||
let host_c = manifest().join("tests").join(host_rel);
|
let host_c = manifest().join("tests").join(host_rel);
|
||||||
let outdir = std::env::temp_dir().join(format!(
|
let outdir = std::env::temp_dir().join(format!(
|
||||||
"ail-embed-record-e2e-{}-{}",
|
"ail-embed-record-e2e-{}-{}",
|
||||||
|
|||||||
@@ -2,18 +2,14 @@
|
|||||||
//! produces `lib<entry>.a` (program-only) + a separate
|
//! produces `lib<entry>.a` (program-only) + a separate
|
||||||
//! `libailang_rt.a`, and emits NO executable / NO `@main`.
|
//! `libailang_rt.a`, and emits NO executable / NO `@main`.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::workspace_root;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn staticlib_emit_produces_two_archives() {
|
fn staticlib_emit_produces_two_archives() {
|
||||||
let fixture = ws_root().join("examples").join("embed_backtest_step.ail");
|
let fixture = workspace_root().join("examples").join("embed_backtest_step.ail");
|
||||||
let outdir = std::env::temp_dir()
|
let outdir = std::env::temp_dir()
|
||||||
.join(format!("ail-embed-cli-{}", std::process::id()));
|
.join(format!("ail-embed-cli-{}", std::process::id()));
|
||||||
std::fs::create_dir_all(&outdir).unwrap();
|
std::fs::create_dir_all(&outdir).unwrap();
|
||||||
@@ -39,7 +35,7 @@ fn staticlib_emit_produces_two_archives() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn staticlib_emit_requires_an_export() {
|
fn staticlib_emit_requires_an_export() {
|
||||||
// embed_noentry_baseline has no `(export …)` fn.
|
// embed_noentry_baseline has no `(export …)` fn.
|
||||||
let fixture = ws_root().join("examples")
|
let fixture = workspace_root().join("examples")
|
||||||
.join("embed_noentry_baseline.ail");
|
.join("embed_noentry_baseline.ail");
|
||||||
let outdir = std::env::temp_dir()
|
let outdir = std::env::temp_dir()
|
||||||
.join(format!("ail-embed-noexp-{}", std::process::id()));
|
.join(format!("ail-embed-noexp-{}", std::process::id()));
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
|
||||||
}
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
|
|
||||||
fn build_staticlib(outdir: &PathBuf) {
|
fn build_staticlib(outdir: &PathBuf) {
|
||||||
@@ -16,7 +13,7 @@ fn build_staticlib(outdir: &PathBuf) {
|
|||||||
let st = Command::new(ail_bin())
|
let st = Command::new(ail_bin())
|
||||||
.args(["build", "examples/embed_backtest_step.ail",
|
.args(["build", "examples/embed_backtest_step.ail",
|
||||||
"--emit=staticlib", "--alloc=rc", "-o", outdir.to_str().unwrap()])
|
"--emit=staticlib", "--alloc=rc", "-o", outdir.to_str().unwrap()])
|
||||||
.current_dir(ws_root()).status().expect("ail build");
|
.current_dir(ailang_test_support::canonical_workspace_root()).status().expect("ail build");
|
||||||
assert!(st.success(), "ail build --emit=staticlib failed");
|
assert!(st.success(), "ail build --emit=staticlib failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +21,7 @@ fn build_staticlib(outdir: &PathBuf) {
|
|||||||
fn scalar_swarm_per_ctx_is_tsan_clean_and_matches_oracle() {
|
fn scalar_swarm_per_ctx_is_tsan_clean_and_matches_oracle() {
|
||||||
let outdir = std::env::temp_dir().join("ail_m2_swarm_ok");
|
let outdir = std::env::temp_dir().join("ail_m2_swarm_ok");
|
||||||
build_staticlib(&outdir);
|
build_staticlib(&outdir);
|
||||||
let root = ws_root();
|
let root = ailang_test_support::canonical_workspace_root();
|
||||||
let bin = outdir.join("swarm_ok");
|
let bin = outdir.join("swarm_ok");
|
||||||
let st = Command::new("clang").args([
|
let st = Command::new("clang").args([
|
||||||
"-O1", "-g", "-fsanitize=thread", "-pthread",
|
"-O1", "-g", "-fsanitize=thread", "-pthread",
|
||||||
|
|||||||
@@ -28,14 +28,12 @@
|
|||||||
//! `g_rc_*`, not ctx; the invariant is GLOBAL Σallocs == Σfrees
|
//! `g_rc_*`, not ctx; the invariant is GLOBAL Σallocs == Σfrees
|
||||||
//! across *all* stat lines, not per-ctx-line balance).
|
//! across *all* stat lines, not per-ctx-line balance).
|
||||||
|
|
||||||
|
use ailang_test_support::workspace_root;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RcStats {
|
struct RcStats {
|
||||||
@@ -49,7 +47,7 @@ struct RcStats {
|
|||||||
/// `AILANG_RC_STATS=1`, and return the summed RC-stats readback.
|
/// `AILANG_RC_STATS=1`, and return the summed RC-stats readback.
|
||||||
fn build_link_run_embed(fixture: &str, host_rel: &str, extra_cc: &[&str]) -> RcStats {
|
fn build_link_run_embed(fixture: &str, host_rel: &str, extra_cc: &[&str]) -> RcStats {
|
||||||
let module = fixture.strip_suffix(".ail").expect("fixture is a .ail file");
|
let module = fixture.strip_suffix(".ail").expect("fixture is a .ail file");
|
||||||
let fixture_path = ws_root().join("examples").join(fixture);
|
let fixture_path = workspace_root().join("examples").join(fixture);
|
||||||
let host_c = manifest().join("tests").join(host_rel);
|
let host_c = manifest().join("tests").join(host_rel);
|
||||||
let outdir = std::env::temp_dir().join(format!(
|
let outdir = std::env::temp_dir().join(format!(
|
||||||
"ail-embed-tick-e2e-{}-{}",
|
"ail-embed-tick-e2e-{}-{}",
|
||||||
|
|||||||
@@ -4,18 +4,14 @@
|
|||||||
//! executable-path `MissingEntryMain` rejection — the Decision-5
|
//! executable-path `MissingEntryMain` rejection — the Decision-5
|
||||||
//! IR-readability affordance for the artefact M1 introduced.
|
//! IR-readability affordance for the artefact M1 introduced.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::workspace_root;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||||
fn ws_root() -> PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn emit_ir_staticlib_prints_kernel_forwarder_no_main() {
|
fn emit_ir_staticlib_prints_kernel_forwarder_no_main() {
|
||||||
let fixture = ws_root().join("examples").join("embed_backtest_step.ail");
|
let fixture = workspace_root().join("examples").join("embed_backtest_step.ail");
|
||||||
let out = Command::new(ail_bin())
|
let out = Command::new(ail_bin())
|
||||||
.args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"])
|
.args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"])
|
||||||
.output().expect("ail emit-ir --emit=staticlib");
|
.output().expect("ail emit-ir --emit=staticlib");
|
||||||
@@ -37,7 +33,7 @@ fn emit_ir_staticlib_prints_kernel_forwarder_no_main() {
|
|||||||
fn emit_ir_staticlib_requires_an_export() {
|
fn emit_ir_staticlib_requires_an_export() {
|
||||||
// embed_noentry_baseline has no `(export …)` fn — symmetric with
|
// embed_noentry_baseline has no `(export …)` fn — symmetric with
|
||||||
// build's zero-export guard (embed_staticlib_cli.rs).
|
// build's zero-export guard (embed_staticlib_cli.rs).
|
||||||
let fixture = ws_root().join("examples").join("embed_noentry_baseline.ail");
|
let fixture = workspace_root().join("examples").join("embed_noentry_baseline.ail");
|
||||||
let out = Command::new(ail_bin())
|
let out = Command::new(ail_bin())
|
||||||
.args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"])
|
.args(["emit-ir", fixture.to_str().unwrap(), "--emit=staticlib"])
|
||||||
.output().expect("ail emit-ir --emit=staticlib");
|
.output().expect("ail emit-ir --emit=staticlib");
|
||||||
@@ -54,7 +50,7 @@ fn emit_ir_default_exe_still_requires_main() {
|
|||||||
// Regression: --emit defaults to "exe"; emit-ir on a main-free
|
// Regression: --emit defaults to "exe"; emit-ir on a main-free
|
||||||
// module WITHOUT --emit=staticlib must still hit MissingEntryMain
|
// module WITHOUT --emit=staticlib must still hit MissingEntryMain
|
||||||
// (the new staticlib branch must not alter the default path).
|
// (the new staticlib branch must not alter the default path).
|
||||||
let fixture = ws_root().join("examples").join("embed_backtest_step.ail");
|
let fixture = workspace_root().join("examples").join("embed_backtest_step.ail");
|
||||||
let out = Command::new(ail_bin())
|
let out = Command::new(ail_bin())
|
||||||
.args(["emit-ir", fixture.to_str().unwrap()])
|
.args(["emit-ir", fixture.to_str().unwrap()])
|
||||||
.output().expect("ail emit-ir");
|
.output().expect("ail emit-ir");
|
||||||
|
|||||||
@@ -9,16 +9,9 @@
|
|||||||
//! `float_to_str`, libc `%g` plus the runtime `.0`-fallback for
|
//! `float_to_str`, libc `%g` plus the runtime `.0`-fallback for
|
||||||
//! whole-valued Float results, per Gitea #7).
|
//! whole-valued Float results, per Gitea #7).
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::workspace_root;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn workspace_root() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent().unwrap()
|
|
||||||
.parent().unwrap()
|
|
||||||
.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn floats_example_prints_expected_stdout() {
|
fn floats_example_prints_expected_stdout() {
|
||||||
let root = workspace_root();
|
let root = workspace_root();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
//! changes to the LLVM IR. Snapshots in tests/snapshots/. Update with
|
//! changes to the LLVM IR. Snapshots in tests/snapshots/. Update with
|
||||||
//! UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_.
|
//! UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_.
|
||||||
|
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -44,15 +45,6 @@ fn snapshots_dir() -> PathBuf {
|
|||||||
Path::new(manifest_dir).join("tests").join("snapshots")
|
Path::new(manifest_dir).join("tests").join("snapshots")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest_dir)
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn first_diff_line(a: &str, b: &str) -> Option<(usize, String, String)> {
|
fn first_diff_line(a: &str, b: &str) -> Option<(usize, String, String)> {
|
||||||
let mut la = a.lines();
|
let mut la = a.lines();
|
||||||
|
|||||||
@@ -28,14 +28,7 @@
|
|||||||
//! `workspace_has_typeclasses` gate so every workspace exercises the
|
//! `workspace_has_typeclasses` gate so every workspace exercises the
|
||||||
//! mono pass.
|
//! mono pass.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
d.pop();
|
|
||||||
d.pop();
|
|
||||||
d.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Property: `monomorphise_workspace` must succeed on a workspace that
|
/// Property: `monomorphise_workspace` must succeed on a workspace that
|
||||||
/// (a) flips the typeclass gate (any `Def::Class` + `Def::Instance` is
|
/// (a) flips the typeclass gate (any `Def::Class` + `Def::Instance` is
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
//! diagnostic stream's `code` field. Stdout / runtime behaviour is
|
//! diagnostic stream's `code` field. Stdout / runtime behaviour is
|
||||||
//! out of scope here — the dispatch path is a check-time concern.
|
//! out of scope here — the dispatch path is a check-time concern.
|
||||||
|
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -24,13 +25,6 @@ fn ail_bin() -> PathBuf {
|
|||||||
PathBuf::from(env!("CARGO_BIN_EXE_ail"))
|
PathBuf::from(env!("CARGO_BIN_EXE_ail"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.join("..")
|
|
||||||
.join("..")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_ail_check_json(entry: &str) -> std::process::Output {
|
fn run_ail_check_json(entry: &str) -> std::process::Output {
|
||||||
Command::new(ail_bin())
|
Command::new(ail_bin())
|
||||||
.args(["check", "--json", examples_dir().join(entry).to_str().unwrap()])
|
.args(["check", "--json", examples_dir().join(entry).to_str().unwrap()])
|
||||||
|
|||||||
@@ -20,58 +20,13 @@
|
|||||||
//! were the only consumers of `.ail.json` walks and are retired
|
//! were the only consumers of `.ail.json` walks and are retired
|
||||||
//! alongside.
|
//! alongside.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn ail_bin() -> &'static str {
|
fn ail_bin() -> &'static str {
|
||||||
env!("CARGO_BIN_EXE_ail")
|
env!("CARGO_BIN_EXE_ail")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_root() -> PathBuf {
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
workspace_root().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fixtures that intentionally do NOT parse — the `#55` cutover
|
|
||||||
/// reject corpus. These are negative fixtures whose whole point is
|
|
||||||
/// that the parser rejects them, so `parse∘render∘parse` is not a
|
|
||||||
/// meaningful property for them and the harness must skip them.
|
|
||||||
///
|
|
||||||
/// Scope is deliberately minimal: only fixtures that fail at the
|
|
||||||
/// PARSE stage belong here. The other `*reject*.ail` cutover
|
|
||||||
/// fixtures (`borrow_return_reject`, `borrow_value_reject`,
|
|
||||||
/// `own_return_provenance_reject`, the `embed_export_*_rejected`
|
|
||||||
/// set, …) parse cleanly and are rejected only later at CHECK time,
|
|
||||||
/// so they round-trip fine and are NOT excluded.
|
|
||||||
///
|
|
||||||
/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The
|
|
||||||
/// binary-ParamMode parser rejects it ("fn-type slot requires a
|
|
||||||
/// mode: write (own T) or (borrow T)").
|
|
||||||
const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"];
|
|
||||||
|
|
||||||
fn list_ail_fixtures() -> Vec<PathBuf> {
|
|
||||||
let dir = examples_dir();
|
|
||||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
|
||||||
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.map(|e| e.path())
|
|
||||||
.filter(|p| {
|
|
||||||
p.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.map(|n| {
|
|
||||||
n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n)
|
|
||||||
})
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
paths.sort();
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
||||||
/// CLI-pipeline idempotency (post-form-a-default-authoring §C3): for
|
/// CLI-pipeline idempotency (post-form-a-default-authoring §C3): for
|
||||||
/// every `.ail` fixture, the user-facing pipeline
|
/// every `.ail` fixture, the user-facing pipeline
|
||||||
/// `ail parse <ail> | ail render | ail parse` is byte-identical
|
/// `ail parse <ail> | ail render | ail parse` is byte-identical
|
||||||
|
|||||||
@@ -6,17 +6,7 @@
|
|||||||
//! Tasks 9 (`missing-constraint`) and 10 (`no-instance`).
|
//! Tasks 9 (`missing-constraint`) and 10 (`no-instance`).
|
||||||
|
|
||||||
use ailang_core::ast::Type;
|
use ailang_core::ast::Type;
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest_dir)
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.parent()
|
|
||||||
.unwrap()
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Property protected: a class method declared in a `Def::Class` must be
|
/// Property protected: a class method declared in a `Def::Class` must be
|
||||||
/// reachable as a name in the module's `ModuleGlobals` so that pass-2
|
/// reachable as a name in the module's `ModuleGlobals` so that pass-2
|
||||||
|
|||||||
@@ -6,16 +6,9 @@
|
|||||||
//! the fixtures live in `<repo>/examples/`.
|
//! the fixtures live in `<repo>/examples/`.
|
||||||
|
|
||||||
use ailang_core::ast::Type;
|
use ailang_core::ast::Type;
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
// crates/ail/ → repo root → examples/
|
|
||||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
d.pop();
|
|
||||||
d.pop();
|
|
||||||
d.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
||||||
// Pick any class-free fixture; `examples/hello.ail.json` is
|
// Pick any class-free fixture; `examples/hello.ail.json` is
|
||||||
|
|||||||
@@ -12,3 +12,6 @@ thiserror.workspace = true
|
|||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ailang-test-support.workspace = true
|
||||||
|
|||||||
@@ -3,13 +3,7 @@
|
|||||||
//! `Int`/`Float`, or whose effect set is non-empty, MUST fail
|
//! `Int`/`Float`, or whose effect set is non-empty, MUST fail
|
||||||
//! `ail check` with the new diagnostic. A scalar+pure export passes.
|
//! `ail check` with the new diagnostic. A scalar+pure export passes.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent().unwrap().parent().unwrap()
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_codes(file: &str) -> Vec<String> {
|
fn check_codes(file: &str) -> Vec<String> {
|
||||||
let path = examples_dir().join(file);
|
let path = examples_dir().join(file);
|
||||||
|
|||||||
@@ -12,16 +12,8 @@
|
|||||||
use ailang_check::{build_check_env, build_module_globals, Env};
|
use ailang_check::{build_check_env, build_module_globals, Env};
|
||||||
use ailang_core::ast::Def;
|
use ailang_core::ast::Def;
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest)
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a parent (crates/ailang-check)")
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a grandparent (crates/)")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Independent reproduction of every workspace-flat env seeding
|
/// Independent reproduction of every workspace-flat env seeding
|
||||||
/// step. Pre-refactor, workspace-flat seeding was split between
|
/// step. Pre-refactor, workspace-flat seeding was split between
|
||||||
|
|||||||
@@ -6,15 +6,7 @@
|
|||||||
|
|
||||||
use ailang_check::check_workspace;
|
use ailang_check::check_workspace;
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
|
||||||
PathBuf::from(manifest)
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a parent (crates/ailang-check)")
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a grandparent (crates/)")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load the named fixture under `examples/`, run check_workspace, and
|
/// Load the named fixture under `examples/`, run check_workspace, and
|
||||||
/// return the diagnostic code list (one entry per Diagnostic).
|
/// return the diagnostic code list (one entry per Diagnostic).
|
||||||
|
|||||||
@@ -8,16 +8,7 @@
|
|||||||
use ailang_check::elaborate_workspace;
|
use ailang_check::elaborate_workspace;
|
||||||
use ailang_mir::{Callee, MTerm, MirWorkspace, Mode, StrRep};
|
use ailang_mir::{Callee, MTerm, MirWorkspace, Mode, StrRep};
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::{Path, PathBuf};
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
/// `<repo>/examples` — resolved from this crate's manifest dir,
|
|
||||||
/// cwd-independent (same pattern as `method_collision_pin.rs`).
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent().expect("crates/ailang-check has parent crates/")
|
|
||||||
.parent().expect("crates/ has parent repo root")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a witness fixture (prelude injected) and elaborate it to MIR.
|
/// Load a witness fixture (prelude injected) and elaborate it to MIR.
|
||||||
fn elaborate_fixture(module: &str) -> MirWorkspace {
|
fn elaborate_fixture(module: &str) -> MirWorkspace {
|
||||||
|
|||||||
@@ -14,15 +14,7 @@
|
|||||||
|
|
||||||
use ailang_check::build_check_env;
|
use ailang_check::build_check_env;
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest)
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a parent (crates/ailang-check)")
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a grandparent (crates/)")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Class-class repurpose: the fixture
|
/// Class-class repurpose: the fixture
|
||||||
/// `examples/test_22b2_method_name_collision_class_class.ail.json`
|
/// `examples/test_22b2_method_name_collision_class_class.ail.json`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use ailang_check::{check_workspace, Severity};
|
use ailang_check::{check_workspace, Severity};
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
/// assert that the linearity check's `suggested_rewrites`
|
/// assert that the linearity check's `suggested_rewrites`
|
||||||
/// payload is non-empty AND each `replacement` parses as a form-A AILang
|
/// payload is non-empty AND each `replacement` parses as a form-A AILang
|
||||||
@@ -30,10 +30,6 @@ fn assert_suggested_rewrites_well_formed(d: &ailang_check::Diagnostic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest).parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn happy_path_resolves_qualified_import() {
|
fn happy_path_resolves_qualified_import() {
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ indexmap.workspace = true
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
ailang-surface = { path = "../ailang-surface" }
|
ailang-surface = { path = "../ailang-surface" }
|
||||||
|
ailang-test-support.workspace = true
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
//! for the lambda-capture-of-loop-binder rejection
|
//! for the lambda-capture-of-loop-binder rejection
|
||||||
//! (loop-binder-captured-by-lambda).
|
//! (loop-binder-captured-by-lambda).
|
||||||
|
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
const EXPECTED: &[&str] = &[
|
const EXPECTED: &[&str] = &[
|
||||||
@@ -40,11 +41,6 @@ const EXPECTED: &[&str] = &[
|
|||||||
"test_recur_type_mismatch.ail.json",
|
"test_recur_type_mismatch.ail.json",
|
||||||
];
|
];
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
crate_dir.parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn examples_ail_json_inventory_matches_carve_outs() {
|
fn examples_ail_json_inventory_matches_carve_outs() {
|
||||||
let expected: BTreeSet<&str> = EXPECTED.iter().copied().collect();
|
let expected: BTreeSet<&str> = EXPECTED.iter().copied().collect();
|
||||||
|
|||||||
@@ -15,15 +15,7 @@
|
|||||||
use ailang_core::canonical;
|
use ailang_core::canonical;
|
||||||
use ailang_core::ast::Type;
|
use ailang_core::ast::Type;
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
use std::path::Path;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let manifest = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest)
|
|
||||||
.parent().expect("CARGO_MANIFEST_DIR has a parent (crates/)")
|
|
||||||
.parent().expect("crates has a parent (workspace root)")
|
|
||||||
.join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_modules_with_same_bare_foo_both_register() {
|
fn two_modules_with_same_bare_foo_both_register() {
|
||||||
|
|||||||
@@ -18,11 +18,7 @@
|
|||||||
use ailang_core::ast::*;
|
use ailang_core::ast::*;
|
||||||
use ailang_core::canonical;
|
use ailang_core::canonical;
|
||||||
use ailang_core::hash::def_hash;
|
use ailang_core::hash::def_hash;
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
fn examples_dir() -> std::path::PathBuf {
|
|
||||||
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
manifest_dir.parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_fn() -> Def {
|
fn sample_fn() -> Def {
|
||||||
Def::Fn(FnDef {
|
Def::Fn(FnDef {
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
//! a separate iteration); the test must not be relaxed.
|
//! a separate iteration); the test must not be relaxed.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||||
|
|
||||||
use ailang_core::ast::{
|
use ailang_core::ast::{
|
||||||
Def, InstanceMethod, Literal, Module, NewArg, ParamMode, Pattern, Term, Type,
|
Def, InstanceMethod, Literal, Module, NewArg, ParamMode, Pattern, Term, Type,
|
||||||
@@ -354,39 +355,6 @@ fn visit_module(m: &Module, observed: &mut HashSet<VariantTag>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
crate_dir.parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fixtures that intentionally do NOT parse — the `#55` cutover
|
|
||||||
/// reject corpus. Negative fixtures whose whole point is that the
|
|
||||||
/// parser rejects them, so they have no AST to scan for variant
|
|
||||||
/// coverage and the scan must skip them. Mirrors the same list in
|
|
||||||
/// `crates/ail/tests/roundtrip_cli.rs`.
|
|
||||||
///
|
|
||||||
/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The
|
|
||||||
/// binary-ParamMode parser rejects it ("fn-type slot requires a
|
|
||||||
/// mode: write (own T) or (borrow T)").
|
|
||||||
const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"];
|
|
||||||
|
|
||||||
fn list_ail_fixtures() -> Vec<PathBuf> {
|
|
||||||
let dir = examples_dir();
|
|
||||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
|
||||||
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.map(|e| e.path())
|
|
||||||
.filter(|p| {
|
|
||||||
p.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.map(|n| n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n))
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
paths.sort();
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_ast_variant_is_observed_in_the_fixture_corpus() {
|
fn every_ast_variant_is_observed_in_the_fixture_corpus() {
|
||||||
let fixtures = list_ail_fixtures();
|
let fixtures = list_ail_fixtures();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
use ailang_core::workspace::{Registry, RegistryEntry, WorkspaceLoadError};
|
use ailang_core::workspace::{Registry, RegistryEntry, WorkspaceLoadError};
|
||||||
use ailang_surface::load_workspace;
|
use ailang_surface::load_workspace;
|
||||||
|
use ailang_test_support::examples_dir;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
// Adapter so this integration crate can call `load_modules_with` with
|
// Adapter so this integration crate can call `load_modules_with` with
|
||||||
@@ -37,10 +38,6 @@ fn load_one_adapter(path: &Path) -> Result<ailang_core::ast::Module, WorkspaceLo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
|
||||||
Path::new(manifest_dir).parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn loads_example_workspace_happy_path() {
|
fn loads_example_workspace_happy_path() {
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ ailang-core.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ailang-surface = { path = "../ailang-surface" }
|
ailang-surface = { path = "../ailang-surface" }
|
||||||
|
ailang-test-support.workspace = true
|
||||||
|
|||||||
@@ -12,16 +12,7 @@
|
|||||||
//! purpose — Iter 20a is the seeding round, and we want every change
|
//! purpose — Iter 20a is the seeding round, and we want every change
|
||||||
//! after this to be visible in a diff.
|
//! after this to be visible in a diff.
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::examples_dir;
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
// tests/ runs with CARGO_MANIFEST_DIR == crates/ailang-prose.
|
|
||||||
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
p.pop();
|
|
||||||
p.pop();
|
|
||||||
p.push("examples");
|
|
||||||
p
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_snapshot(stem: &str) {
|
fn check_snapshot(stem: &str) {
|
||||||
let dir = examples_dir();
|
let dir = examples_dir();
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ thiserror.workspace = true
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
ailang-test-support.workspace = true
|
||||||
|
|||||||
@@ -32,14 +32,7 @@
|
|||||||
//! `every_ail_fixture_matches_its_json_counterpart` (no longer
|
//! `every_ail_fixture_matches_its_json_counterpart` (no longer
|
||||||
//! expressible: only one form is hand-authored post-iter).
|
//! expressible: only one form is hand-authored post-iter).
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||||
|
|
||||||
fn examples_dir() -> PathBuf {
|
|
||||||
// `CARGO_MANIFEST_DIR` is the surface crate root; examples live two
|
|
||||||
// levels up.
|
|
||||||
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
||||||
crate_dir.parent().unwrap().parent().unwrap().join("examples")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retired iter form-a.1 T9:
|
// Retired iter form-a.1 T9:
|
||||||
// - `list_json_fixtures` helper (walked `.ail.json` for the two retired tests below).
|
// - `list_json_fixtures` helper (walked `.ail.json` for the two retired tests below).
|
||||||
@@ -51,34 +44,6 @@ fn examples_dir() -> PathBuf {
|
|||||||
// only one form is hand-authored post-iter; counterparts are derived in-process
|
// only one form is hand-authored post-iter; counterparts are derived in-process
|
||||||
// via `ail parse`.
|
// via `ail parse`.
|
||||||
|
|
||||||
/// Fixtures that intentionally do NOT parse — the `#55` cutover
|
|
||||||
/// reject corpus. Negative fixtures whose whole point is that the
|
|
||||||
/// parser rejects them, so the parse/round-trip properties below are
|
|
||||||
/// not meaningful for them and the corpus scan must skip them.
|
|
||||||
/// Mirrors the same list in `crates/ail/tests/roundtrip_cli.rs`.
|
|
||||||
///
|
|
||||||
/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The
|
|
||||||
/// binary-ParamMode parser rejects it ("fn-type slot requires a
|
|
||||||
/// mode: write (own T) or (borrow T)").
|
|
||||||
const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"];
|
|
||||||
|
|
||||||
fn list_ail_fixtures() -> Vec<PathBuf> {
|
|
||||||
let dir = examples_dir();
|
|
||||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
|
||||||
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
|
||||||
.filter_map(|entry| entry.ok())
|
|
||||||
.map(|e| e.path())
|
|
||||||
.filter(|p| {
|
|
||||||
p.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.map(|n| n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n))
|
|
||||||
.unwrap_or(false)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
paths.sort();
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pins **idempotency-under-print** (property 2 of the post-form-a
|
/// Pins **idempotency-under-print** (property 2 of the post-form-a
|
||||||
/// Roundtrip Invariant, design/contracts/0009-roundtrip-invariant.md): for every
|
/// Roundtrip Invariant, design/contracts/0009-roundtrip-invariant.md): for every
|
||||||
/// well-formed `.ail` text `t`, the composition `parse → print → parse`
|
/// well-formed `.ail` text `t`, the composition `parse → print → parse`
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "ailang-test-support"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
# Zero-dependency leaf crate. Holds test helpers shared across the
|
||||||
|
# workspace's integration-test suites (path navigation, the fixture
|
||||||
|
# corpus filter). Depended on only as a `dev-dependency`, never by
|
||||||
|
# production code.
|
||||||
|
[dependencies]
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
//! Shared helpers for the workspace's integration-test suites.
|
||||||
|
//!
|
||||||
|
//! Several crates' `tests/*.rs` files need the same handful of path
|
||||||
|
//! computations and the same fixture-corpus filter. Before this crate
|
||||||
|
//! they were copy-pasted per file and drifted out of sync. This crate
|
||||||
|
//! is the single source; integration tests depend on it as a
|
||||||
|
//! `dev-dependency` and `use ailang_test_support::*`.
|
||||||
|
//!
|
||||||
|
//! Path helpers resolve relative to *this* crate's `CARGO_MANIFEST_DIR`
|
||||||
|
//! (`crates/ailang-test-support`), so they are independent of which
|
||||||
|
//! crate's test binary calls them.
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Absolute path to the workspace root (the directory containing
|
||||||
|
/// `crates/` and `examples/`).
|
||||||
|
pub fn workspace_root() -> PathBuf {
|
||||||
|
let crate_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
crate_dir
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.to_path_buf()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`workspace_root`] with symlinks resolved and the path normalized
|
||||||
|
/// (`std::fs::canonicalize`). Used by the tsan/race tests that feed the
|
||||||
|
/// root into native build/link steps where a symlinked checkout would
|
||||||
|
/// otherwise leak a non-canonical path.
|
||||||
|
pub fn canonical_workspace_root() -> PathBuf {
|
||||||
|
std::fs::canonicalize(workspace_root()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Absolute path to the `examples/` fixture directory at the workspace
|
||||||
|
/// root.
|
||||||
|
pub fn examples_dir() -> PathBuf {
|
||||||
|
workspace_root().join("examples")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fixtures under `examples/` that intentionally do NOT parse — the
|
||||||
|
/// `#55` cutover reject corpus. Negative fixtures whose whole point is
|
||||||
|
/// that the parser rejects them, so they have no AST and any corpus
|
||||||
|
/// scan must skip them.
|
||||||
|
///
|
||||||
|
/// - `bare_slot_reject.ail`: a bare fn-type slot with no mode. The
|
||||||
|
/// binary-`ParamMode` parser rejects it ("fn-type slot requires a
|
||||||
|
/// mode: write (own T) or (borrow T)").
|
||||||
|
pub const NON_PARSEABLE_FIXTURES: &[&str] = &["bare_slot_reject.ail"];
|
||||||
|
|
||||||
|
/// All parseable `.ail` fixtures under `examples/`, sorted, with the
|
||||||
|
/// [`NON_PARSEABLE_FIXTURES`] reject corpus filtered out.
|
||||||
|
pub fn list_ail_fixtures() -> Vec<PathBuf> {
|
||||||
|
let dir = examples_dir();
|
||||||
|
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||||
|
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.map(|e| e.path())
|
||||||
|
.filter(|p| {
|
||||||
|
p.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.map(|n| n.ends_with(".ail") && !NON_PARSEABLE_FIXTURES.contains(&n))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
paths.sort();
|
||||||
|
paths
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user