From d19f9ddb866ac2415cbfe66966897a8efe8ddb63 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Fri, 16 Oct 2026 20:53:55 +0800 Subject: test(compiler): add seeded C++ differential tests across seven groups Generate 10000 random records per group plus boundary inputs using a portable SHA-256 stream. Compare native and runner output independently. Co-Authored-By: Claude Fable 5.1 --- tests/CMakeLists.txt | 2 +- tests/ii/Random.pm | 25 ++++++++++++++++++++++++ tests/ii/arithmetic/case.json | 1 + tests/ii/arithmetic/generate.pl | 23 ++++++++++++++++++++++ tests/ii/arithmetic/notes.md | 5 +++++ tests/ii/arithmetic/program.cps | 12 ++++++++++++ tests/ii/arithmetic/reference.cc | 13 +++++++++++++ tests/ii/arrays/case.json | 1 + tests/ii/arrays/generate.pl | 17 ++++++++++++++++ tests/ii/arrays/notes.md | 5 +++++ tests/ii/arrays/program.cps | 22 +++++++++++++++++++++ tests/ii/arrays/reference.cc | 13 +++++++++++++ tests/ii/calls/case.json | 1 + tests/ii/calls/generate.pl | 16 +++++++++++++++ tests/ii/calls/notes.md | 5 +++++ tests/ii/calls/program.cps | 40 ++++++++++++++++++++++++++++++++++++++ tests/ii/calls/reference.cc | 12 ++++++++++++ tests/ii/files/case.json | 3 +++ tests/ii/files/generate.pl | 21 ++++++++++++++++++++ tests/ii/files/notes.md | 5 +++++ tests/ii/files/program.cps | 29 +++++++++++++++++++++++++++ tests/ii/files/reference.cc | 20 +++++++++++++++++++ tests/ii/native-types/case.json | 1 + tests/ii/native-types/generate.pl | 20 +++++++++++++++++++ tests/ii/native-types/notes.md | 5 +++++ tests/ii/native-types/program.cps | 22 +++++++++++++++++++++ tests/ii/native-types/reference.cc | 18 +++++++++++++++++ tests/ii/real/case.json | 1 + tests/ii/real/generate.pl | 17 ++++++++++++++++ tests/ii/real/notes.md | 5 +++++ tests/ii/real/program.cps | 17 ++++++++++++++++ tests/ii/real/reference.cc | 15 ++++++++++++++ tests/ii/strings/case.json | 1 + tests/ii/strings/generate.pl | 23 ++++++++++++++++++++++ tests/ii/strings/notes.md | 5 +++++ tests/ii/strings/program.cps | 17 ++++++++++++++++ tests/ii/strings/reference.cc | 20 +++++++++++++++++++ tests/run.py | 5 +++-- 38 files changed, 480 insertions(+), 3 deletions(-) create mode 100644 tests/ii/Random.pm create mode 100644 tests/ii/arithmetic/case.json create mode 100644 tests/ii/arithmetic/generate.pl create mode 100644 tests/ii/arithmetic/notes.md create mode 100644 tests/ii/arithmetic/program.cps create mode 100644 tests/ii/arithmetic/reference.cc create mode 100644 tests/ii/arrays/case.json create mode 100644 tests/ii/arrays/generate.pl create mode 100644 tests/ii/arrays/notes.md create mode 100644 tests/ii/arrays/program.cps create mode 100644 tests/ii/arrays/reference.cc create mode 100644 tests/ii/calls/case.json create mode 100644 tests/ii/calls/generate.pl create mode 100644 tests/ii/calls/notes.md create mode 100644 tests/ii/calls/program.cps create mode 100644 tests/ii/calls/reference.cc create mode 100644 tests/ii/files/case.json create mode 100644 tests/ii/files/generate.pl create mode 100644 tests/ii/files/notes.md create mode 100644 tests/ii/files/program.cps create mode 100644 tests/ii/files/reference.cc create mode 100644 tests/ii/native-types/case.json create mode 100644 tests/ii/native-types/generate.pl create mode 100644 tests/ii/native-types/notes.md create mode 100644 tests/ii/native-types/program.cps create mode 100644 tests/ii/native-types/reference.cc create mode 100644 tests/ii/real/case.json create mode 100644 tests/ii/real/generate.pl create mode 100644 tests/ii/real/notes.md create mode 100644 tests/ii/real/program.cps create mode 100644 tests/ii/real/reference.cc create mode 100644 tests/ii/strings/case.json create mode 100644 tests/ii/strings/generate.pl create mode 100644 tests/ii/strings/notes.md create mode 100644 tests/ii/strings/program.cps create mode 100644 tests/ii/strings/reference.cc diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5a7dc2..4d385b9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,6 @@ foreach(suite i ii iii) --seed "${CPSC_TEST_SEED}" --count "${CPSC_TEST_COUNT}" --results "${CMAKE_BINARY_DIR}/test-results" ${reference_args}) set_tests_properties("${suite}/${case_name}" PROPERTIES - LABELS "${suite}" TIMEOUT 600) + LABELS "${suite}" TIMEOUT 900) endforeach() endforeach() diff --git a/tests/ii/Random.pm b/tests/ii/Random.pm new file mode 100644 index 0000000..a7a95e5 --- /dev/null +++ b/tests/ii/Random.pm @@ -0,0 +1,25 @@ +package Random; +use strict; +use warnings; +use Digest::SHA qw(sha256); + +# Fixed byte order and decimal counter: independent of Perl's rand and hash seed. +sub new { + my ($class, $seed, $group) = @_; + return bless { seed => $seed, group => $group, counter => 0 }, $class; +} +sub integer { + my ($self, $low, $high) = @_; + die "invalid random range\n" if $high < $low || $high - $low >= 4294967296; + my $bytes = sha256($self->{seed} . "\0" . $self->{group} . "\0" . $self->{counter}++); + return $low + unpack('N', substr($bytes, 0, 4)) % ($high - $low + 1); +} +sub emit { + my ($self, $count, $edges, $make) = @_; + die "positive count required\n" unless defined($count) && $count =~ /^[1-9][0-9]*$/; + binmode STDOUT; # Identical input bytes on every platform. + print scalar(@$edges) + $count, "\n"; + print "$_\n" for @$edges; + print $make->(), "\n" for 1 .. $count; +} +1; diff --git a/tests/ii/arithmetic/case.json b/tests/ii/arithmetic/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/arithmetic/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/arithmetic/generate.pl b/tests/ii/arithmetic/generate.pl new file mode 100644 index 0000000..24236a8 --- /dev/null +++ b/tests/ii/arithmetic/generate.pl @@ -0,0 +1,23 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "arithmetic"); +my @edges = ( + '9223372036854775807 1', + '-9223372036854775808 1', + '-9223372036854775808 2', + '0 -1', + '7 -3', + '-7 3', + '-7 -3', + '1 1', +); +$rng->emit($count, \@edges, sub { +my $a = $rng->integer(-1000000, 1000000); +my $b = $rng->integer(-1000000, 1000000); +$b = 1 if $b == 0; +return "$a $b"; +}); diff --git a/tests/ii/arithmetic/notes.md b/tests/ii/arithmetic/notes.md new file mode 100644 index 0000000..947a3fa --- /dev/null +++ b/tests/ii/arithmetic/notes.md @@ -0,0 +1,5 @@ +# arithmetic + +CIE pp. 15-16, project i64 and truncation-towards-zero DIV/MOD. Full-width extremes are used only in safe division/remainder and comparisons; addition, subtraction and multiplication use A MOD 1000001. B is nonzero, and INT64_MIN / -1 is never generated. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/arithmetic/program.cps b/tests/ii/arithmetic/program.cps new file mode 100644 index 0000000..7c4bf52 --- /dev/null +++ b/tests/ii/arithmetic/program.cps @@ -0,0 +1,12 @@ +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE A : INTEGER +DECLARE B : INTEGER +DECLARE Small : INTEGER +INPUT Count +FOR Index <- 1 TO Count + INPUT A + INPUT B + Small <- A MOD 1000001 + OUTPUT A < B, ":", A DIV B, ":", A MOD B, ":", Small + B, ":", Small - B, ":", Small * B +NEXT Index diff --git a/tests/ii/arithmetic/reference.cc b/tests/ii/arithmetic/reference.cc new file mode 100644 index 0000000..6d3d98f --- /dev/null +++ b/tests/ii/arithmetic/reference.cc @@ -0,0 +1,13 @@ +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + std::int64_t a, b; + if (!(std::cin >> a >> b) || b == 0) return 1; + auto small = a % 1000001; + std::cout << (a < b ? "TRUE" : "FALSE") << ':' << a / b << ':' << a % b + << ':' << small + b << ':' << small - b << ':' << small * b << '\n'; + } +} diff --git a/tests/ii/arrays/case.json b/tests/ii/arrays/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/arrays/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/arrays/generate.pl b/tests/ii/arrays/generate.pl new file mode 100644 index 0000000..6f4a5bd --- /dev/null +++ b/tests/ii/arrays/generate.pl @@ -0,0 +1,17 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "arrays"); +my @edges = ( + '-20 1 0 0', + '0 12 -100 1000', + '1 2 1 -1', + '-5 12 100 -1000', +); +$rng->emit($count, \@edges, sub { +return join ' ', $rng->integer(-20,20), $rng->integer(1,12), + $rng->integer(-100,100), $rng->integer(-1000,1000); +}); diff --git a/tests/ii/arrays/notes.md b/tests/ii/arrays/notes.md new file mode 100644 index 0000000..19300bf --- /dev/null +++ b/tests/ii/arrays/notes.md @@ -0,0 +1,5 @@ +# arrays + +CIE pp. 10-11,20; project dynamic array bounds. The C++ oracle uses an arithmetic-series formula, not the pseudocode loop. Sizes stay 1..12 and both inclusive endpoints are observed. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/arrays/program.cps b/tests/ii/arrays/program.cps new file mode 100644 index 0000000..5d2562e --- /dev/null +++ b/tests/ii/arrays/program.cps @@ -0,0 +1,22 @@ +DECLARE Count : INTEGER +DECLARE Record : INTEGER +DECLARE Lower : INTEGER +DECLARE Size : INTEGER +DECLARE Step : INTEGER +DECLARE Base : INTEGER +DECLARE I : INTEGER +DECLARE Sum : INTEGER +INPUT Count +FOR Record <- 1 TO Count + INPUT Lower + INPUT Size + INPUT Step + INPUT Base + DECLARE Values : ARRAY[Lower:Lower+Size-1] OF INTEGER + Sum <- 0 + FOR I <- Lower TO Lower + Size - 1 + Values[I] <- Base + (I - Lower) * Step + Sum <- Sum + Values[I] + NEXT I + OUTPUT Sum, ":", Values[Lower], ":", Values[Lower+Size-1] +NEXT Record diff --git a/tests/ii/arrays/reference.cc b/tests/ii/arrays/reference.cc new file mode 100644 index 0000000..9a136bd --- /dev/null +++ b/tests/ii/arrays/reference.cc @@ -0,0 +1,13 @@ +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int record = 0; record < count; ++record) { + std::int64_t lower, size, step, base; + if (!(std::cin >> lower >> size >> step >> base) || size < 1) return 1; + // Closed form is independent of pseudocode's indexed loop. + auto sum = size * base + step * size * (size - 1) / 2; + std::cout << sum << ':' << base << ':' << base + (size - 1) * step << '\n'; + } +} diff --git a/tests/ii/calls/case.json b/tests/ii/calls/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/calls/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/calls/generate.pl b/tests/ii/calls/generate.pl new file mode 100644 index 0000000..8bfe996 --- /dev/null +++ b/tests/ii/calls/generate.pl @@ -0,0 +1,16 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "calls"); +my @edges = ( + '0 0 0', + '1 -1000000 1000000', + '10 1000000 -1000000', +); +$rng->emit($count, \@edges, sub { +return join ' ', $rng->integer(0,10), $rng->integer(-1000000,1000000), + $rng->integer(-1000000,1000000); +}); diff --git a/tests/ii/calls/notes.md b/tests/ii/calls/notes.md new file mode 100644 index 0000000..87c999d --- /dev/null +++ b/tests/ii/calls/notes.md @@ -0,0 +1,5 @@ +# calls + +CIE pp. 12-14,22-24. Exercises record copies/BYVAL, pointer field mutation, sticky BYREF scalar parameters and recursive return paths. The oracle calculates direct values and a closed-form triangular sum. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/calls/program.cps b/tests/ii/calls/program.cps new file mode 100644 index 0000000..9a79ad6 --- /dev/null +++ b/tests/ii/calls/program.cps @@ -0,0 +1,40 @@ +TYPE Pair + DECLARE X : INTEGER + DECLARE Y : INTEGER +ENDTYPE +TYPE PairPtr = ^Pair +PROCEDURE IgnoreCopy(P : Pair) + P.X <- 999 +ENDPROCEDURE +PROCEDURE Shift(BYREF X : INTEGER, Y : INTEGER) + X <- X + 1 + Y <- Y - 1 +ENDPROCEDURE +FUNCTION Triangle(N : INTEGER) RETURNS INTEGER + IF N = 0 THEN + RETURN 0 + ENDIF + RETURN N + Triangle(N - 1) +ENDFUNCTION +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE N : INTEGER +DECLARE Value : Pair +DECLARE Saved : Pair +DECLARE P : PairPtr +INPUT Count +FOR Index <- 1 TO Count + INPUT N + DECLARE X : INTEGER + DECLARE Y : INTEGER + INPUT X + INPUT Y + Value.X <- X + Value.Y <- Y + Saved <- Value + CALL IgnoreCopy(Value) + P <- ^Value + P^.X <- P^.X + P^.Y + CALL Shift(X, Y) + OUTPUT Value.X, ":", Saved.X, ":", X, ":", Y, ":", Triangle(N) +NEXT Index diff --git a/tests/ii/calls/reference.cc b/tests/ii/calls/reference.cc new file mode 100644 index 0000000..c5ca6ca --- /dev/null +++ b/tests/ii/calls/reference.cc @@ -0,0 +1,12 @@ +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + std::int64_t n, x, y; + if (!(std::cin >> n >> x >> y)) return 1; + std::cout << x + y << ':' << x << ':' << x + 1 << ':' << y - 1 + << ':' << n * (n + 1) / 2 << '\n'; + } +} diff --git a/tests/ii/files/case.json b/tests/ii/files/case.json new file mode 100644 index 0000000..5609dd2 --- /dev/null +++ b/tests/ii/files/case.json @@ -0,0 +1,3 @@ +{ + "timeout": 180 +} diff --git a/tests/ii/files/generate.pl b/tests/ii/files/generate.pl new file mode 100644 index 0000000..18020d0 --- /dev/null +++ b/tests/ii/files/generate.pl @@ -0,0 +1,21 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "files"); +my @edges = ( + 'first last', + 'A Z', + '0 0', +); +$rng->emit($count, \@edges, sub { +my @alphabet = split //, 'ABCabc019_-'; +my @words; +for (1..2) { + my $size = $rng->integer(1,24); + push @words, join '', map { $alphabet[$rng->integer(0,$#alphabet)] } 1 .. $size; +} +return join ' ', @words; +}); diff --git a/tests/ii/files/notes.md b/tests/ii/files/notes.md new file mode 100644 index 0000000..02831fa --- /dev/null +++ b/tests/ii/files/notes.md @@ -0,0 +1,5 @@ +# files + +CIE p. 25. Both programs exercise actual text files, including truncation, append, empty lines and EOF. Separate working directories avoid shared files. Pseudocode uses the project-required WHILE DO spelling; i/standard-while separately checks the standard spelling. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/files/program.cps b/tests/ii/files/program.cps new file mode 100644 index 0000000..9bc4aad --- /dev/null +++ b/tests/ii/files/program.cps @@ -0,0 +1,29 @@ +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE Text : STRING +DECLARE Extra : STRING +DECLARE Line : STRING +DECLARE Combined : STRING +DECLARE Lines : INTEGER +INPUT Count +FOR Index <- 1 TO Count + INPUT Text + INPUT Extra + OPENFILE "roundtrip.txt" FOR WRITE + WRITEFILE "roundtrip.txt", Text + WRITEFILE "roundtrip.txt", "" + CLOSEFILE "roundtrip.txt" + OPENFILE "roundtrip.txt" FOR APPEND + WRITEFILE "roundtrip.txt", Extra + CLOSEFILE "roundtrip.txt" + OPENFILE "roundtrip.txt" FOR READ + Combined <- "" + Lines <- 0 + WHILE NOT EOF("roundtrip.txt") DO + READFILE "roundtrip.txt", Line + Combined <- Combined & "[" & Line & "]" + Lines <- Lines + 1 + ENDWHILE + CLOSEFILE "roundtrip.txt" + OUTPUT Lines, ":", Combined +NEXT Index diff --git a/tests/ii/files/reference.cc b/tests/ii/files/reference.cc new file mode 100644 index 0000000..d9b784c --- /dev/null +++ b/tests/ii/files/reference.cc @@ -0,0 +1,20 @@ +#include +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + std::string text, extra; + if (!(std::cin >> text >> extra)) return 1; + { std::ofstream file("roundtrip.txt"); file << text << "\n\n"; if (!file) return 1; } + { std::ofstream file("roundtrip.txt", std::ios::app); file << extra << '\n'; if (!file) return 1; } + std::ifstream file("roundtrip.txt"); + if (!file) return 1; + std::string line, combined; + int lines = 0; + while (std::getline(file, line)) { combined += '[' + line + ']'; ++lines; } + if (!file.eof()) return 1; + std::cout << lines << ':' << combined << '\n'; + } +} diff --git a/tests/ii/native-types/case.json b/tests/ii/native-types/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/native-types/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/native-types/generate.pl b/tests/ii/native-types/generate.pl new file mode 100644 index 0000000..35332cf --- /dev/null +++ b/tests/ii/native-types/generate.pl @@ -0,0 +1,20 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "native-types"); +my @edges = ( + '0 0', + '2147483647 127', + '2147483648 128', + '4294967295 255', + '4294967296 0', + '9223372036854775807 255', + '-9223372036854775808 128', + '-1 255', +); +$rng->emit($count, \@edges, sub { +return join ' ', $rng->integer(-1000000,1000000), $rng->integer(0,255); +}); diff --git a/tests/ii/native-types/notes.md b/tests/ii/native-types/notes.md new file mode 100644 index 0000000..a9de803 --- /dev/null +++ b/tests/ii/native-types/notes.md @@ -0,0 +1,5 @@ +# native-types + +Project C_TYPE contract only. C++ is built with the same CMake toolchain and ABI as cpsc: native long width, plain-char signedness, integer promotions and unsigned wrap follow that platform. Signed out-of-range casts are compared to that native implementation; no signed arithmetic overflow is performed. No pseudocode INTEGER overflow or REAL output is involved. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/native-types/program.cps b/tests/ii/native-types/program.cps new file mode 100644 index 0000000..92967b7 --- /dev/null +++ b/tests/ii/native-types/program.cps @@ -0,0 +1,22 @@ +TYPE NativeLong = C_TYPE("long") +TYPE NativeChar = C_TYPE("char") +TYPE Byte = C_TYPE("unsigned char") +TYPE Word = C_TYPE("unsigned int") +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE A : INTEGER +DECLARE B : INTEGER +DECLARE L : NativeLong +DECLARE C : NativeChar +DECLARE U : Byte +DECLARE W : Word +INPUT Count +FOR Index <- 1 TO Count + INPUT A + INPUT B + L <- (NativeLong) A + C <- (NativeChar) B + U <- (Byte) B + W <- (Word) A + OUTPUT L, ":", (INTEGER) C, ":", U + (Byte) 1, ":", W + (Word) 1 +NEXT Index diff --git a/tests/ii/native-types/reference.cc b/tests/ii/native-types/reference.cc new file mode 100644 index 0000000..64fc4cc --- /dev/null +++ b/tests/ii/native-types/reference.cc @@ -0,0 +1,18 @@ +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + std::int64_t a; + int b; + if (!(std::cin >> a >> b)) return 1; + auto l = static_cast(a); + auto c = static_cast(b); + auto u = static_cast(b); + auto w = static_cast(a); + std::cout << l << ':' << static_cast(c) << ':' + << u + static_cast(1) << ':' + << w + static_cast(1) << '\n'; + } +} diff --git a/tests/ii/real/case.json b/tests/ii/real/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/real/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/real/generate.pl b/tests/ii/real/generate.pl new file mode 100644 index 0000000..cf11ce6 --- /dev/null +++ b/tests/ii/real/generate.pl @@ -0,0 +1,17 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "real"); +my @edges = ( + '0 0', + '-8192 8192', + '8192 -8192', + '1 1', + '-1 1', +); +$rng->emit($count, \@edges, sub { +return join ' ', $rng->integer(-8192,8192), $rng->integer(-8192,8192); +}); diff --git a/tests/ii/real/notes.md b/tests/ii/real/notes.md new file mode 100644 index 0000000..59995b2 --- /dev/null +++ b/tests/ii/real/notes.md @@ -0,0 +1,5 @@ +# real + +CIE pp. 15-16,23-24. Binary fractions with denominator 8 keep sums and products exactly representable, so these checks need neither tolerances nor REAL text formatting. All intermediates fit a 32-bit C++ int. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/real/program.cps b/tests/ii/real/program.cps new file mode 100644 index 0000000..57834e1 --- /dev/null +++ b/tests/ii/real/program.cps @@ -0,0 +1,17 @@ +FUNCTION Twice(Value : REAL) RETURNS REAL + RETURN Value * 2.0 +ENDFUNCTION +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE A : INTEGER +DECLARE B : INTEGER +DECLARE X : REAL +DECLARE Y : REAL +INPUT Count +FOR Index <- 1 TO Count + INPUT A + INPUT B + X <- A / 8 + Y <- B / 8 + OUTPUT X < Y, ":", X = Y, ":", Twice(X) = (A / 4), ":", (X + Y) = ((A + B) / 8), ":", (X * Y) = ((A * B) / 64) +NEXT Index diff --git a/tests/ii/real/reference.cc b/tests/ii/real/reference.cc new file mode 100644 index 0000000..4c1e118 --- /dev/null +++ b/tests/ii/real/reference.cc @@ -0,0 +1,15 @@ +#include +const char *boolean(bool value) { return value ? "TRUE" : "FALSE"; } +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + int a, b; + if (!(std::cin >> a >> b)) return 1; + double x = a / 8.0, y = b / 8.0; + std::cout << boolean(x < y) << ':' << boolean(x == y) << ':' + << boolean(2.0 * x == a / 4.0) << ':' + << boolean(x + y == (a + b) / 8.0) << ':' + << boolean(x * y == (a * b) / 64.0) << '\n'; + } +} diff --git a/tests/ii/strings/case.json b/tests/ii/strings/case.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/ii/strings/case.json @@ -0,0 +1 @@ +{} diff --git a/tests/ii/strings/generate.pl b/tests/ii/strings/generate.pl new file mode 100644 index 0000000..f3ac098 --- /dev/null +++ b/tests/ii/strings/generate.pl @@ -0,0 +1,23 @@ +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/.."; +use Random; +my ($seed, $count) = @ARGV; +my $rng = Random->new($seed, "strings"); +my @edges = ( + '1 placeholder 1 0', + '0 A 1 1', + '0 Ab09 4 1', + '0 Ab09 1 4', + '0 Ab09 2 0', +); +$rng->emit($count, \@edges, sub { +my $size = $rng->integer(0,24); +return '1 placeholder 1 0' if $size == 0; +my @alphabet = split //, 'ABCXYZabcxyz019@_-'; +my $text = join '', map { $alphabet[$rng->integer(0,$#alphabet)] } 1 .. $size; +my $start = $rng->integer(1,$size); +my $width = $rng->integer(0,$size-$start+1); +return "0 $text $start $width"; +}); diff --git a/tests/ii/strings/notes.md b/tests/ii/strings/notes.md new file mode 100644 index 0000000..01ea90a --- /dev/null +++ b/tests/ii/strings/notes.md @@ -0,0 +1,5 @@ +# strings + +CIE pp. 16-17 for LENGTH/MID/RIGHT/concatenation; full-STRING LCASE/UCASE is a project extension. Inputs are ASCII under the default C locale. Empty input is encoded with a flag because project INPUT reads tokens. Substrings remain in range; zero width is a documented project boundary. + +Each record produces exactly one output line. Random records are preceded by fixed boundary records; every arithmetic intermediate is bounded. diff --git a/tests/ii/strings/program.cps b/tests/ii/strings/program.cps new file mode 100644 index 0000000..55e7541 --- /dev/null +++ b/tests/ii/strings/program.cps @@ -0,0 +1,17 @@ +DECLARE Count : INTEGER +DECLARE Index : INTEGER +DECLARE Empty : INTEGER +DECLARE Text : STRING +DECLARE Start : INTEGER +DECLARE Width : INTEGER +INPUT Count +FOR Index <- 1 TO Count + INPUT Empty + INPUT Text + INPUT Start + INPUT Width + IF Empty = 1 THEN + Text <- "" + ENDIF + OUTPUT LENGTH(Text), ":", MID(Text, Start, Width), ":", RIGHT(Text, Width), ":", Text = ("" & Text), ":", LCASE(Text), ":", UCASE(Text) +NEXT Index diff --git a/tests/ii/strings/reference.cc b/tests/ii/strings/reference.cc new file mode 100644 index 0000000..54d6efc --- /dev/null +++ b/tests/ii/strings/reference.cc @@ -0,0 +1,20 @@ +#include +#include +#include +#include +int main() { + int count; + if (!(std::cin >> count)) return 1; + for (int i = 0; i < count; ++i) { + int empty, start, width; + std::string text; + if (!(std::cin >> empty >> text >> start >> width)) return 1; + if (empty) text.clear(); + auto lower = text, upper = text; + for (auto &c : lower) c = static_cast(std::tolower(static_cast(c))); + for (auto &c : upper) c = static_cast(std::toupper(static_cast(c))); + auto mid = text.substr(static_cast(start - 1), static_cast(width)); + auto right = text.substr(text.size() - static_cast(width)); + std::cout << text.size() << ':' << mid << ':' << right << ":TRUE:" << lower << ':' << upper << '\n'; + } +} diff --git a/tests/run.py b/tests/run.py index 432d1b9..0d57728 100644 --- a/tests/run.py +++ b/tests/run.py @@ -201,7 +201,8 @@ def main(argv=None): if not records or int(records[0]) != len(records) - 1: raise Failure("generator must emit record count followed by one record per line") work = workspace(case_dir, directory, "reference") - reference = commands.run([args.reference], work, data) + reference = commands.run([args.reference], work, data, + timeout=config.get("timeout", 30)) if reference.returncode != 0 or reference.stderr: raise Failure(f"reference failed ({reference.returncode}): {reference.stderr!r}") expected = reference.stdout @@ -221,7 +222,7 @@ def main(argv=None): command = [target] else: command = [args.cpsc, source, "-r"] - result = commands.run(command, work, data) + result = commands.run(command, work, data, timeout=config.get("timeout", 30)) if suite == "ii" and normalized(result.stdout) != normalized(expected): wanted, got = normalized(expected).splitlines(), normalized(result.stdout).splitlines() index = next((i for i in range(min(len(wanted), len(got))) -- cgit v1.3.1-4-g156e