aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-16 17:47:05 +0000
committer_ov4 <ov4@ov4.org>2026-10-16 17:47:05 +0000
commit6b90cec14aa90cb9e840aa0ca659078a0c2615a5 (patch)
tree6f5ab91285b3365623959010bf7e2d9ad22a3b34 /tests
parent13c80c288929045875ca2fb5de1d4f5266086209 (diff)
test(harness): port check_generators to Perl
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt4
-rw-r--r--tests/check_generators.pl87
-rw-r--r--tests/check_generators.py26
3 files changed, 89 insertions, 28 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 630d363..1cb2eff 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -5,8 +5,8 @@ set(CPSC_TEST_COUNT 10000 CACHE STRING "Random records per differential group")
add_test(NAME harness COMMAND "${Python3_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/test_runner.py")
-add_test(NAME generators COMMAND "${Python3_EXECUTABLE}"
- "${CMAKE_CURRENT_SOURCE_DIR}/check_generators.py" "${PERL_EXECUTABLE}")
+add_test(NAME generators COMMAND "${PERL_EXECUTABLE}"
+ "${CMAKE_CURRENT_SOURCE_DIR}/check_generators.pl" "${PERL_EXECUTABLE}")
foreach(suite i ii iii)
file(GLOB cases CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${suite}/*/case.json")
foreach(manifest IN LISTS cases)
diff --git a/tests/check_generators.pl b/tests/check_generators.pl
new file mode 100644
index 0000000..731215b
--- /dev/null
+++ b/tests/check_generators.pl
@@ -0,0 +1,87 @@
+#!/usr/bin/env perl
+# Require identical generated input bytes across platforms and Perl versions.
+use strict;
+use warnings;
+use Digest::SHA qw(sha256_hex);
+use File::Spec;
+use File::Temp ();
+use FindBin;
+use JSON::PP ();
+
+die "usage: $0 <perl>\n" unless @ARGV == 1;
+my ($perl) = @ARGV;
+
+my $dir = File::Spec->catdir($FindBin::Bin, 'ii');
+my $vectors_path = File::Spec->catfile($dir, 'generator-vectors.json');
+open my $fh, '<:raw', $vectors_path or die "cannot read $vectors_path: $!\n";
+my $vectors_text = do { local $/; <$fh> };
+close $fh;
+my $vectors = JSON::PP->new->decode($vectors_text);
+
+for my $name (sort keys %{$vectors}) {
+ my $digest = $vectors->{$name};
+ my $generate = File::Spec->catfile($dir, $name, 'generate.pl');
+ my @outputs;
+ for my $seed (('0' x 40, '0' x 40, '1' x 40)) {
+ my ($exit, $out, $err) = run_capture($perl, $generate, $seed, '100');
+ die "$name: generator exited with $exit\n" if $exit != 0;
+ if (length $err) {
+ die "$name: unexpected stderr " . display($err) . "\n";
+ }
+ push @outputs, $out;
+ }
+ if ($outputs[0] ne $outputs[1] || $outputs[0] eq $outputs[2]) {
+ die "$name: seed reproducibility failed\n";
+ }
+ if (sha256_hex($outputs[0]) ne $digest) {
+ die "$name: generated input differs from the portable test vector\n";
+ }
+ print "PASS $name: repeatable, seed-sensitive, portable bytes\n";
+}
+
+sub run_capture {
+ my (@command) = @_;
+ my $out_fh = File::Temp->new(UNLINK => 1);
+ my $err_fh = File::Temp->new(UNLINK => 1);
+ my $out_path = $out_fh->filename;
+ my $err_path = $err_fh->filename;
+ close $out_fh;
+ close $err_fh;
+ my $pid = fork();
+ die "cannot fork: $!\n" unless defined $pid;
+ if ($pid == 0) {
+ open STDOUT, '>', $out_path or exit 127;
+ open STDERR, '>', $err_path or exit 127;
+ binmode STDOUT;
+ binmode STDERR;
+ exec { $command[0] } @command;
+ exit 127;
+ }
+ my $timed_out = 0;
+ local $SIG{ALRM} = sub { $timed_out = 1; kill 'KILL', $pid; };
+ alarm 10;
+ waitpid($pid, 0);
+ alarm 0;
+ if ($timed_out) {
+ waitpid($pid, 0);
+ die "generator timed out after 10s: @command\n";
+ }
+ my $status = $?;
+ open my $out_in, '<:raw', $out_path or die "cannot read $out_path: $!\n";
+ my $out = do { local $/; <$out_in> };
+ close $out_in;
+ open my $err_in, '<:raw', $err_path or die "cannot read $err_path: $!\n";
+ my $err = do { local $/; <$err_in> };
+ close $err_in;
+ $out = '' unless defined $out;
+ $err = '' unless defined $err;
+ my $exit = ($status >> 8);
+ return ($exit, $out, $err);
+}
+
+sub display {
+ my ($bytes) = @_;
+ $bytes =~ s/([^ -~\n])/sprintf('\\x{%02x}', ord($1))/ge;
+ $bytes =~ s/\n/\\n/g;
+ return "'$bytes'";
+}
diff --git a/tests/check_generators.py b/tests/check_generators.py
deleted file mode 100644
index 2b92789..0000000
--- a/tests/check_generators.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python3
-"""Require identical generated input bytes across platforms and Perl versions."""
-
-import hashlib
-import json
-from pathlib import Path
-import subprocess
-import sys
-
-
-directory = Path(__file__).resolve().parent / "ii"
-vectors = json.loads((directory / "generator-vectors.json").read_text(encoding="utf-8"))
-for name, digest in vectors.items():
- command = [sys.argv[1], str(directory / name / "generate.pl")]
- outputs = []
- for seed in ["0" * 40, "0" * 40, "1" * 40]:
- result = subprocess.run([*command, seed, "100"], check=True, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE, timeout=10)
- if result.stderr:
- raise RuntimeError(f"{name}: unexpected stderr {result.stderr!r}")
- outputs.append(result.stdout)
- if outputs[0] != outputs[1] or outputs[0] == outputs[2]:
- raise RuntimeError(f"{name}: seed reproducibility failed")
- if hashlib.sha256(outputs[0]).hexdigest() != digest:
- raise RuntimeError(f"{name}: generated input differs from the portable test vector")
- print(f"PASS {name}: repeatable, seed-sensitive, portable bytes")