From 50186a23cd62fc6ce120f6114b428dc5c26b5a56 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Sat, 17 Oct 2026 09:23:04 +0000 Subject: test(multifile): cover imports, shared globals and exception propagation Allow fixtures to pass ordered source lists and require file-qualified negative diagnostics. Add native and runner cases for cross-unit calls, global mutation and shadowing, transitive imports, types, MAIN status, exception catches, runtime failures and destructor unwinding. Co-Authored-By: Claude Opus 5.5 --- tests/README.md | 8 +++++++ tests/i/multifile-functions-globals/case.json | 6 +++++ tests/i/multifile-functions-globals/expected.txt | 6 +++++ tests/i/multifile-functions-globals/lib.cps | 27 +++++++++++++++++++++ tests/i/multifile-functions-globals/notes.md | 5 ++++ tests/i/multifile-functions-globals/program.cps | 11 +++++++++ tests/i/multifile-main-exit/case.json | 7 ++++++ tests/i/multifile-main-exit/expected.txt | 1 + tests/i/multifile-main-exit/lib.cps | 9 +++++++ tests/i/multifile-main-exit/notes.md | 5 ++++ tests/i/multifile-main-exit/program.cps | 6 +++++ tests/i/multifile-runtime-catch/case.json | 6 +++++ tests/i/multifile-runtime-catch/expected.txt | 3 +++ tests/i/multifile-runtime-catch/lib.cps | 3 +++ tests/i/multifile-runtime-catch/notes.md | 5 ++++ tests/i/multifile-runtime-catch/program.cps | 7 ++++++ tests/i/multifile-transitive-types/bridge.cps | 10 ++++++++ tests/i/multifile-transitive-types/case.json | 7 ++++++ tests/i/multifile-transitive-types/expected.txt | 2 ++ .../i/multifile-transitive-types/nested/values.cps | 6 +++++ tests/i/multifile-transitive-types/notes.md | 5 ++++ tests/i/multifile-transitive-types/program.cps | 8 +++++++ tests/i/multifile-try-catch/case.json | 7 ++++++ tests/i/multifile-try-catch/expected.txt | 7 ++++++ tests/i/multifile-try-catch/leaf.cps | 15 ++++++++++++ tests/i/multifile-try-catch/middle.cps | 13 ++++++++++ tests/i/multifile-try-catch/notes.md | 5 ++++ tests/i/multifile-try-catch/program.cps | 13 ++++++++++ tests/i/multifile-uncaught/case.json | 7 ++++++ tests/i/multifile-uncaught/expected.txt | 1 + tests/i/multifile-uncaught/lib.cps | 3 +++ tests/i/multifile-uncaught/notes.md | 5 ++++ tests/i/multifile-uncaught/program.cps | 5 ++++ tests/iii/multifile-body-location/case.json | 13 ++++++++++ tests/iii/multifile-body-location/lib.cps | 3 +++ tests/iii/multifile-body-location/program.cps | 2 ++ tests/iii/multifile-duplicate-constant/case.json | 13 ++++++++++ tests/iii/multifile-duplicate-constant/lib.cps | 1 + tests/iii/multifile-duplicate-constant/program.cps | 2 ++ tests/iii/multifile-duplicate-function/case.json | 13 ++++++++++ tests/iii/multifile-duplicate-function/lib.cps | 3 +++ tests/iii/multifile-duplicate-function/program.cps | 4 ++++ tests/iii/multifile-global-array/case.json | 13 ++++++++++ tests/iii/multifile-global-array/lib.cps | 3 +++ tests/iii/multifile-global-array/program.cps | 1 + tests/iii/multifile-global-call/case.json | 13 ++++++++++ tests/iii/multifile-global-call/lib.cps | 7 ++++++ tests/iii/multifile-global-call/program.cps | 1 + tests/iii/multifile-global-expression/case.json | 13 ++++++++++ tests/iii/multifile-global-expression/lib.cps | 4 ++++ tests/iii/multifile-global-expression/program.cps | 1 + tests/iii/multifile-hidden-symbols/case.json | 28 ++++++++++++++++++++++ tests/iii/multifile-hidden-symbols/lib.cps | 8 +++++++ tests/iii/multifile-hidden-symbols/program.cps | 4 ++++ tests/iii/multifile-invalid-main/case.json | 13 ++++++++++ tests/iii/multifile-invalid-main/lib.cps | 1 + tests/iii/multifile-invalid-main/program.cps | 3 +++ tests/iii/multifile-lexer-location/case.json | 13 ++++++++++ tests/iii/multifile-lexer-location/lib.cps | 1 + tests/iii/multifile-lexer-location/program.cps | 1 + tests/iii/multifile-main-and-code/case.json | 13 ++++++++++ tests/iii/multifile-main-and-code/lib.cps | 1 + tests/iii/multifile-main-and-code/program.cps | 3 +++ tests/iii/multifile-missing-include/case.json | 13 ++++++++++ tests/iii/multifile-missing-include/lib.cps | 1 + tests/iii/multifile-missing-include/program.cps | 2 ++ tests/iii/multifile-no-entry/case.json | 13 ++++++++++ tests/iii/multifile-no-entry/lib.cps | 2 ++ tests/iii/multifile-no-entry/program.cps | 1 + tests/iii/multifile-parser-location/case.json | 13 ++++++++++ tests/iii/multifile-parser-location/lib.cps | 3 +++ tests/iii/multifile-parser-location/program.cps | 1 + tests/iii/multifile-two-entries/case.json | 13 ++++++++++ tests/iii/multifile-two-entries/lib.cps | 1 + tests/iii/multifile-two-entries/program.cps | 1 + tests/run.pl | 23 +++++++++++++----- tests/test_runner.pl | 17 +++++++++++++ 77 files changed, 531 insertions(+), 6 deletions(-) create mode 100644 tests/i/multifile-functions-globals/case.json create mode 100644 tests/i/multifile-functions-globals/expected.txt create mode 100644 tests/i/multifile-functions-globals/lib.cps create mode 100644 tests/i/multifile-functions-globals/notes.md create mode 100644 tests/i/multifile-functions-globals/program.cps create mode 100644 tests/i/multifile-main-exit/case.json create mode 100644 tests/i/multifile-main-exit/expected.txt create mode 100644 tests/i/multifile-main-exit/lib.cps create mode 100644 tests/i/multifile-main-exit/notes.md create mode 100644 tests/i/multifile-main-exit/program.cps create mode 100644 tests/i/multifile-runtime-catch/case.json create mode 100644 tests/i/multifile-runtime-catch/expected.txt create mode 100644 tests/i/multifile-runtime-catch/lib.cps create mode 100644 tests/i/multifile-runtime-catch/notes.md create mode 100644 tests/i/multifile-runtime-catch/program.cps create mode 100644 tests/i/multifile-transitive-types/bridge.cps create mode 100644 tests/i/multifile-transitive-types/case.json create mode 100644 tests/i/multifile-transitive-types/expected.txt create mode 100644 tests/i/multifile-transitive-types/nested/values.cps create mode 100644 tests/i/multifile-transitive-types/notes.md create mode 100644 tests/i/multifile-transitive-types/program.cps create mode 100644 tests/i/multifile-try-catch/case.json create mode 100644 tests/i/multifile-try-catch/expected.txt create mode 100644 tests/i/multifile-try-catch/leaf.cps create mode 100644 tests/i/multifile-try-catch/middle.cps create mode 100644 tests/i/multifile-try-catch/notes.md create mode 100644 tests/i/multifile-try-catch/program.cps create mode 100644 tests/i/multifile-uncaught/case.json create mode 100644 tests/i/multifile-uncaught/expected.txt create mode 100644 tests/i/multifile-uncaught/lib.cps create mode 100644 tests/i/multifile-uncaught/notes.md create mode 100644 tests/i/multifile-uncaught/program.cps create mode 100644 tests/iii/multifile-body-location/case.json create mode 100644 tests/iii/multifile-body-location/lib.cps create mode 100644 tests/iii/multifile-body-location/program.cps create mode 100644 tests/iii/multifile-duplicate-constant/case.json create mode 100644 tests/iii/multifile-duplicate-constant/lib.cps create mode 100644 tests/iii/multifile-duplicate-constant/program.cps create mode 100644 tests/iii/multifile-duplicate-function/case.json create mode 100644 tests/iii/multifile-duplicate-function/lib.cps create mode 100644 tests/iii/multifile-duplicate-function/program.cps create mode 100644 tests/iii/multifile-global-array/case.json create mode 100644 tests/iii/multifile-global-array/lib.cps create mode 100644 tests/iii/multifile-global-array/program.cps create mode 100644 tests/iii/multifile-global-call/case.json create mode 100644 tests/iii/multifile-global-call/lib.cps create mode 100644 tests/iii/multifile-global-call/program.cps create mode 100644 tests/iii/multifile-global-expression/case.json create mode 100644 tests/iii/multifile-global-expression/lib.cps create mode 100644 tests/iii/multifile-global-expression/program.cps create mode 100644 tests/iii/multifile-hidden-symbols/case.json create mode 100644 tests/iii/multifile-hidden-symbols/lib.cps create mode 100644 tests/iii/multifile-hidden-symbols/program.cps create mode 100644 tests/iii/multifile-invalid-main/case.json create mode 100644 tests/iii/multifile-invalid-main/lib.cps create mode 100644 tests/iii/multifile-invalid-main/program.cps create mode 100644 tests/iii/multifile-lexer-location/case.json create mode 100644 tests/iii/multifile-lexer-location/lib.cps create mode 100644 tests/iii/multifile-lexer-location/program.cps create mode 100644 tests/iii/multifile-main-and-code/case.json create mode 100644 tests/iii/multifile-main-and-code/lib.cps create mode 100644 tests/iii/multifile-main-and-code/program.cps create mode 100644 tests/iii/multifile-missing-include/case.json create mode 100644 tests/iii/multifile-missing-include/lib.cps create mode 100644 tests/iii/multifile-missing-include/program.cps create mode 100644 tests/iii/multifile-no-entry/case.json create mode 100644 tests/iii/multifile-no-entry/lib.cps create mode 100644 tests/iii/multifile-no-entry/program.cps create mode 100644 tests/iii/multifile-parser-location/case.json create mode 100644 tests/iii/multifile-parser-location/lib.cps create mode 100644 tests/iii/multifile-parser-location/program.cps create mode 100644 tests/iii/multifile-two-entries/case.json create mode 100644 tests/iii/multifile-two-entries/lib.cps create mode 100644 tests/iii/multifile-two-entries/program.cps (limited to 'tests') diff --git a/tests/README.md b/tests/README.md index b617161..b186aec 100644 --- a/tests/README.md +++ b/tests/README.md @@ -29,3 +29,11 @@ Use the actual SHA from a failed report in place of `FULL_COMMIT_SHA`. Clear `CP In Actions, each compiler platform runs all three suites and summarizes results before its existing package smoke checks and upload. Failures retain inputs and logs as an Actions artifact for 14 days. Each platform publishes only after its own tests pass. The initial Linux failures have been fixed; any failing compiler test still blocks publication for that platform. The independent plugin job can publish separately. For adding cases and runner interfaces, see [AGENTS/TESTING.md](../AGENTS/TESTING.md). + +Multi-file fixtures use a `sources` array in `case.json`, listing all compiler inputs relative to the case directory in command-line order. The default remains `["program.cps"]`. Included libraries live beside the fixture source or in subdirectories; INCLUDE paths stay relative to their including source. Sources are passed separately to both native compilation and the configured runner. Negative cases may add `file` to an error expectation; the reason, line and exact resolved source path must occur on the same diagnostic. + +Run the cross-file cases (functions, shared globals, imported types, include cycles, MAIN, exception unwinding and rejection diagnostics) with: + +```sh +ctest --test-dir build -R multifile --output-on-failure +``` diff --git a/tests/i/multifile-functions-globals/case.json b/tests/i/multifile-functions-globals/case.json new file mode 100644 index 0000000..032ec6a --- /dev/null +++ b/tests/i/multifile-functions-globals/case.json @@ -0,0 +1,6 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ] +} diff --git a/tests/i/multifile-functions-globals/expected.txt b/tests/i/multifile-functions-globals/expected.txt new file mode 100644 index 0000000..86906d2 --- /dev/null +++ b/tests/i/multifile-functions-globals/expected.txt @@ -0,0 +1,6 @@ +3 +3:shared:TRUE:Q:TRUE +5 +40 +5 +99:7 diff --git a/tests/i/multifile-functions-globals/lib.cps b/tests/i/multifile-functions-globals/lib.cps new file mode 100644 index 0000000..0d09ad5 --- /dev/null +++ b/tests/i/multifile-functions-globals/lib.cps @@ -0,0 +1,27 @@ +CONSTANT Offset = 2 +FUNCTION Add(a : INTEGER, b : INTEGER) RETURNS INTEGER + RETURN a + b +ENDFUNCTION +PROCEDURE Increment() + Counter <- Counter + Offset +ENDPROCEDURE +PROCEDURE Shadow() + DECLARE Counter : INTEGER + Counter <- 40 + OUTPUT Counter +ENDPROCEDURE +FUNCTION ReadCounter RETURNS INTEGER + RETURN Counter +ENDFUNCTION +GLOBAL + DECLARE Counter : INTEGER + Counter <- 3 + DECLARE Label : STRING + Label <- "shared" + DECLARE Enabled : BOOLEAN + Enabled <- TRUE + DECLARE Initial : CHAR + Initial <- 'Q' + DECLARE Ratio : REAL + Ratio <- -1.5 +ENDGLOBAL diff --git a/tests/i/multifile-functions-globals/notes.md b/tests/i/multifile-functions-globals/notes.md new file mode 100644 index 0000000..632c848 --- /dev/null +++ b/tests/i/multifile-functions-globals/notes.md @@ -0,0 +1,5 @@ +# Multi-file regression + +Human review: pending. Expected output was independently traced by AI. + +These are compiler extensions, not Cambridge guide requirements. Add returns 3. Globals start at 3/shared/TRUE/Q/-1.5. Increment adds the library constant 2. Shadow prints its local 40 without changing the global 5. The entry local 99 survives another library increment; ReadCounter returns the shared 7. Functions precede GLOBAL declarations to check pre-pass visibility. diff --git a/tests/i/multifile-functions-globals/program.cps b/tests/i/multifile-functions-globals/program.cps new file mode 100644 index 0000000..e5e6a6c --- /dev/null +++ b/tests/i/multifile-functions-globals/program.cps @@ -0,0 +1,11 @@ +INCLUDE "lib.cps" +OUTPUT Add(1, 2) +OUTPUT Counter, ":", Label, ":", Enabled, ":", Initial, ":", Ratio = -1.5 +CALL Increment() +OUTPUT Counter +CALL Shadow() +OUTPUT Counter +DECLARE Counter : INTEGER +Counter <- 99 +CALL Increment() +OUTPUT Counter, ":", ReadCounter() diff --git a/tests/i/multifile-main-exit/case.json b/tests/i/multifile-main-exit/case.json new file mode 100644 index 0000000..3cfd155 --- /dev/null +++ b/tests/i/multifile-main-exit/case.json @@ -0,0 +1,7 @@ +{ + "sources": [ + "lib.cps", + "program.cps" + ], + "exit_code": 3 +} diff --git a/tests/i/multifile-main-exit/expected.txt b/tests/i/multifile-main-exit/expected.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/tests/i/multifile-main-exit/expected.txt @@ -0,0 +1 @@ +1 diff --git a/tests/i/multifile-main-exit/lib.cps b/tests/i/multifile-main-exit/lib.cps new file mode 100644 index 0000000..2bb4941 --- /dev/null +++ b/tests/i/multifile-main-exit/lib.cps @@ -0,0 +1,9 @@ +GLOBAL + DECLARE Counter : INTEGER +ENDGLOBAL +PROCEDURE Increment() + Counter <- Counter + 1 +ENDPROCEDURE +FUNCTION Status RETURNS INTEGER + RETURN 4294967298 + Counter +ENDFUNCTION diff --git a/tests/i/multifile-main-exit/notes.md b/tests/i/multifile-main-exit/notes.md new file mode 100644 index 0000000..486abab --- /dev/null +++ b/tests/i/multifile-main-exit/notes.md @@ -0,0 +1,5 @@ +# Multi-file regression + +Human review: pending. Expected output was independently traced by AI. + +These are compiler extensions, not Cambridge guide requirements. The entry is the second input and its name has mixed case. Increment changes the shared counter from 0 to 1. Status returns 4294967299, whose low 32 bits are 3; both native execution and the configured runner must return status 3. diff --git a/tests/i/multifile-main-exit/program.cps b/tests/i/multifile-main-exit/program.cps new file mode 100644 index 0000000..928d4df --- /dev/null +++ b/tests/i/multifile-main-exit/program.cps @@ -0,0 +1,6 @@ +INCLUDE "lib.cps" +FUNCTION mAiN RETURNS INTEGER + CALL Increment() + OUTPUT Counter + RETURN Status() +ENDFUNCTION diff --git a/tests/i/multifile-runtime-catch/case.json b/tests/i/multifile-runtime-catch/case.json new file mode 100644 index 0000000..032ec6a --- /dev/null +++ b/tests/i/multifile-runtime-catch/case.json @@ -0,0 +1,6 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ] +} diff --git a/tests/i/multifile-runtime-catch/expected.txt b/tests/i/multifile-runtime-catch/expected.txt new file mode 100644 index 0000000..731df7f --- /dev/null +++ b/tests/i/multifile-runtime-catch/expected.txt @@ -0,0 +1,3 @@ +[Fatal] line 2: Division by zero +runtime caught +5 diff --git a/tests/i/multifile-runtime-catch/lib.cps b/tests/i/multifile-runtime-catch/lib.cps new file mode 100644 index 0000000..694eaf6 --- /dev/null +++ b/tests/i/multifile-runtime-catch/lib.cps @@ -0,0 +1,3 @@ +FUNCTION Quotient(Divisor : INTEGER) RETURNS INTEGER + RETURN 10 DIV Divisor +ENDFUNCTION diff --git a/tests/i/multifile-runtime-catch/notes.md b/tests/i/multifile-runtime-catch/notes.md new file mode 100644 index 0000000..d3a118f --- /dev/null +++ b/tests/i/multifile-runtime-catch/notes.md @@ -0,0 +1,5 @@ +# Cross-file runtime-check regression + +Human review: pending. Expected output was independently traced by AI. + +TRY/CATCH and multi-file builds are compiler extensions. Quotient(0) fails at the division on library line 2, printing the exact runtime diagnostic before throwing. The entry catches it and continues. Quotient(2) then returns 5, proving the handler leaves execution usable. diff --git a/tests/i/multifile-runtime-catch/program.cps b/tests/i/multifile-runtime-catch/program.cps new file mode 100644 index 0000000..3d11099 --- /dev/null +++ b/tests/i/multifile-runtime-catch/program.cps @@ -0,0 +1,7 @@ +INCLUDE "lib.cps" +TRY + OUTPUT Quotient(0) +CATCH + OUTPUT "runtime caught" +ENDTRY +OUTPUT Quotient(2) diff --git a/tests/i/multifile-transitive-types/bridge.cps b/tests/i/multifile-transitive-types/bridge.cps new file mode 100644 index 0000000..cbdcae8 --- /dev/null +++ b/tests/i/multifile-transitive-types/bridge.cps @@ -0,0 +1,10 @@ +INCLUDE "nested/values.cps" +CLASS Container + Value : INTEGER + FUNCTION Read RETURNS INTEGER + RETURN Value + Base + ENDFUNCTION +ENDCLASS +TYPE Pair + DECLARE Value : INTEGER +ENDTYPE diff --git a/tests/i/multifile-transitive-types/case.json b/tests/i/multifile-transitive-types/case.json new file mode 100644 index 0000000..445f846 --- /dev/null +++ b/tests/i/multifile-transitive-types/case.json @@ -0,0 +1,7 @@ +{ + "sources": [ + "program.cps", + "bridge.cps", + "nested/values.cps" + ] +} diff --git a/tests/i/multifile-transitive-types/expected.txt b/tests/i/multifile-transitive-types/expected.txt new file mode 100644 index 0000000..a14bbdb --- /dev/null +++ b/tests/i/multifile-transitive-types/expected.txt @@ -0,0 +1,2 @@ +11:6:0 +11 diff --git a/tests/i/multifile-transitive-types/nested/values.cps b/tests/i/multifile-transitive-types/nested/values.cps new file mode 100644 index 0000000..713ffce --- /dev/null +++ b/tests/i/multifile-transitive-types/nested/values.cps @@ -0,0 +1,6 @@ +INCLUDE "../bridge.cps" +CONSTANT Base = 3 +TYPE Choice = (First, Second) +FUNCTION Twice(n : INTEGER) RETURNS INTEGER + RETURN n * 2 +ENDFUNCTION diff --git a/tests/i/multifile-transitive-types/notes.md b/tests/i/multifile-transitive-types/notes.md new file mode 100644 index 0000000..160c801 --- /dev/null +++ b/tests/i/multifile-transitive-types/notes.md @@ -0,0 +1,5 @@ +# Multi-file regression + +Human review: pending. Expected output was independently traced by AI. + +These are compiler extensions, not Cambridge guide requirements. The bridge imports nested values and the nested file imports the bridge, forming a cycle; repeated imports must not duplicate definitions. The entry sees Base=3, Twice and the enum through the bridge. Container.Read computes 8+3=11, Twice(3)=6, First has ordinal 0, and Pair stores 11. This covers imported methods, records, enums and constants, plus relative nested paths. diff --git a/tests/i/multifile-transitive-types/program.cps b/tests/i/multifile-transitive-types/program.cps new file mode 100644 index 0000000..9f4e6bd --- /dev/null +++ b/tests/i/multifile-transitive-types/program.cps @@ -0,0 +1,8 @@ +INCLUDE "bridge.cps" +INCLUDE "bridge.cps" +DECLARE Item : Container +Item.Value <- 8 +OUTPUT Item.Read(), ":", Twice(Base), ":", First +DECLARE Node : Pair +Node.Value <- Item.Read() +OUTPUT Node.Value diff --git a/tests/i/multifile-try-catch/case.json b/tests/i/multifile-try-catch/case.json new file mode 100644 index 0000000..edcaa8c --- /dev/null +++ b/tests/i/multifile-try-catch/case.json @@ -0,0 +1,7 @@ +{ + "sources": [ + "program.cps", + "middle.cps", + "leaf.cps" + ] +} diff --git a/tests/i/multifile-try-catch/expected.txt b/tests/i/multifile-try-catch/expected.txt new file mode 100644 index 0000000..3560fb1 --- /dev/null +++ b/tests/i/multifile-try-catch/expected.txt @@ -0,0 +1,7 @@ +destroy:2 +destroy:1 +caught:2 +continued +destroy:2 +library caught:3 +returned diff --git a/tests/i/multifile-try-catch/leaf.cps b/tests/i/multifile-try-catch/leaf.cps new file mode 100644 index 0000000..44b991a --- /dev/null +++ b/tests/i/multifile-try-catch/leaf.cps @@ -0,0 +1,15 @@ +GLOBAL + DECLARE Destroyed : INTEGER +ENDGLOBAL +CLASS Guard + Id : INTEGER + PROCEDURE DELETE() + OUTPUT "destroy:", Id + Destroyed <- Destroyed + 1 + ENDPROCEDURE +ENDCLASS +PROCEDURE Raise() + DECLARE GuardTwo : Guard + GuardTwo.Id <- 2 + THROW +ENDPROCEDURE diff --git a/tests/i/multifile-try-catch/middle.cps b/tests/i/multifile-try-catch/middle.cps new file mode 100644 index 0000000..11b8a85 --- /dev/null +++ b/tests/i/multifile-try-catch/middle.cps @@ -0,0 +1,13 @@ +INCLUDE "leaf.cps" +PROCEDURE Outer() + DECLARE GuardOne : Guard + GuardOne.Id <- 1 + CALL Raise() +ENDPROCEDURE +PROCEDURE CatchHere() + TRY + CALL Raise() + CATCH + OUTPUT "library caught:", Destroyed + ENDTRY +ENDPROCEDURE diff --git a/tests/i/multifile-try-catch/notes.md b/tests/i/multifile-try-catch/notes.md new file mode 100644 index 0000000..cf7f961 --- /dev/null +++ b/tests/i/multifile-try-catch/notes.md @@ -0,0 +1,5 @@ +# Multi-file regression + +Human review: pending. Expected output was independently traced by AI. + +These are compiler extensions, not Cambridge guide requirements. Raise destroys guard 2, then unwinding Outer destroys guard 1 before the entry catch reads Destroyed=2. Execution continues. CatchHere catches another Raise inside the middle file; only guard 2 is destroyed, yielding Destroyed=3, and the entry outer catch must not run. Every throw crosses a unit boundary and cleanup runs before catch output. diff --git a/tests/i/multifile-try-catch/program.cps b/tests/i/multifile-try-catch/program.cps new file mode 100644 index 0000000..ec3edca --- /dev/null +++ b/tests/i/multifile-try-catch/program.cps @@ -0,0 +1,13 @@ +INCLUDE "middle.cps" +TRY + CALL Outer() +CATCH + OUTPUT "caught:", Destroyed +ENDTRY +OUTPUT "continued" +TRY + CALL CatchHere() + OUTPUT "returned" +CATCH + OUTPUT "wrong handler" +ENDTRY diff --git a/tests/i/multifile-uncaught/case.json b/tests/i/multifile-uncaught/case.json new file mode 100644 index 0000000..bd1e132 --- /dev/null +++ b/tests/i/multifile-uncaught/case.json @@ -0,0 +1,7 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "exit_code": 1 +} diff --git a/tests/i/multifile-uncaught/expected.txt b/tests/i/multifile-uncaught/expected.txt new file mode 100644 index 0000000..e9431ec --- /dev/null +++ b/tests/i/multifile-uncaught/expected.txt @@ -0,0 +1 @@ +Unhandled exception. Program terminated. diff --git a/tests/i/multifile-uncaught/lib.cps b/tests/i/multifile-uncaught/lib.cps new file mode 100644 index 0000000..c482a6d --- /dev/null +++ b/tests/i/multifile-uncaught/lib.cps @@ -0,0 +1,3 @@ +PROCEDURE Fail() + THROW +ENDPROCEDURE diff --git a/tests/i/multifile-uncaught/notes.md b/tests/i/multifile-uncaught/notes.md new file mode 100644 index 0000000..f89adf8 --- /dev/null +++ b/tests/i/multifile-uncaught/notes.md @@ -0,0 +1,5 @@ +# Multi-file regression + +Human review: pending. Expected output was independently traced by AI. + +These are compiler extensions, not Cambridge guide requirements. Fail throws in the library, unwinds the explicit MAIN function and reaches the generated wrapper handler. Exactly the generic message is printed and the process returns 1. diff --git a/tests/i/multifile-uncaught/program.cps b/tests/i/multifile-uncaught/program.cps new file mode 100644 index 0000000..d61ea3b --- /dev/null +++ b/tests/i/multifile-uncaught/program.cps @@ -0,0 +1,5 @@ +INCLUDE "lib.cps" +FUNCTION MAIN RETURNS INTEGER + CALL Fail() + RETURN 0 +ENDFUNCTION diff --git a/tests/iii/multifile-body-location/case.json b/tests/iii/multifile-body-location/case.json new file mode 100644 index 0000000..510015f --- /dev/null +++ b/tests/iii/multifile-body-location/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Unknown variable name Missing", + "line": 2, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-body-location/lib.cps b/tests/iii/multifile-body-location/lib.cps new file mode 100644 index 0000000..4afdc98 --- /dev/null +++ b/tests/iii/multifile-body-location/lib.cps @@ -0,0 +1,3 @@ +FUNCTION Broken RETURNS INTEGER +RETURN Missing +ENDFUNCTION diff --git a/tests/iii/multifile-body-location/program.cps b/tests/iii/multifile-body-location/program.cps new file mode 100644 index 0000000..cbca736 --- /dev/null +++ b/tests/iii/multifile-body-location/program.cps @@ -0,0 +1,2 @@ +INCLUDE "lib.cps" +OUTPUT Broken() diff --git a/tests/iii/multifile-duplicate-constant/case.json b/tests/iii/multifile-duplicate-constant/case.json new file mode 100644 index 0000000..2ef6783 --- /dev/null +++ b/tests/iii/multifile-duplicate-constant/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Duplicate definition answer", + "line": 1, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-duplicate-constant/lib.cps b/tests/iii/multifile-duplicate-constant/lib.cps new file mode 100644 index 0000000..fbce689 --- /dev/null +++ b/tests/iii/multifile-duplicate-constant/lib.cps @@ -0,0 +1 @@ +CONSTANT answer = 2 diff --git a/tests/iii/multifile-duplicate-constant/program.cps b/tests/iii/multifile-duplicate-constant/program.cps new file mode 100644 index 0000000..4f49665 --- /dev/null +++ b/tests/iii/multifile-duplicate-constant/program.cps @@ -0,0 +1,2 @@ +CONSTANT Answer = 1 +OUTPUT Answer diff --git a/tests/iii/multifile-duplicate-function/case.json b/tests/iii/multifile-duplicate-function/case.json new file mode 100644 index 0000000..0d0affd --- /dev/null +++ b/tests/iii/multifile-duplicate-function/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Duplicate definition value", + "line": 1, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-duplicate-function/lib.cps b/tests/iii/multifile-duplicate-function/lib.cps new file mode 100644 index 0000000..7fcd8f2 --- /dev/null +++ b/tests/iii/multifile-duplicate-function/lib.cps @@ -0,0 +1,3 @@ +FUNCTION value RETURNS INTEGER +RETURN 2 +ENDFUNCTION diff --git a/tests/iii/multifile-duplicate-function/program.cps b/tests/iii/multifile-duplicate-function/program.cps new file mode 100644 index 0000000..8488a19 --- /dev/null +++ b/tests/iii/multifile-duplicate-function/program.cps @@ -0,0 +1,4 @@ +OUTPUT 1 +FUNCTION Value RETURNS INTEGER +RETURN 1 +ENDFUNCTION diff --git a/tests/iii/multifile-global-array/case.json b/tests/iii/multifile-global-array/case.json new file mode 100644 index 0000000..e4ec559 --- /dev/null +++ b/tests/iii/multifile-global-array/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "GLOBAL blocks allow only primitive DECLARE", + "line": 2, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-global-array/lib.cps b/tests/iii/multifile-global-array/lib.cps new file mode 100644 index 0000000..ade1e72 --- /dev/null +++ b/tests/iii/multifile-global-array/lib.cps @@ -0,0 +1,3 @@ +GLOBAL +DECLARE Items : ARRAY[1:2] OF INTEGER +ENDGLOBAL diff --git a/tests/iii/multifile-global-array/program.cps b/tests/iii/multifile-global-array/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-global-array/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-global-call/case.json b/tests/iii/multifile-global-call/case.json new file mode 100644 index 0000000..d11e454 --- /dev/null +++ b/tests/iii/multifile-global-call/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "GLOBAL initializer must be a literal", + "line": 3, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-global-call/lib.cps b/tests/iii/multifile-global-call/lib.cps new file mode 100644 index 0000000..dae2078 --- /dev/null +++ b/tests/iii/multifile-global-call/lib.cps @@ -0,0 +1,7 @@ +GLOBAL +DECLARE Value : INTEGER +Value <- Produce() +ENDGLOBAL +FUNCTION Produce RETURNS INTEGER +RETURN 3 +ENDFUNCTION diff --git a/tests/iii/multifile-global-call/program.cps b/tests/iii/multifile-global-call/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-global-call/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-global-expression/case.json b/tests/iii/multifile-global-expression/case.json new file mode 100644 index 0000000..d11e454 --- /dev/null +++ b/tests/iii/multifile-global-expression/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "GLOBAL initializer must be a literal", + "line": 3, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-global-expression/lib.cps b/tests/iii/multifile-global-expression/lib.cps new file mode 100644 index 0000000..dfb6060 --- /dev/null +++ b/tests/iii/multifile-global-expression/lib.cps @@ -0,0 +1,4 @@ +GLOBAL +DECLARE Value : INTEGER +Value <- 1 + 2 +ENDGLOBAL diff --git a/tests/iii/multifile-global-expression/program.cps b/tests/iii/multifile-global-expression/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-global-expression/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-hidden-symbols/case.json b/tests/iii/multifile-hidden-symbols/case.json new file mode 100644 index 0000000..2495840 --- /dev/null +++ b/tests/iii/multifile-hidden-symbols/case.json @@ -0,0 +1,28 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Call to undefined function HiddenFunction", + "line": 1, + "file": "program.cps" + }, + { + "reason": "Unknown variable name HiddenVariable", + "line": 2, + "file": "program.cps" + }, + { + "reason": "Unknown variable name HiddenConstant", + "line": 3, + "file": "program.cps" + }, + { + "reason": "Unknown type HiddenType", + "line": 4, + "file": "program.cps" + } + ] +} diff --git a/tests/iii/multifile-hidden-symbols/lib.cps b/tests/iii/multifile-hidden-symbols/lib.cps new file mode 100644 index 0000000..aeaa0a3 --- /dev/null +++ b/tests/iii/multifile-hidden-symbols/lib.cps @@ -0,0 +1,8 @@ +FUNCTION HiddenFunction RETURNS INTEGER +RETURN 1 +ENDFUNCTION +GLOBAL +DECLARE HiddenVariable : INTEGER +ENDGLOBAL +CONSTANT HiddenConstant = 1 +TYPE HiddenType = (HiddenEnum) diff --git a/tests/iii/multifile-hidden-symbols/program.cps b/tests/iii/multifile-hidden-symbols/program.cps new file mode 100644 index 0000000..6f926cb --- /dev/null +++ b/tests/iii/multifile-hidden-symbols/program.cps @@ -0,0 +1,4 @@ +OUTPUT HiddenFunction() +OUTPUT HiddenVariable +OUTPUT HiddenConstant +DECLARE Item : HiddenType diff --git a/tests/iii/multifile-invalid-main/case.json b/tests/iii/multifile-invalid-main/case.json new file mode 100644 index 0000000..ab929a9 --- /dev/null +++ b/tests/iii/multifile-invalid-main/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "MAIN must be a FUNCTION with no parameters returning INTEGER", + "line": 1, + "file": "program.cps" + } + ] +} diff --git a/tests/iii/multifile-invalid-main/lib.cps b/tests/iii/multifile-invalid-main/lib.cps new file mode 100644 index 0000000..af7b016 --- /dev/null +++ b/tests/iii/multifile-invalid-main/lib.cps @@ -0,0 +1 @@ +CONSTANT Value = 1 diff --git a/tests/iii/multifile-invalid-main/program.cps b/tests/iii/multifile-invalid-main/program.cps new file mode 100644 index 0000000..cd8bb37 --- /dev/null +++ b/tests/iii/multifile-invalid-main/program.cps @@ -0,0 +1,3 @@ +FUNCTION MAIN(n : INTEGER) RETURNS INTEGER +RETURN n +ENDFUNCTION diff --git a/tests/iii/multifile-lexer-location/case.json b/tests/iii/multifile-lexer-location/case.json new file mode 100644 index 0000000..5190ab3 --- /dev/null +++ b/tests/iii/multifile-lexer-location/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Unterminated string literal", + "line": 1, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-lexer-location/lib.cps b/tests/iii/multifile-lexer-location/lib.cps new file mode 100644 index 0000000..a0619d2 --- /dev/null +++ b/tests/iii/multifile-lexer-location/lib.cps @@ -0,0 +1 @@ +CONSTANT Broken = "unterminated diff --git a/tests/iii/multifile-lexer-location/program.cps b/tests/iii/multifile-lexer-location/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-lexer-location/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-main-and-code/case.json b/tests/iii/multifile-main-and-code/case.json new file mode 100644 index 0000000..9e770f6 --- /dev/null +++ b/tests/iii/multifile-main-and-code/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "cannot coexist with top-level code", + "line": 1, + "file": "program.cps" + } + ] +} diff --git a/tests/iii/multifile-main-and-code/lib.cps b/tests/iii/multifile-main-and-code/lib.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-main-and-code/lib.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-main-and-code/program.cps b/tests/iii/multifile-main-and-code/program.cps new file mode 100644 index 0000000..90ddf90 --- /dev/null +++ b/tests/iii/multifile-main-and-code/program.cps @@ -0,0 +1,3 @@ +FUNCTION MAIN RETURNS INTEGER +RETURN 0 +ENDFUNCTION diff --git a/tests/iii/multifile-missing-include/case.json b/tests/iii/multifile-missing-include/case.json new file mode 100644 index 0000000..f5b32c8 --- /dev/null +++ b/tests/iii/multifile-missing-include/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "cannot INCLUDE 'missing.cps': not given on the command line", + "line": 1, + "file": "program.cps" + } + ] +} diff --git a/tests/iii/multifile-missing-include/lib.cps b/tests/iii/multifile-missing-include/lib.cps new file mode 100644 index 0000000..af7b016 --- /dev/null +++ b/tests/iii/multifile-missing-include/lib.cps @@ -0,0 +1 @@ +CONSTANT Value = 1 diff --git a/tests/iii/multifile-missing-include/program.cps b/tests/iii/multifile-missing-include/program.cps new file mode 100644 index 0000000..772f3f2 --- /dev/null +++ b/tests/iii/multifile-missing-include/program.cps @@ -0,0 +1,2 @@ +INCLUDE "missing.cps" +OUTPUT 1 diff --git a/tests/iii/multifile-no-entry/case.json b/tests/iii/multifile-no-entry/case.json new file mode 100644 index 0000000..f765fda --- /dev/null +++ b/tests/iii/multifile-no-entry/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "no main program", + "line": 1, + "file": "program.cps" + } + ] +} diff --git a/tests/iii/multifile-no-entry/lib.cps b/tests/iii/multifile-no-entry/lib.cps new file mode 100644 index 0000000..e167bd1 --- /dev/null +++ b/tests/iii/multifile-no-entry/lib.cps @@ -0,0 +1,2 @@ +PROCEDURE Work() +ENDPROCEDURE diff --git a/tests/iii/multifile-no-entry/program.cps b/tests/iii/multifile-no-entry/program.cps new file mode 100644 index 0000000..af7b016 --- /dev/null +++ b/tests/iii/multifile-no-entry/program.cps @@ -0,0 +1 @@ +CONSTANT Value = 1 diff --git a/tests/iii/multifile-parser-location/case.json b/tests/iii/multifile-parser-location/case.json new file mode 100644 index 0000000..698d9d0 --- /dev/null +++ b/tests/iii/multifile-parser-location/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "INCLUDE is only allowed at top level", + "line": 2, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-parser-location/lib.cps b/tests/iii/multifile-parser-location/lib.cps new file mode 100644 index 0000000..81ef7e3 --- /dev/null +++ b/tests/iii/multifile-parser-location/lib.cps @@ -0,0 +1,3 @@ +PROCEDURE Broken() +INCLUDE "program.cps" +ENDPROCEDURE diff --git a/tests/iii/multifile-parser-location/program.cps b/tests/iii/multifile-parser-location/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-parser-location/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/iii/multifile-two-entries/case.json b/tests/iii/multifile-two-entries/case.json new file mode 100644 index 0000000..8895fad --- /dev/null +++ b/tests/iii/multifile-two-entries/case.json @@ -0,0 +1,13 @@ +{ + "sources": [ + "program.cps", + "lib.cps" + ], + "errors": [ + { + "reason": "Top-level code in both", + "line": 1, + "file": "lib.cps" + } + ] +} diff --git a/tests/iii/multifile-two-entries/lib.cps b/tests/iii/multifile-two-entries/lib.cps new file mode 100644 index 0000000..b196807 --- /dev/null +++ b/tests/iii/multifile-two-entries/lib.cps @@ -0,0 +1 @@ +DECLARE Local : INTEGER diff --git a/tests/iii/multifile-two-entries/program.cps b/tests/iii/multifile-two-entries/program.cps new file mode 100644 index 0000000..5fd1059 --- /dev/null +++ b/tests/iii/multifile-two-entries/program.cps @@ -0,0 +1 @@ +OUTPUT 1 diff --git a/tests/run.pl b/tests/run.pl index 41437de..54cd10c 100644 --- a/tests/run.pl +++ b/tests/run.pl @@ -113,13 +113,16 @@ sub check_diagnostics { my @matching = grep { /error:/ && /$reason/ } @lines; my $found = 0; for my $line (@matching) { - if ($line =~ /\bline $line_no\b/) { + my $file = $expectation->{file}; + if ($line =~ /\bline $line_no\b/ && + (!defined($file) || $line =~ /\bline $line_no in \Q$file\E(?:\s|$)/)) { $found = 1; last; } } if (!$found) { push @errors, 'missing error /' . $reason . '/ at line ' . $line_no + . (defined($expectation->{file}) ? ' in ' . $expectation->{file} : '') . '; matching diagnostics: [' . join(', ', map { show_bytes($_) } @matching) . ']'; } } @@ -339,7 +342,15 @@ sub main { run_mode => $opt_run_mode, failures => [], }; - my $source = File::Spec->catfile($case_dir, 'program.cps'); + my @sources = map { File::Spec->catfile($case_dir, split(m{/}, $_)) } + @{$config->{sources} // ['program.cps']}; + my @expectations = map { + my %expectation = %{$_}; + if (defined($expectation{file})) { + $expectation{file} = File::Spec->catfile($case_dir, split(m{/}, $expectation{file})); + } + \%expectation; + } @{$config->{errors} // []}; my $attempt = sub { my ($label, $code) = @_; @@ -367,7 +378,7 @@ sub main { write_file($target, $sentinel); } my $result = run_command($commands, - [$cpsc_abs, $source, @flags, '-o', $target], $work, '', 120); + [$cpsc_abs, @sources, @flags, '-o', $target], $work, '', 120); my $preservation_error; if ($existing && (!-f $target || read_file($target) ne $sentinel)) { $preservation_error = 'compilation failure overwrote existing output'; @@ -384,7 +395,7 @@ sub main { if (defined($captured_error)) { die "$captured_error\n"; } - check_diagnostics($captured_result, $config->{errors}); + check_diagnostics($captured_result, \@expectations); }, ); } @@ -443,12 +454,12 @@ sub main { my $target = File::Spec->catfile($work, $IS_WINDOWS ? 'program.exe' : 'program.bin'); my $compiled = run_command($commands, - [$cpsc_abs, $source, '-o', $target], $work, '', 120); + [$cpsc_abs, @sources, '-o', $target], $work, '', 120); check_result($compiled, ''); $command = [$target]; } else { - $command = [$cpsc_abs, $source, '-r']; + $command = [$cpsc_abs, @sources, '-r']; } my $exec_timeout = exists($config->{timeout}) ? $config->{timeout} + 0 : 30; my $result = run_command($commands, $command, $work, $data, $exec_timeout); diff --git a/tests/test_runner.pl b/tests/test_runner.pl index af08556..16a0e35 100644 --- a/tests/test_runner.pl +++ b/tests/test_runner.pl @@ -52,6 +52,23 @@ for my $bad ( ok(!$ok, 'diagnostic mismatch rejected: ' . show_bytes($bad)); } +my $file_expectation = [{reason => "Expected ':'", line => 3, file => '/fixtures/lib.cps'}]; +for my $sample ( + ["error: Expected ':' at line 3 in /fixtures/lib.cps", 1], + ["error: Expected ':' at line 3 in /fixtures/program.cps", 0], + ["error: Expected ':' at line 3 in /fixtures/lib.cps.extra", 0], + ["error: Expected ':' at line 30 in /fixtures/lib.cps", 0], + ["error: Expected ':' at line 3\nerror: unrelated at line 3 in /fixtures/lib.cps", 0], +) { + my ($diagnostic, $expected) = @{$sample}; + my $ok = eval { + check_diagnostics({returncode => 1, stdout => '', + stderr => $diagnostic . "\ncompilation terminated.\n"}, $file_expectation); + 1; + }; + is($ok ? 1 : 0, $expected, 'file-qualified diagnostic: ' . show_bytes($diagnostic)); +} + my $dir = tempdir(CLEANUP => 1); my $state = commands_new($dir); my $ran = eval { run_command($state, [$^X, '-e', 'sleep 5'], $dir, '', 1); 1; }; -- cgit v1.3.1-4-g156e