From 6b90cec14aa90cb9e840aa0ca659078a0c2615a5 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Fri, 16 Oct 2026 17:47:05 +0000 Subject: test(harness): port check_generators to Perl Co-Authored-By: Claude Fable 5.1 --- tests/CMakeLists.txt | 4 +-- tests/check_generators.pl | 87 +++++++++++++++++++++++++++++++++++++++++++++++ tests/check_generators.py | 26 -------------- 3 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 tests/check_generators.pl delete mode 100644 tests/check_generators.py 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 \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") -- cgit v1.3.1-4-g156e