Release 1.0.0-beta.19 test discovery foundation

This commit is contained in:
sanjin 2026-05-23 01:01:44 +02:00
parent 3b231b7f21
commit 98f81d2d59
18 changed files with 787 additions and 52 deletions

View File

@ -0,0 +1,87 @@
# 1.0.0-beta.19 Test Discovery And User-Project Conformance Foundation
## Scope
`1.0.0-beta.19` is a compiler/tooling and conformance slice. It does
not change the Slovo source language or standard library surface.
Add deterministic list-only test discovery for:
- `glagol test --list <file|project|workspace>`
- `glagol --run-tests --list <file>` for the legacy single-file path
## Contract
List mode must reuse the same checked front-end path as normal test execution:
parse, lower, type-check, resolve project/workspace inputs, discover tests, and
apply `--filter <substring>`.
The command then lists discovered/selected tests without evaluating test bodies.
It must not execute runtime calls from test bodies, mutate files through test
logic, open sockets through test logic, or otherwise trigger user test
side-effects.
Ordering must remain deterministic and match current test execution discovery:
- single-file tests keep source order
- project tests keep existing module/package discovery order
- workspace tests keep existing workspace/package discovery order
Normal `glagol test` behavior and output remain unchanged unless `--list` is
present. Invalid files, projects, and workspaces still fail through the
existing diagnostic path.
## Output Shape
The initial output format is beta tooling. It should be stable enough for local
release-gate tests, but it is not a frozen public schema.
The output should make selected, skipped, total discovered, and filter state
visible. A concise text shape is enough; a stable JSON/event stream is out of
scope for this slice.
## Non-Scope
This scope does not add:
- source-language syntax
- runtime helper names
- JSON expansion
- parallel test execution
- retries
- tags or groups
- coverage reports
- event streams
- stable artifact-manifest schema freeze
- stable Markdown schema freeze
- LSP or watch behavior
- SARIF or daemon protocols
- package registries
- semver solving
- performance claims
## Acceptance Criteria
- `glagol test --list <file.slo>` lists checked/discovered tests without
executing bodies.
- `glagol test --list <project>` and workspace inputs preserve current
project/workspace ordering.
- `glagol --run-tests --list <file.slo>` works for the legacy single-file path.
- `--filter <substring>` marks/selects the same tests as normal filtered
execution while avoiding body evaluation.
- Normal `glagol test` output stays byte-stable for existing covered cases.
- Invalid inputs still emit existing diagnostics.
- Docs describe beta19 as a released tooling/conformance slice.
- Release-gate coverage includes the focused beta19 test-discovery suite.
## Suggested Gates
```bash
cargo fmt --check
cargo test --test test_discovery_beta19
cargo test --test project_mode
cargo test --test cli_v1_1
cargo test --test diagnostics_schema_beta13
cargo test
./scripts/release-gate.sh
```

View File

@ -0,0 +1,55 @@
# 1.0.0-beta.19 Release Review
Scope: Test Discovery And User-Project Conformance Foundation
## Findings
No blocking findings.
The implementation matches the beta19 contract at the release-blocking level:
`glagol test --list <file|project|workspace>` and legacy
`glagol --run-tests --list <file>` route through checked discovery, avoid test
body evaluation, preserve the existing discovery ordering, honor
`--filter <substring>`, keep ordinary test output unchanged, and are wired into
the release gate through `cargo test --test test_discovery_beta19`.
## Non-Blocking Notes
- Resolved during controller integration: unfiltered list output now prints the
same summary suffix as filtered list output, including `total_discovered`,
`selected`, `passed`, `failed`, `skipped`, and `filter none`.
- Resolved during controller integration: the grammar typo in
`docs/POST_BETA_ROADMAP.md` was corrected.
## Verification Notes
Inspected:
- Working tree status and beta19 diff across CLI parsing/dispatch, project test
mode, test-runner listing, focused tests, docs, version files, and
`scripts/release-gate.sh`.
- Contract drift against
`.llm/BETA_19_TEST_DISCOVERY_AND_CONFORMANCE.md`,
`docs/language/SPEC-v1.md`, release notes, roadmaps, and README beta scope.
- Cached diff status; no cached beta19 changes were present.
Read-only checks run:
- `git diff --check` - passed.
- `git diff --cached --check` - passed.
- Stale-version scan for beta18/beta19 references - no blocking stale current
release references found.
- Conflict-marker and trailing-whitespace scans for the untracked beta19
contract/test files - passed.
Not run:
- `cargo fmt --check`, focused cargo tests, full `cargo test`, and
`./scripts/release-gate.sh`; those commands write build artifacts, and this
review was constrained to read-only commands except for the review file.
## Verdict
Release-ready from this review. No blocking beta19 issues remain in the current
working tree diff. The controller should still run the focused beta19 test suite
and full release gate before tagging.

View File

@ -6,7 +6,7 @@ This repository is the canonical public monorepo for the language design,
standard library source, compiler, runtime, examples, benchmarks, and technical standard library source, compiler, runtime, examples, benchmarks, and technical
documents. documents.
Current release: `1.0.0-beta.18`. Current release: `1.0.0-beta.19`.
## Repository Layout ## Repository Layout
@ -24,7 +24,7 @@ scripts/ local release and document tooling
## Beta Scope ## Beta Scope
`1.0.0-beta.18` keeps the `1.0.0-beta` language baseline, includes the `1.0.0-beta.19` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.1` tooling/install hardening slice, the `1.0.0-beta.2` `1.0.0-beta.1` tooling/install hardening slice, the `1.0.0-beta.2`
runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library
stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics
@ -39,8 +39,9 @@ slice, the `1.0.0-beta.13` diagnostic catalog and schema policy slice, the
`1.0.0-beta.14` benchmark suite catalog and metadata gate, and the `1.0.0-beta.14` benchmark suite catalog and metadata gate, and the
`1.0.0-beta.15` reserved generic collection boundary hardening and collection `1.0.0-beta.15` reserved generic collection boundary hardening and collection
ledger, the `1.0.0-beta.16` string scanning and token boundary foundation, ledger, the `1.0.0-beta.16` string scanning and token boundary foundation,
the `1.0.0-beta.17` JSON primitive scalar parsing foundation, and the the `1.0.0-beta.17` JSON primitive scalar parsing foundation, the
`1.0.0-beta.18` JSON string token parsing foundation. `1.0.0-beta.18` JSON string token parsing foundation, and the
`1.0.0-beta.19` test discovery and user-project conformance foundation.
The language baseline supports practical local command-line, file, and The language baseline supports practical local command-line, file, and
loopback-network programs with: loopback-network programs with:
@ -131,6 +132,13 @@ escapes `\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, and `\t`, and returns
parsing, tokenizer APIs, Unicode escape decoding/normalization, embedded NUL parsing, tokenizer APIs, Unicode escape decoding/normalization, embedded NUL
policy, stable ABI/layout, and a stable stdlib/API freeze remain deferred. policy, stable ABI/layout, and a stable stdlib/API freeze remain deferred.
The `1.0.0-beta.19` test discovery and user-project conformance foundation
adds `glagol test --list <file|project|workspace>` plus legacy
`glagol --run-tests --list <file>` support for listing checked and discovered
tests without executing test bodies. The list mode preserves existing file,
project, and workspace test ordering, honors `--filter <substring>`, and
remains beta tooling rather than a stable output schema.
Still deferred before stable: executable generics, generic aliases, maps/sets, Still deferred before stable: executable generics, generic aliases, maps/sets,
broad package registry semantics, stable Markdown schema, stable stdlib/API broad package registry semantics, stable Markdown schema, stable stdlib/API
compatibility freeze, DNS/TLS/async networking, LSP/watch guarantees, SARIF compatibility freeze, DNS/TLS/async networking, LSP/watch guarantees, SARIF
@ -140,16 +148,11 @@ iterators, additional compiler-known runtime names, stable ABI and layout,
performance claims, stable benchmark JSON metadata schema, and runtime changes performance claims, stable benchmark JSON metadata schema, and runtime changes
for generic collections. for generic collections.
The next likely language slice after `1.0.0-beta.18` should continue from the The beta19 tooling scope is deliberately tooling-only. It does not add parallel
developer-experience, package, benchmark metadata, collection, or string test execution, retries, tags/groups, coverage reports, event streams, stable
processing lanes without claiming executable generics, maps, sets, traits, manifest or Markdown schema guarantees, LSP/watch behavior, SARIF/daemon
inference, monomorphization, iterators, ABI stability, broad runtime changes, protocols, JSON expansion, runtime helper names, source-language syntax,
LSP/watch protocols, SARIF/daemon protocols, registry semantics, stable remote package registries, semver solving, or performance claims.
Markdown schema, stable benchmark JSON schema, a stable `1.0.0` diagnostics
freeze, standard-library/API compatibility freeze, mutable vectors, language
slice/view APIs, additional runtime names, Unicode/grapheme semantics, broader
JSON object/array/full-value parsing, or performance claims until the contract
and gates are explicit.
## Build And Test ## Build And Test

2
compiler/Cargo.lock generated
View File

@ -4,4 +4,4 @@ version = 3
[[package]] [[package]]
name = "glagol" name = "glagol"
version = "1.0.0-beta.18" version = "1.0.0-beta.19"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "glagol" name = "glagol"
version = "1.0.0-beta.18" version = "1.0.0-beta.19"
edition = "2021" edition = "2021"
description = "Glagol, the first compiler for the Slovo language" description = "Glagol, the first compiler for the Slovo language"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -52,6 +52,16 @@ pub fn run_tests(
test_runner::run(file, &checked, filter) test_runner::run(file, &checked, filter)
} }
pub fn list_tests(
file: &str,
source: &str,
filter: Option<&str>,
) -> Result<test_runner::TestRunSuccess, test_runner::TestRunFailure> {
let checked =
checked_program(file, source).map_err(test_runner::TestRunFailure::before_execution)?;
Ok(test_runner::list(&checked, filter))
}
fn checked_program(file: &str, source: &str) -> Result<check::CheckedProgram, Vec<Diagnostic>> { fn checked_program(file: &str, source: &str) -> Result<check::CheckedProgram, Vec<Diagnostic>> {
let tokens = lexer::lex(file, source)?; let tokens = lexer::lex(file, source)?;
let forms = sexpr::parse(file, &tokens)?; let forms = sexpr::parse(file, &tokens)?;

View File

@ -184,7 +184,13 @@ fn run_project_check(invocation: Invocation) -> ! {
} }
fn run_project_test(invocation: Invocation) -> ! { fn run_project_test(invocation: Invocation) -> ! {
match project::run_tests(&invocation.path, invocation.test_filter.as_deref()) { let result = if invocation.test_list {
project::list_tests(&invocation.path, invocation.test_filter.as_deref())
} else {
project::run_tests(&invocation.path, invocation.test_filter.as_deref())
};
match result {
Ok(success) => { Ok(success) => {
let output = success.output; let output = success.output;
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() { let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
@ -679,7 +685,13 @@ fn finish_formatted_source(
fn run_test_mode(invocation: Invocation, source: &str) -> ! { fn run_test_mode(invocation: Invocation, source: &str) -> ! {
let foreign_imports = c_imports_for_manifest(&invocation.path, source); let foreign_imports = c_imports_for_manifest(&invocation.path, source);
match driver::run_tests(&invocation.path, source, invocation.test_filter.as_deref()) { let result = if invocation.test_list {
driver::list_tests(&invocation.path, source, invocation.test_filter.as_deref())
} else {
driver::run_tests(&invocation.path, source, invocation.test_filter.as_deref())
};
match result {
Ok(result) => { Ok(result) => {
let output = result.output; let output = result.output;
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() { let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
@ -1101,6 +1113,7 @@ struct Invocation {
project_template: scaffold::ProjectTemplate, project_template: scaffold::ProjectTemplate,
link_c_paths: Vec<String>, link_c_paths: Vec<String>,
test_filter: Option<String>, test_filter: Option<String>,
test_list: bool,
run_args: Vec<String>, run_args: Vec<String>,
} }
@ -1137,6 +1150,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
let mut project_template = scaffold::ProjectTemplate::Binary; let mut project_template = scaffold::ProjectTemplate::Binary;
let mut link_c_paths = Vec::new(); let mut link_c_paths = Vec::new();
let mut test_filter = None; let mut test_filter = None;
let mut test_list = false;
let mut run_args = Vec::new(); let mut run_args = Vec::new();
let mut no_color = false; let mut no_color = false;
let command_line = raw_args.join(" "); let command_line = raw_args.join(" ");
@ -1345,6 +1359,17 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
command_line: command_line.clone(), command_line: command_line.clone(),
})?); })?);
} }
"--list" => {
if test_list {
return parse_error(
"`--list` was provided more than once",
manifest_path,
diagnostics,
command_line,
);
}
test_list = true;
}
"check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc" | "symbols" "check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc" | "symbols"
if path.is_none() => if path.is_none() =>
{ {
@ -1464,6 +1489,15 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
); );
} }
if test_list && mode != Mode::RunTests {
return parse_error(
"`--list` is only supported with `test` and `--run-tests`",
manifest_path,
diagnostics,
command_line,
);
}
if !run_args.is_empty() && mode != Mode::Run { if !run_args.is_empty() && mode != Mode::Run {
return parse_error( return parse_error(
"`--` program arguments are only supported with `run`", "`--` program arguments are only supported with `run`",
@ -1515,6 +1549,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
project_template, project_template,
link_c_paths, link_c_paths,
test_filter, test_filter,
test_list,
run_args, run_args,
})) }))
} }
@ -1588,6 +1623,7 @@ fn exit_parse_error(err: ParseError, command_line: &str) -> ! {
project_template: scaffold::ProjectTemplate::Binary, project_template: scaffold::ProjectTemplate::Binary,
link_c_paths: Vec::new(), link_c_paths: Vec::new(),
test_filter: None, test_filter: None,
test_list: false,
run_args: Vec::new(), run_args: Vec::new(),
}; };
write_manifest_or_exit( write_manifest_or_exit(
@ -2567,6 +2603,6 @@ fn normalized_output_path(path: &str) -> Option<PathBuf> {
fn print_usage() { fn print_usage() {
eprintln!( eprintln!(
"usage: glagol [check|fmt|test|build|run|clean|symbols] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <file.slo|project> [-- <program-args>...]\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>] [--template binary|library|workspace]\n glagol doc <file.slo|project> -o <dir>\n glagol symbols <file.slo|project|workspace>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] <file.slo>\n glagol --version" "usage: glagol [check|fmt|test|build|run|clean|symbols] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] [--list] <file.slo|project> [-- <program-args>...]\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>] [--template binary|library|workspace]\n glagol doc <file.slo|project> -o <dir>\n glagol symbols <file.slo|project|workspace>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] [--list] <file.slo>\n glagol --version"
); );
} }

View File

@ -452,6 +452,26 @@ pub fn run_tests(
} }
} }
pub fn list_tests(
input: &str,
filter: Option<&str>,
) -> Result<ProjectTestSuccess, ProjectTestFailure> {
let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure {
diagnostics: failure.diagnostics,
report: None,
sources: failure.sources,
artifact: failure.artifact,
})?;
let success = test_runner::list(&checked.program, filter);
Ok(ProjectTestSuccess {
output: success.output,
report: success.report,
sources: checked.sources,
artifact: checked.artifact,
})
}
struct CheckedProject { struct CheckedProject {
program: CheckedProgram, program: CheckedProgram,
sources: Vec<SourceFile>, sources: Vec<SourceFile>,

View File

@ -173,6 +173,39 @@ pub fn run(
} }
} }
pub fn list(program: &CheckedProgram, filter: Option<&str>) -> TestRunSuccess {
let mut output = String::new();
let mut report = TestReport {
total_discovered: program.tests.len(),
selected: 0,
passed: 0,
failed: 0,
skipped: 0,
filter: filter.map(str::to_string),
};
for test in &program.tests {
output.push_str("test ");
write_test_name(&test.name, &mut output);
if let Some(filter) = filter {
if !test.name.contains(filter) {
report.skipped += 1;
output.push_str(" ... skipped\n");
continue;
}
}
report.selected += 1;
output.push_str(" ... selected\n");
}
output.push_str(&format!("{} test(s) selected", report.selected));
write_report_suffix(&report, &mut output);
output.push('\n');
TestRunSuccess { output, report }
}
fn write_report_suffix(report: &TestReport, output: &mut String) { fn write_report_suffix(report: &TestReport, output: &mut String) {
output.push_str(&format!( output.push_str(&format!(
" (total_discovered {}, selected {}, passed {}, failed {}, skipped {}", " (total_discovered {}, selected {}, passed {}, failed {}, skipped {}",
@ -181,6 +214,8 @@ fn write_report_suffix(report: &TestReport, output: &mut String) {
if let Some(filter) = report.filter.as_deref() { if let Some(filter) = report.filter.as_deref() {
output.push_str(", filter "); output.push_str(", filter ");
write_test_name(filter, output); write_test_name(filter, output);
} else {
output.push_str(", filter none");
} }
output.push(')'); output.push(')');
} }

View File

@ -0,0 +1,384 @@
use std::{
fs,
path::PathBuf,
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
#[test]
fn file_list_preserves_order_and_does_not_execute_bodies() {
let fixture = write_fixture(
"file-list",
r#"
(module main)
(test "alpha first"
true)
(test "beta would fail"
false)
"#,
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"file test list",
output,
"test \"alpha first\" ... selected\n\
test \"beta would fail\" ... selected\n\
2 test(s) selected (total_discovered 2, selected 2, passed 0, failed 0, skipped 0, filter none)\n",
);
}
#[test]
fn file_list_filter_marks_selected_and_skipped_in_order() {
let fixture = write_fixture(
"file-list-filter",
r#"
(module main)
(test "alpha first"
false)
(test "beta second"
false)
"#,
);
let output = run_glagol([
"test".as_ref(),
"--list".as_ref(),
fixture.as_os_str(),
"--filter".as_ref(),
"beta".as_ref(),
]);
assert_success_stdout(
"file filtered test list",
output,
"test \"alpha first\" ... skipped\n\
test \"beta second\" ... selected\n\
1 test(s) selected (total_discovered 2, selected 1, passed 0, failed 0, skipped 1, filter \"beta\")\n",
);
}
#[test]
fn legacy_run_tests_list_matches_test_subcommand_list() {
let fixture = write_fixture(
"legacy-list",
r#"
(module main)
(test "legacy first"
false)
(test "legacy second"
false)
"#,
);
let subcommand = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
let legacy = run_glagol([
"--run-tests".as_ref(),
"--list".as_ref(),
fixture.as_os_str(),
]);
assert_success("test --list", &subcommand);
assert_success("legacy --run-tests --list", &legacy);
assert_eq!(
legacy.stdout, subcommand.stdout,
"legacy list output differed from test subcommand"
);
assert!(legacy.stderr.is_empty(), "legacy list wrote stderr");
}
#[test]
fn project_list_preserves_topological_order_and_filter_counts() {
let project = write_project(
"project-list",
&[(
"provider",
"(module provider (export value))\n\n\
(fn value () -> i32\n 1)\n\n\
(test \"provider first\"\n false)\n",
)],
"(module main)\n\n\
(import provider (value))\n\n\
(fn main () -> i32\n (value))\n\n\
(test \"consumer second\"\n false)\n",
);
let output = run_glagol([
"test".as_ref(),
"--list".as_ref(),
project.as_os_str(),
"--filter".as_ref(),
"consumer".as_ref(),
]);
assert_success_stdout(
"project filtered list",
output,
"test \"provider first\" ... skipped\n\
test \"consumer second\" ... selected\n\
1 test(s) selected (total_discovered 2, selected 1, passed 0, failed 0, skipped 1, filter \"consumer\")\n",
);
}
#[test]
fn workspace_list_preserves_package_order_without_running_tests() {
let workspace = write_workspace(
"workspace-list",
"[workspace]\nmembers = [\"packages/app\", \"packages/util\"]\n",
&[
WorkspacePackageSpec {
member: "packages/util",
manifest: "[package]\nname = \"util\"\nversion = \"0.1.0\"\n",
modules: &[(
"util",
"(module util (export answer))\n\n\
(fn answer () -> i32\n 41)\n\n\
(test \"util provider first\"\n false)\n",
)],
},
WorkspacePackageSpec {
member: "packages/app",
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\n",
modules: &[(
"main",
"(module main)\n\n\
(import util.util (answer))\n\n\
(fn main () -> i32\n (answer))\n\n\
(test \"app consumer second\"\n false)\n",
)],
},
],
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), workspace.as_os_str()]);
assert_success_stdout(
"workspace list",
output,
"test \"util provider first\" ... selected\n\
test \"app consumer second\" ... selected\n\
2 test(s) selected (total_discovered 2, selected 2, passed 0, failed 0, skipped 0, filter none)\n",
);
}
#[test]
fn ordinary_test_output_stays_byte_stable() {
let fixture = write_fixture(
"ordinary-test",
r#"
(module main)
(test "alpha first"
true)
(test "beta second"
true)
"#,
);
let subcommand = run_glagol(["test".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"ordinary test",
subcommand,
"test \"alpha first\" ... ok\n\
test \"beta second\" ... ok\n\
2 test(s) passed\n",
);
let legacy = run_glagol(["--run-tests".as_ref(), fixture.as_os_str()]);
assert_success_stdout(
"ordinary legacy run-tests",
legacy,
"test \"alpha first\" ... ok\n\
test \"beta second\" ... ok\n\
2 test(s) passed\n",
);
}
#[test]
fn list_mode_reuses_checked_diagnostics_for_invalid_tests() {
let fixture = write_fixture(
"invalid-list",
r#"
(module main)
(test "not bool"
1)
"#,
);
let output = run_glagol(["test".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_failure_stderr_contains(
"invalid list",
&output,
&[
"TestExpressionNotBool",
"Expected:",
"bool",
"Found:",
"i32",
],
);
assert!(
output.stdout.is_empty(),
"invalid list wrote stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
}
#[test]
fn list_flag_is_rejected_outside_test_mode() {
let fixture = write_fixture(
"list-outside-test",
r#"
(module main)
(fn main () -> i32
0)
"#,
);
let output = run_glagol(["check".as_ref(), "--list".as_ref(), fixture.as_os_str()]);
assert_failure_stderr_contains(
"check --list",
&output,
&["`--list` is only supported with `test` and `--run-tests`"],
);
}
fn write_fixture(name: &str, source: &str) -> PathBuf {
let path = unique_path(name).with_extension("slo");
fs::write(&path, source).expect("write fixture");
path
}
fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf {
let root = unique_path(name);
let src = root.join("src");
fs::create_dir_all(&src).expect("create project src");
fs::write(
root.join("slovo.toml"),
format!(
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
name
),
)
.expect("write project manifest");
fs::write(src.join("main.slo"), main).expect("write project main");
for (module, source) in modules {
fs::write(src.join(format!("{}.slo", module)), source).expect("write project module");
}
root
}
struct WorkspacePackageSpec<'a> {
member: &'a str,
manifest: &'a str,
modules: &'a [(&'a str, &'a str)],
}
fn write_workspace(
name: &str,
workspace_manifest: &str,
packages: &[WorkspacePackageSpec<'_>],
) -> PathBuf {
let root = unique_path(name);
fs::create_dir_all(&root).expect("create workspace root");
fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest");
for package in packages {
let package_root = root.join(package.member);
let src = package_root.join("src");
fs::create_dir_all(&src).expect("create workspace package src");
fs::write(package_root.join("slovo.toml"), package.manifest)
.expect("write workspace package manifest");
for (module, source) in package.modules {
fs::write(src.join(format!("{}.slo", module)), source)
.expect("write workspace package module");
}
}
root
}
fn unique_path(name: &str) -> PathBuf {
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::SeqCst);
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!(
"glagol-test-discovery-beta19-{}-{}-{}-{}",
std::process::id(),
nanos,
id,
name
))
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<std::ffi::OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.output()
.expect("run glagol")
}
fn assert_success(context: &str, output: &Output) {
assert!(
output.status.success(),
"{} failed\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_success_stdout(context: &str, output: Output, expected: &str) {
assert_success(context, &output);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
expected,
"{} stdout mismatch",
context
);
assert!(
output.stderr.is_empty(),
"{} wrote stderr:\n{}",
context,
String::from_utf8_lossy(&output.stderr)
);
}
fn assert_failure_stderr_contains(context: &str, output: &Output, expected: &[&str]) {
assert!(
!output.status.success(),
"{} unexpectedly succeeded\nstdout:\n{}\nstderr:\n{}",
context,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
for needle in expected {
assert!(
stderr.contains(needle),
"{} stderr missing `{}`:\n{}",
context,
needle,
stderr
);
}
}

View File

@ -41,6 +41,21 @@ Released in `1.0.0-beta.1`: `glagol run`, `glagol clean`,
installed std/runtime discovery, README coverage, focused DX tests, and a installed std/runtime discovery, README coverage, focused DX tests, and a
concise release-gate success line. concise release-gate success line.
Released in `1.0.0-beta.19`: test discovery and user-project conformance
tooling. The scope adds the
`glagol test --list <file|project|workspace>` command and legacy
`glagol --run-tests --list <file>` so users and tooling can list
checked/discovered tests without executing test bodies. It preserves existing
single-file, project, and workspace ordering, honors
`--filter <substring>`, and keeps the output beta-scoped rather than a stable
public schema.
Beta19 non-scope: no parallel test execution, retries, tags/groups, coverage
reports, event streams, stable artifact-manifest or Markdown schema freeze,
LSP/watch behavior, SARIF/daemon protocols, JSON expansion, runtime helper
names, source-language syntax, package registries, semver solving, or
performance claims.
Why first: it reduces friction for every later feature and gives users a better Why first: it reduces friction for every later feature and gives users a better
way to exercise the beta. way to exercise the beta.

View File

@ -10,10 +10,41 @@ integration/readiness release, not the first real beta.
## Unreleased ## Unreleased
Next scoped Glagol work is expected to continue after the `1.0.0-beta.18` No active unreleased compiler scope is documented here yet.
JSON string token parsing foundation.
No unreleased compiler scope is committed here yet. ## 1.0.0-beta.19
Release label: `1.0.0-beta.19`
Release date: 2026-05-23
Release state: test discovery and user-project conformance foundation
### Summary
The beta.19 compiler/tooling contract adds deterministic list-only test
discovery without changing normal test execution output:
- `glagol test --list <file|project|workspace>`
- `glagol --run-tests --list <file>` for legacy single-file test execution
- Bump the `glagol` compiler package version to `1.0.0-beta.19`.
- Reuse the same checked discovery path as normal `glagol test` for file,
project, and workspace inputs.
- Preserve current file/project/workspace test ordering.
- Honor `--filter <substring>` by marking selected and skipped discovered
tests without executing test bodies or triggering runtime/test side effects.
- Keep normal `glagol test` and legacy `glagol --run-tests` output unchanged
when `--list` is absent.
- Add focused beta19 release-gate coverage for test discovery.
### Explicit Deferrals
This release does not implement parallel test execution, retries, tags/groups, coverage,
event streams, stable artifact-manifest or Markdown schema freezes,
LSP/watch/SARIF/daemon protocols, JSON expansion, runtime helper names,
source-language syntax, package registries, semver solving, and performance
claims.
## 1.0.0-beta.18 ## 1.0.0-beta.18

View File

@ -22,8 +22,8 @@ general-purpose beta release.
A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests. A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests.
Current stage: `1.0.0-beta.18`, released on 2026-05-23 as a JSON string Current stage: `1.0.0-beta.19`, released on 2026-05-23 as a test discovery
token parsing runtime foundation. It keeps the and user-project conformance foundation. It keeps the
`1.0.0-beta` language/compiler support baseline and includes the `1.0.0-beta` language/compiler support baseline and includes the
`1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2` runtime/resource `1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2` runtime/resource
foundation release, the `1.0.0-beta.3` standard-library stabilization release, foundation release, the `1.0.0-beta.3` standard-library stabilization release,
@ -89,16 +89,24 @@ diagnostics. It keeps object parsing, array parsing, recursive values,
tokenizers, Unicode escape decoding, Unicode normalization, streaming, schema tokenizers, Unicode escape decoding, Unicode normalization, streaming, schema
validation, embedded NUL support in the current null-terminated string ABI, and validation, embedded NUL support in the current null-terminated string ABI, and
stable JSON APIs deferred. stable JSON APIs deferred.
The beta.19 compiler/tooling slice adds
`glagol test --list <file|project|workspace>` and legacy
`glagol --run-tests --list <file>` support. The list action reuses the same
checked discovery path as normal test execution, preserves existing
single-file, project, and workspace ordering, honors `--filter <substring>`,
and avoids executing test bodies. It keeps normal test execution output
unchanged when `--list` is absent.
Next stage target: post-`1.0.0-beta.18` developer-experience, package, and Generic vectors, generic collections, maps, sets, generic stdlib dispatch,
collection/generic planning. Generic vectors, generic collections, maps, sets, runtime collection changes, collection unification, stable human diagnostic
generic stdlib dispatch, runtime collection changes, collection unification, text, stable artifact-manifest or Markdown schema freezes, LSP/watch
stable human diagnostic text, stable Markdown schema, LSP/watch protocols, protocols, SARIF/daemon protocols, re-exports/globs/hierarchical modules,
SARIF/daemon protocols, re-exports/globs/hierarchical modules, registry registry semantics, semver solving, mutable vectors, stable slice/view APIs,
semantics, mutable vectors, stable slice/view APIs, tokenizers, broader JSON tokenizers, broader JSON parsing, runtime helper names, source-language
parsing, iterators, performance claims, ABI/layout stability, and a stable syntax, parallel test execution, retries, tags/groups, coverage/event streams,
stdlib/API compatibility freeze remain unimplemented until a later scoped performance claims, ABI/layout stability, and a stable stdlib/API
contract promotes them explicitly. compatibility freeze remain unimplemented until a later scoped contract
promotes them explicitly.
The final experimental precursor scope is `exp-125`. Its unsigned direct-value The final experimental precursor scope is `exp-125`. Its unsigned direct-value
flow, parse/format runtime lanes, and matching staged stdlib helper breadth flow, parse/format runtime lanes, and matching staged stdlib helper breadth

View File

@ -8,7 +8,7 @@ Historical `exp-*` releases listed here are experimental maturity milestones.
The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an
experimental integration/readiness release, not as a beta maturity claim. experimental integration/readiness release, not as a beta maturity claim.
The current release is `1.0.0-beta.18`, published on 2026-05-23. It keeps the The current release is `1.0.0-beta.19`, published on 2026-05-23. It keeps the
`1.0.0-beta` language surface, includes the first post-beta tooling/install `1.0.0-beta` language surface, includes the first post-beta tooling/install
hardening bundle from `1.0.0-beta.1`, and adds the first runtime/resource hardening bundle from `1.0.0-beta.1`, and adds the first runtime/resource
foundation bundle from `1.0.0-beta.2` plus the first standard-library foundation bundle from `1.0.0-beta.2` plus the first standard-library
@ -28,11 +28,41 @@ collection alias unification and generic reservation slice from
collection ledger from `1.0.0-beta.15`, plus the string scanning and token collection ledger from `1.0.0-beta.15`, plus the string scanning and token
boundary foundation from `1.0.0-beta.16`, and the JSON primitive scalar boundary foundation from `1.0.0-beta.16`, and the JSON primitive scalar
parsing foundation from `1.0.0-beta.17`, plus the JSON string token parsing parsing foundation from `1.0.0-beta.17`, plus the JSON string token parsing
foundation from `1.0.0-beta.18`. foundation from `1.0.0-beta.18`, and the test discovery and user-project
conformance foundation from `1.0.0-beta.19`.
## Unreleased ## Unreleased
No unreleased language scope is committed here yet. No active unreleased language scope is documented here yet.
## 1.0.0-beta.19
Release label: `1.0.0-beta.19`
Release name: Test Discovery And User-Project Conformance Foundation
Release date: 2026-05-23
Status: released beta tooling/conformance foundation on the `1.0.0-beta`
language baseline.
The beta19 contract is tooling/conformance only. It adds deterministic test
discovery listing for:
- `glagol test --list <file|project|workspace>`
- `glagol --run-tests --list <file>` for the legacy single-file test path
List mode parses, lowers, type-checks, and discovers tests through the same
front-end path as normal test execution, then prints the discovered/selected
tests without evaluating their bodies. It preserves current single-file,
project, and workspace test ordering, honors `--filter <substring>`, and
leaves normal `glagol test` execution output unchanged.
This release does not add source-language syntax, runtime helper names, JSON
expansion, parallel test execution, retries, tags/groups, coverage reports,
event streams, stable manifest schemas, stable Markdown schemas, LSP/watch
behavior, SARIF/daemon protocols, package registries, semver solving, or
performance claims.
## 1.0.0-beta.18 ## 1.0.0-beta.18

View File

@ -10,8 +10,9 @@ Long-horizon planning lives in
release train from the historical `v2.0.0-beta.1` tag toward and beyond the release train from the historical `v2.0.0-beta.1` tag toward and beyond the
first real general-purpose beta Slovo contract. first real general-purpose beta Slovo contract.
Current stage: `1.0.0-beta.18`, released on 2026-05-23 as a post-beta JSON Current stage: `1.0.0-beta.19`, released on 2026-05-23 as a post-beta test
string token parsing foundation. It keeps the `1.0.0-beta` language discovery and user-project conformance foundation. It keeps the
`1.0.0-beta` language
contract and includes the `1.0.0-beta.1` tooling hardening release, the contract and includes the `1.0.0-beta.1` tooling hardening release, the
`1.0.0-beta.2` runtime/resource foundation release, the `1.0.0-beta.3` `1.0.0-beta.2` runtime/resource foundation release, the `1.0.0-beta.3`
standard-library stabilization release, the `1.0.0-beta.4` standard-library stabilization release, the `1.0.0-beta.4`
@ -25,7 +26,8 @@ documentation, `1.0.0-beta.12` concrete vector helper parity,
benchmark suite catalog and metadata gate, `1.0.0-beta.15` reserved generic benchmark suite catalog and metadata gate, `1.0.0-beta.15` reserved generic
collection boundary hardening and collection ledger, and `1.0.0-beta.16` collection boundary hardening and collection ledger, and `1.0.0-beta.16`
string scanning and token boundary helpers, `1.0.0-beta.17` JSON primitive string scanning and token boundary helpers, `1.0.0-beta.17` JSON primitive
scalar token parsing, and `1.0.0-beta.18` JSON string token parsing. scalar token parsing, `1.0.0-beta.18` JSON string token parsing, and
`1.0.0-beta.19` test discovery and user-project conformance tooling.
`1.0.0-beta.16` adds `std.string` source facades and examples for `1.0.0-beta.16` adds `std.string` source facades and examples for
`byte_at_result`, `slice_result`, `starts_with`, and `ends_with`. These helpers `byte_at_result`, `slice_result`, `starts_with`, and `ends_with`. These helpers
@ -56,16 +58,23 @@ APIs, additional runtime names, Unicode/grapheme string semantics, timing
publication, performance claims, stable benchmark JSON schema, and package publication, performance claims, stable benchmark JSON schema, and package
registry semantics remain deferred. registry semantics remain deferred.
Next stage target: post-`1.0.0-beta.18` continuation from `1.0.0-beta.19` is a tooling/conformance slice, not a new source-language
developer-experience, feature. It adds the `glagol test --list <file|project|workspace>` command
package, benchmark metadata, collection, or string-processing planning without plus legacy `glagol --run-tests --list <file>`: parse, lower, type-check, and
claiming executable generics, an LSP/watch protocol, SARIF/daemon protocol, discover tests through the same front-end path as normal test execution, then
stable Markdown schema, registry semantics, stable benchmark JSON schema, list the discovered/selected tests without evaluating test bodies. The mode
stable `1.0.0` diagnostics freeze, stable standard-library/API compatibility preserves current single-file, project, and workspace ordering, honors
freeze, mutable vectors, language slice/view APIs, additional runtime names, `--filter <substring>`, and leaves normal `glagol test` execution behavior
Unicode/grapheme semantics, full JSON parsing, maps/sets, iterators, or unchanged.
performance claims until the exact scope is frozen from the manifest and
roadmap. The beta19 tooling scope does not claim executable generics, maps/sets,
iterators, runtime helper names, source-language syntax, JSON expansion,
parallel test execution, retries, tags/groups, coverage/event streams,
LSP/watch protocols, SARIF/daemon protocols, stable artifact-manifest or
Markdown schemas, stable benchmark JSON schema, stable `1.0.0` diagnostics
freeze, stable standard-library/API compatibility freeze, registry semantics,
semver solving, performance claims, mutable vectors, language slice/view APIs,
additional runtime names, or Unicode/grapheme semantics.
The final experimental precursor scope is `exp-125`, defined in The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its `.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its

View File

@ -9,8 +9,9 @@ diagnostic catalog and schema policy update, and `1.0.0-beta.14` benchmark
suite catalog and metadata gate, `1.0.0-beta.15` reserved generic collection suite catalog and metadata gate, `1.0.0-beta.15` reserved generic collection
boundary hardening and collection ledger, `1.0.0-beta.16` string scanning boundary hardening and collection ledger, `1.0.0-beta.16` string scanning
and token boundary foundation, `1.0.0-beta.17` JSON primitive scalar parsing and token boundary foundation, `1.0.0-beta.17` JSON primitive scalar parsing
foundation, and `1.0.0-beta.18` JSON string token foundation, `1.0.0-beta.18` JSON string token parsing foundation, and
parsing foundation. The language contract `1.0.0-beta.19` test discovery and user-project conformance tooling. The
language contract
integrates integrates
promoted language slices through `exp-125` and the historical publication promoted language slices through `exp-125` and the historical publication
baseline through `exp-123`. `1.0.0-beta` is the first real general-purpose baseline through `exp-123`. `1.0.0-beta` is the first real general-purpose
@ -217,6 +218,16 @@ Current v1 release surface and explicit experimental targets:
APIs, recursive `JsonValue`, Unicode escape decoding/normalization, embedded APIs, recursive `JsonValue`, Unicode escape decoding/normalization, embedded
NUL policy, stable ABI/layout, performance claims, and stable stdlib/API NUL policy, stable ABI/layout, performance claims, and stable stdlib/API
freeze remain out of scope freeze remain out of scope
- `1.0.0-beta.19` test discovery and user-project conformance target:
`glagol test --list <file|project|workspace>` and legacy
`glagol --run-tests --list <file>` list checked/discovered tests without
executing test bodies; list mode preserves current test ordering, honors
`--filter <substring>`, and remains beta tooling rather than a stable
schema. This target does not add source-language syntax, runtime helper
names, JSON expansion, parallel test execution, retries, tags/groups,
coverage/event streams, stable artifact-manifest or Markdown schemas,
LSP/watch behavior, SARIF/daemon protocols, package registries, semver
solving, or performance claims
- `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two - `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two
`string` values and returns an immutable runtime-owned `string`; existing `string` values and returns an immutable runtime-owned `string`; existing
string equality, length, printing, locals, parameters, returns, and calls work string equality, length, printing, locals, parameters, returns, and calls work

View File

@ -6,7 +6,7 @@ Do not edit this file by hand.
## Stability Tiers ## Stability Tiers
- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line. - `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.18`; future releases may mark new helpers this way before they graduate. - `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.19`; future releases may mark new helpers this way before they graduate.
- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog. - `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.
The catalog is a beta API discovery aid, not a stable `1.0.0` standard-library freeze. The catalog is a beta API discovery aid, not a stable `1.0.0` standard-library freeze.

View File

@ -69,6 +69,7 @@ cargo test --test reserved_generic_collection_beta15
cargo test --test standard_string_scanning_beta16 cargo test --test standard_string_scanning_beta16
cargo test --test standard_json_scalar_parsing_beta17 cargo test --test standard_json_scalar_parsing_beta17
cargo test --test standard_json_string_parsing_beta18 cargo test --test standard_json_string_parsing_beta18
cargo test --test test_discovery_beta19
# Full cargo test includes unignored integration gates such as dx_v1_7, # Full cargo test includes unignored integration gates such as dx_v1_7,
# beta_v2_0_0_beta_1, and beta_1_0_0. # beta_v2_0_0_beta_1, and beta_1_0_0.
cargo test cargo test