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-prose",
|
||||
"ailang-surface",
|
||||
"ailang-test-support",
|
||||
"anyhow",
|
||||
"blake3",
|
||||
"clap",
|
||||
@@ -25,6 +26,7 @@ dependencies = [
|
||||
"ailang-core",
|
||||
"ailang-mir",
|
||||
"ailang-surface",
|
||||
"ailang-test-support",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -48,6 +50,7 @@ name = "ailang-core"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"ailang-surface",
|
||||
"ailang-test-support",
|
||||
"blake3",
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -73,6 +76,7 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"ailang-core",
|
||||
"ailang-surface",
|
||||
"ailang-test-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -81,11 +85,16 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"ailang-core",
|
||||
"ailang-kernel",
|
||||
"ailang-test-support",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ailang-test-support"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ members = [
|
||||
"crates/ailang-kernel",
|
||||
"crates/ailang-surface",
|
||||
"crates/ailang-prose",
|
||||
"crates/ailang-test-support",
|
||||
"crates/ail",
|
||||
]
|
||||
# `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-surface = { path = "crates/ailang-surface" }
|
||||
ailang-prose = { path = "crates/ailang-prose" }
|
||||
ailang-test-support = { path = "crates/ailang-test-support" }
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
|
||||
@@ -21,3 +21,4 @@ anyhow.workspace = true
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
blake3.workspace = true
|
||||
ailang-test-support.workspace = true
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
//! someone reverts the migration (or breaks the canonical-form
|
||||
//! acceptance path in the registry), that test goes red.
|
||||
|
||||
use ailang_test_support::examples_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -19,15 +20,6 @@ fn ail_bin() -> PathBuf {
|
||||
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
|
||||
/// cross-module Type::Con / Term::Ctor reference — here `Mystery_Type`,
|
||||
/// 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:
|
||||
//! AST → typecheck → LLVM IR → clang → binary → correct stdout.
|
||||
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -2801,15 +2802,6 @@ fn merge_prose_prints_framed_prompt() {
|
||||
|
||||
// ---------- 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
|
||||
/// the Form-B `.ail.json` canonical form) and exits zero on a
|
||||
/// 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
|
||||
//! (exit 0). This is the whole-milestone existence proof.
|
||||
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||
fn ws_root() -> PathBuf {
|
||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 outdir = std::env::temp_dir()
|
||||
.join(format!("ail-embed-e2e-{}", std::process::id()));
|
||||
|
||||
@@ -9,18 +9,13 @@
|
||||
//!
|
||||
//! Pure reader: writes nothing to the repo.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::process::Command;
|
||||
|
||||
fn ail_bin() -> &'static str {
|
||||
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]
|
||||
fn ail_build_on_main_free_module_is_rejected_at_cli() {
|
||||
let fixture = workspace_root()
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
//! Per-thread-ctx build: tsan-clean + exit 0. RED until rc.c gains
|
||||
//! ailang_ctx_* + __ail_tls_ctx (link error: undefined symbols).
|
||||
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) {
|
||||
let st = Command::new("clang").args(args).status().expect("spawn clang");
|
||||
@@ -17,7 +13,7 @@ fn cc(args: &[&str], ctx: &str) {
|
||||
|
||||
#[test]
|
||||
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");
|
||||
std::fs::create_dir_all(&tmp).unwrap();
|
||||
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
|
||||
// ctx->alloc_count++ is a genuine data race tsan MUST report.
|
||||
// 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");
|
||||
std::fs::create_dir_all(&tmp).unwrap();
|
||||
let rc_o = tmp.join("rc.o");
|
||||
|
||||
@@ -28,12 +28,8 @@
|
||||
//! build of any kernel emits `libailang_rt.a`; the C host links the
|
||||
//! runtime archive directly and never calls a kernel.
|
||||
|
||||
use std::path::PathBuf;
|
||||
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") }
|
||||
|
||||
/// 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!(
|
||||
"ail-rc-global-stats-race-{}", std::process::id()));
|
||||
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.
|
||||
let st = Command::new(ail_bin())
|
||||
|
||||
@@ -23,14 +23,12 @@
|
||||
//! `e2e.rs::build_and_run_with_rc_stats` (the runtime's fixed
|
||||
//! `ailang_rc_stats: allocs=N frees=M live=K` shape).
|
||||
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||
fn ws_root() -> PathBuf {
|
||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RcStats {
|
||||
@@ -44,7 +42,7 @@ struct RcStats {
|
||||
/// `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 {
|
||||
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 outdir = std::env::temp_dir().join(format!(
|
||||
"ail-embed-record-e2e-{}-{}",
|
||||
|
||||
@@ -2,18 +2,14 @@
|
||||
//! produces `lib<entry>.a` (program-only) + a separate
|
||||
//! `libailang_rt.a`, and emits NO executable / NO `@main`.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::process::Command;
|
||||
|
||||
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]
|
||||
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()
|
||||
.join(format!("ail-embed-cli-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&outdir).unwrap();
|
||||
@@ -39,7 +35,7 @@ fn staticlib_emit_produces_two_archives() {
|
||||
#[test]
|
||||
fn staticlib_emit_requires_an_export() {
|
||||
// embed_noentry_baseline has no `(export …)` fn.
|
||||
let fixture = ws_root().join("examples")
|
||||
let fixture = workspace_root().join("examples")
|
||||
.join("embed_noentry_baseline.ail");
|
||||
let outdir = std::env::temp_dir()
|
||||
.join(format!("ail-embed-noexp-{}", std::process::id()));
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
use std::process::Command;
|
||||
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 build_staticlib(outdir: &PathBuf) {
|
||||
@@ -16,7 +13,7 @@ fn build_staticlib(outdir: &PathBuf) {
|
||||
let st = Command::new(ail_bin())
|
||||
.args(["build", "examples/embed_backtest_step.ail",
|
||||
"--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");
|
||||
}
|
||||
|
||||
@@ -24,7 +21,7 @@ fn build_staticlib(outdir: &PathBuf) {
|
||||
fn scalar_swarm_per_ctx_is_tsan_clean_and_matches_oracle() {
|
||||
let outdir = std::env::temp_dir().join("ail_m2_swarm_ok");
|
||||
build_staticlib(&outdir);
|
||||
let root = ws_root();
|
||||
let root = ailang_test_support::canonical_workspace_root();
|
||||
let bin = outdir.join("swarm_ok");
|
||||
let st = Command::new("clang").args([
|
||||
"-O1", "-g", "-fsanitize=thread", "-pthread",
|
||||
|
||||
@@ -28,14 +28,12 @@
|
||||
//! `g_rc_*`, not ctx; the invariant is GLOBAL Σallocs == Σfrees
|
||||
//! across *all* stat lines, not per-ctx-line balance).
|
||||
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") }
|
||||
fn manifest() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) }
|
||||
fn ws_root() -> PathBuf {
|
||||
manifest().parent().unwrap().parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RcStats {
|
||||
@@ -49,7 +47,7 @@ struct RcStats {
|
||||
/// `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 {
|
||||
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 outdir = std::env::temp_dir().join(format!(
|
||||
"ail-embed-tick-e2e-{}-{}",
|
||||
|
||||
@@ -4,18 +4,14 @@
|
||||
//! executable-path `MissingEntryMain` rejection — the Decision-5
|
||||
//! IR-readability affordance for the artefact M1 introduced.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::process::Command;
|
||||
|
||||
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]
|
||||
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())
|
||||
.args(["emit-ir", fixture.to_str().unwrap(), "--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() {
|
||||
// embed_noentry_baseline has no `(export …)` fn — symmetric with
|
||||
// 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())
|
||||
.args(["emit-ir", fixture.to_str().unwrap(), "--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
|
||||
// module WITHOUT --emit=staticlib must still hit MissingEntryMain
|
||||
// (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())
|
||||
.args(["emit-ir", fixture.to_str().unwrap()])
|
||||
.output().expect("ail emit-ir");
|
||||
|
||||
@@ -9,16 +9,9 @@
|
||||
//! `float_to_str`, libc `%g` plus the runtime `.0`-fallback for
|
||||
//! whole-valued Float results, per Gitea #7).
|
||||
|
||||
use std::path::PathBuf;
|
||||
use ailang_test_support::workspace_root;
|
||||
use std::process::Command;
|
||||
|
||||
fn workspace_root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent().unwrap()
|
||||
.parent().unwrap()
|
||||
.to_path_buf()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floats_example_prints_expected_stdout() {
|
||||
let root = workspace_root();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
//! changes to the LLVM IR. Snapshots in tests/snapshots/. Update with
|
||||
//! UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_.
|
||||
|
||||
use ailang_test_support::examples_dir;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
@@ -44,15 +45,6 @@ fn snapshots_dir() -> PathBuf {
|
||||
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)> {
|
||||
let mut la = a.lines();
|
||||
|
||||
@@ -28,14 +28,7 @@
|
||||
//! `workspace_has_typeclasses` gate so every workspace exercises the
|
||||
//! mono pass.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn examples_dir() -> PathBuf {
|
||||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
d.pop();
|
||||
d.pop();
|
||||
d.join("examples")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// Property: `monomorphise_workspace` must succeed on a workspace that
|
||||
/// (a) flips the typeclass gate (any `Def::Class` + `Def::Instance` is
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
//! diagnostic stream's `code` field. Stdout / runtime behaviour is
|
||||
//! out of scope here — the dispatch path is a check-time concern.
|
||||
|
||||
use ailang_test_support::examples_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
@@ -24,13 +25,6 @@ fn ail_bin() -> PathBuf {
|
||||
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 {
|
||||
Command::new(ail_bin())
|
||||
.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
|
||||
//! alongside.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||
use std::process::Command;
|
||||
|
||||
fn ail_bin() -> &'static str {
|
||||
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
|
||||
/// every `.ail` fixture, the user-facing pipeline
|
||||
/// `ail parse <ail> | ail render | ail parse` is byte-identical
|
||||
|
||||
@@ -6,17 +6,7 @@
|
||||
//! Tasks 9 (`missing-constraint`) and 10 (`no-instance`).
|
||||
|
||||
use ailang_core::ast::Type;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn examples_dir() -> PathBuf {
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
Path::new(manifest_dir)
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("examples")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// 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
|
||||
|
||||
@@ -6,16 +6,9 @@
|
||||
//! the fixtures live in `<repo>/examples/`.
|
||||
|
||||
use ailang_core::ast::Type;
|
||||
use ailang_test_support::examples_dir;
|
||||
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]
|
||||
fn monomorphise_workspace_is_identity_on_class_free_workspace() {
|
||||
// Pick any class-free fixture; `examples/hello.ail.json` is
|
||||
|
||||
@@ -12,3 +12,6 @@ thiserror.workspace = true
|
||||
indexmap.workspace = true
|
||||
serde.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
|
||||
//! `ail check` with the new diagnostic. A scalar+pure export passes.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn examples_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent().unwrap().parent().unwrap()
|
||||
.join("examples")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
fn check_codes(file: &str) -> Vec<String> {
|
||||
let path = examples_dir().join(file);
|
||||
|
||||
@@ -12,16 +12,8 @@
|
||||
use ailang_check::{build_check_env, build_module_globals, Env};
|
||||
use ailang_core::ast::Def;
|
||||
use ailang_surface::load_workspace;
|
||||
use ailang_test_support::examples_dir;
|
||||
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
|
||||
/// step. Pre-refactor, workspace-flat seeding was split between
|
||||
|
||||
@@ -6,15 +6,7 @@
|
||||
|
||||
use ailang_check::check_workspace;
|
||||
use ailang_surface::load_workspace;
|
||||
use std::path::PathBuf;
|
||||
|
||||
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")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// Load the named fixture under `examples/`, run check_workspace, and
|
||||
/// return the diagnostic code list (one entry per Diagnostic).
|
||||
|
||||
@@ -8,16 +8,7 @@
|
||||
use ailang_check::elaborate_workspace;
|
||||
use ailang_mir::{Callee, MTerm, MirWorkspace, Mode, StrRep};
|
||||
use ailang_surface::load_workspace;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// `<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")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// Load a witness fixture (prelude injected) and elaborate it to MIR.
|
||||
fn elaborate_fixture(module: &str) -> MirWorkspace {
|
||||
|
||||
@@ -14,15 +14,7 @@
|
||||
|
||||
use ailang_check::build_check_env;
|
||||
use ailang_surface::load_workspace;
|
||||
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")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// Class-class repurpose: the fixture
|
||||
/// `examples/test_22b2_method_name_collision_class_class.ail.json`
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use ailang_check::{check_workspace, Severity};
|
||||
use ailang_surface::load_workspace;
|
||||
use std::path::Path;
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
/// assert that the linearity check's `suggested_rewrites`
|
||||
/// 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]
|
||||
fn happy_path_resolves_qualified_import() {
|
||||
|
||||
@@ -14,3 +14,4 @@ indexmap.workspace = true
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
ailang-surface = { path = "../ailang-surface" }
|
||||
ailang-test-support.workspace = true
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//! for the lambda-capture-of-loop-binder rejection
|
||||
//! (loop-binder-captured-by-lambda).
|
||||
|
||||
use ailang_test_support::examples_dir;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
const EXPECTED: &[&str] = &[
|
||||
@@ -40,11 +41,6 @@ const EXPECTED: &[&str] = &[
|
||||
"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]
|
||||
fn examples_ail_json_inventory_matches_carve_outs() {
|
||||
let expected: BTreeSet<&str> = EXPECTED.iter().copied().collect();
|
||||
|
||||
@@ -15,15 +15,7 @@
|
||||
use ailang_core::canonical;
|
||||
use ailang_core::ast::Type;
|
||||
use ailang_surface::load_workspace;
|
||||
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/)")
|
||||
.parent().expect("crates has a parent (workspace root)")
|
||||
.join("examples")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
#[test]
|
||||
fn two_modules_with_same_bare_foo_both_register() {
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
use ailang_core::ast::*;
|
||||
use ailang_core::canonical;
|
||||
use ailang_core::hash::def_hash;
|
||||
|
||||
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")
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
fn sample_fn() -> Def {
|
||||
Def::Fn(FnDef {
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
//! a separate iteration); the test must not be relaxed.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||
|
||||
use ailang_core::ast::{
|
||||
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]
|
||||
fn every_ast_variant_is_observed_in_the_fixture_corpus() {
|
||||
let fixtures = list_ail_fixtures();
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
use ailang_core::workspace::{Registry, RegistryEntry, WorkspaceLoadError};
|
||||
use ailang_surface::load_workspace;
|
||||
use ailang_test_support::examples_dir;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// 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]
|
||||
fn loads_example_workspace_happy_path() {
|
||||
|
||||
@@ -9,3 +9,4 @@ ailang-core.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
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
|
||||
//! after this to be visible in a diff.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
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
|
||||
}
|
||||
use ailang_test_support::examples_dir;
|
||||
|
||||
fn check_snapshot(stem: &str) {
|
||||
let dir = examples_dir();
|
||||
|
||||
@@ -12,3 +12,4 @@ thiserror.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
ailang-test-support.workspace = true
|
||||
|
||||
@@ -32,14 +32,7 @@
|
||||
//! `every_ail_fixture_matches_its_json_counterpart` (no longer
|
||||
//! expressible: only one form is hand-authored post-iter).
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
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")
|
||||
}
|
||||
use ailang_test_support::{examples_dir, list_ail_fixtures};
|
||||
|
||||
// Retired iter form-a.1 T9:
|
||||
// - `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
|
||||
// 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
|
||||
/// Roundtrip Invariant, design/contracts/0009-roundtrip-invariant.md): for every
|
||||
/// 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