Objeck Language Regression Test Suite
This regression test suite validates the compiler, virtual machine, JIT and libraries after every change. It has about 780 tests; TESTS.md lists every one, by category, with what it checks. It covers:
Contents
Purpose
- Core language - classes, generics, closures and lambdas, records, strings, collections, exceptions
- JIT - AMD64 and ARM64 code generation (
jit_*,arm64_*) - GC and concurrency - minor and major collection, write barriers, threads
- Compiler and runtime errors - negative tests (
bad_*) that must be rejected, with the expected message - Libraries - JSON, XML, regex, networking and TLS, ODBC, ML and AI, OpenGL, ONNX
- Tools - debugger, DAP, LSP and MCP entry points
TESTS.md is generated by gen_manifest.py in this directory; it also lists the 14 debugger tests that run_debugger_tests.sh drives. Do not commit it in a pull request: the Regression test manifest workflow regenerates it on master after each merge that changes a test. When every pull request carried its own copy, each merge made every other open pull request conflict on it.
Running Tests
Build a deploy tree first (core/release/deploy_windows.cmd x64|arm64, core/release/deploy_posix.sh x64|arm64 or core/release/deploy_macos_arm64.sh). The runners use core/release/deploy-<arch> when it exists and core/release/deploy otherwise.
Linux/macOS
cd programs/regression chmod +x run_regression.sh ./run_regression.sh x64 # or arm64
Windows
cd programs\regression run_regression.cmd x64 REM For x64 build run_regression.cmd arm64 REM For ARM64 build
Each test is compiled with obc -opt s3 -lib cipher,collect,xml,json (plus any libraries its # EXTRA_LIBS: line names) and run with obr. Compiler logs and program output go to results/. Apart from the negative tests (see the directives below), the runners judge a test by its exit code: 0 passes, anything else fails. A test that prints a line starting with SKIP: and exits 0 counts as skipped, not passed.
Environment variables both runners honor:
TEST_TIMEOUT- seconds allowed per test run (the POSIX runner defaults to 60, the Windows runner to 300, the value the nightly runs use)OBJECK_VM_ARGS- extraobroptions for every test, e.g.--gc-threshold=64k
OBJECK_JIT_THRESHOLD=1 makes obr compile every method on its first call; CI runs the suite a second time with it.
Other runners in this directory:
run_debugger_tests.sh/run_debugger_tests.cmd[x64|arm64]-obdcommand-line tests (the POSIX one needsexpect)python3 run_dap_tests.py <bin_dir>- Debug Adapter Protocol (obd --dap) testspython3 run_vm_flag_tests.py <bin_dir>-obrflags, and interpreter/JIT equivalencepython3 run_differential.py <bin_dir>- compiles every test at-opt s0and-opt s3and requires the same output with the JIT off, on by default and forced on; its header documents its markers
Test Organization
The file-name prefix decides a test's category in TESTS.md (core_, jit_, arm64_, bad_, collect_, ml_ and so on; the mapping is CATS in gen_manifest.py, and a name that matches none is filed under Other). Directives are comment lines starting at column 0:
| Directive | Effect |
|---|---|
# EXTRA_LIBS: net,gen_collect | Links these libraries as well (comma-separated, no spaces) |
# EXPECT_COMPILE_ERROR: <message> | Passes only if compilation fails and the compiler output contains <message> |
# EXPECT_RUNTIME_ERROR | Passes only if obr exits non-zero with output |
# JIT_DISABLE | Runs the test with the JIT off; the next line must be # reason: ... (tools/cicd/check_jit_optouts.py) |
# VERIFY_SKIP, # GC_STRESS_SKIP | Skipped in the nightly heap-verifier and forced-GC-threshold runs; the next line must be # reason: ... naming the run that failed |
Data a test reads lives under data/, found relative to this directory (every runner runs tests from here). data/bert/ holds three tiny BERT-shaped ONNX models, their vocabulary and gen_tiny_bert.py, which regenerates them byte-for-byte; docs/ML_BERT_DESIGN.md describes them.
Adding New Tests
- Create a new
.obsfile in this directory - Name it descriptively:
category_feature.obs - Include a comment header explaining what it tests;
gen_manifest.pytakes the test'sTESTS.mddescription from it - Output
PASSorFAILmessages for automation - Exit with code 0 on success, non-zero on failure (
Runtime->Exit(1)). PrintingFAILis not enough: a test that returns normally exits 0 and is scored a pass, and CI'stools/cicd/check_test_exit_codes.pyrejects a test that printsFAILwith no exit path - Leave
TESTS.mdalone: it is regenerated on master after the merge (runpython gen_manifest.pylocally to preview, but do not commit the result)
Test Template
#~
# Test Name
# Brief description of what this tests
# Related commit: <commit-hash>
~#
class MyTest {
function : Main(args : String[]) ~ Nil {
"Testing my feature..."->PrintLine();
pass := true;
# Your test logic here
if(test_condition) {
# Test passed
} else {
pass := false;
" FAIL: Description of failure"->PrintLine();
};
if(pass) {
"PASS: My feature"->PrintLine();
} else {
"FAIL: My feature"->PrintLine();
Runtime->Exit(1);
};
}
}
CI/CD Integration
This test suite runs automatically in GitHub Actions on:
- Push to master
- Pull requests to master
It runs natively on all five platforms (Windows x64 and ARM64, Linux x64 and ARM64, macOS ARM64), twice on each: once as is and once with OBJECK_JIT_THRESHOLD=1. The same job runs the debugger, DAP and VM-flag tests from this directory, and CI's tools job lints the tests themselves (check_test_exit_codes.py, check_jit_optouts.py, check_diff_markers.py in tools/cicd). .github/workflows/nightly-hardening.yml (dispatched by hand for now) adds run_differential.py and runs the suite under the heap verifier and a forced GC threshold.
See .github/workflows/ci-build.yml for CI configuration.
Expected Output
From run_regression.sh; run_regression.cmd prints the same summary with slightly different per-test lines.
======================================== Objeck Regression Test Suite Platform: x64 ======================================== Running: ai_game_test... [PASS] (1s) ... Running: api_gemini_test... [SKIP] SKIP: No Gemini API key ... Running: bad_call_undefined... [PASS] (compile error as expected) ... ======================================== Results: N passed, M skipped, 0 failed ========================================
Related Documentation
Maintenance
- Run regression tests before major releases
- Add tests for new features and bug fixes
- Update test expectations if language semantics change intentionally
- Do not commit
TESTS.md; it is regenerated on master after each merge that adds or removes a test