diff --git a/dev/cpan-reports/cpan-compatibility-fail.dat b/dev/cpan-reports/cpan-compatibility-fail.dat new file mode 100644 index 000000000..ee90b4bf6 --- /dev/null +++ b/dev/cpan-reports/cpan-compatibility-fail.dat @@ -0,0 +1,134 @@ +A1z::Html FAIL Unknown test outcome 2026-04-12 +Alien::Base::Wrapper FAIL Unknown test outcome 2026-04-12 +AnnoCPAN::Perldoc::SyncDB FAIL No parseable output 2026-04-12 +Announcements FAIL Configure failed 2026-04-12 +App::Cmd::Setup FAIL 57 31 26/57 subtests failed 2026-04-12 +Asm::Preproc FAIL 1 0 1/1 subtests failed 2026-04-12 +BerkeleyDB FAIL 3 3 2026-04-12 +Bit::Vector FAIL 1 0 14/1 subtests failed 2026-04-12 +CGI::Emulate::PSGI FAIL 41 28 13/41 subtests failed 2026-04-12 +CGI::PSGI FAIL Configure failed 2026-04-12 +Carp::Clan FAIL 116 58 58/116 subtests failed 2026-04-12 +Child FAIL 8 8 2026-04-12 +Class::Accessor FAIL 139 137 2/139 subtests failed 2026-04-12 +Class::Accessor::Lite FAIL Configure failed 2026-04-12 +Class::Load FAIL 86 70 16/86 subtests failed 2026-04-12 +Class::XSAccessor FAIL 10 0 184/10 subtests failed 2026-04-12 +Const::Fast FAIL 1 0 1/1 subtests failed 2026-04-12 +Crypt::Sodium FAIL 1 0 1/1 subtests failed 2026-04-12 +DBIx::Class::Schema::PopulateMore FAIL Configure failed 2026-04-12 +DBIx::Class::TimeStamp FAIL Configure failed 2026-04-12 +DBIx::Class::UUIDColumns FAIL Configure failed 2026-04-12 +DBIx::Tree::NestedSet FAIL 2026-04-12 +Dancer2::Plugin::CSRF FAIL 1 0 1/1 subtests failed 2026-04-12 +Dancer2::Plugin::SlapbirdAPM FAIL 1 0 1/1 subtests failed 2026-04-12 +Data::Alias FAIL 1 0 635/1 subtests failed 2026-04-12 +Data::Dump FAIL 2026-04-12 +Data::Serializer::JSON FAIL 480 231 249/480 subtests failed 2026-04-12 +Data::Stream::Bulk FAIL 13 0 13/13 subtests failed 2026-04-12 +Data::UUID FAIL 1 0 32/1 subtests failed 2026-04-12 +Data::Visitor FAIL 1 0 1/1 subtests failed 2026-04-12 +Date::Calc FAIL 2997 2951 46/2997 subtests failed 2026-04-12 +DateTime::Format::Builder FAIL 11 9 2/11 subtests failed 2026-04-12 +DateTime::Format::Duration::XSD FAIL 1 0 37/1 subtests failed 2026-04-12 +DateTime::Format::Flexible FAIL Unknown test outcome 2026-04-12 +Devel::Caller FAIL 1 0 72/1 subtests failed 2026-04-12 +Devel::CheckCompiler FAIL 7 4 3/7 subtests failed 2026-04-12 +Devel::PPPort FAIL Configure failed 2026-04-12 +Devel::Symdump FAIL Configure failed 2026-04-12 +Digest::SHA1 FAIL 2026-04-12 +Directory::Scratch FAIL Unknown test outcome 2026-04-12 +FFI::CheckLib FAIL Unknown test outcome 2026-04-12 +File::Copy::Recursive::Reduced FAIL 2026-04-12 +File::Path::Expand FAIL 1 0 8/1 subtests failed 2026-04-12 +Filter::signatures FAIL 10 0 59/10 subtests failed 2026-04-12 +Font::Metrics::Courier FAIL 2 2 Missing: Font/AFM.pm 2026-04-12 +Geo::IP FAIL Configure failed 2026-04-12 +HTML::FormatText FAIL 29 11 18/29 subtests failed 2026-04-12 +HTML::FormatText::Any FAIL PerlOnJava: register limit exceeded 2026-04-12 +HTML::Summary FAIL 2026-04-12 +HTML::Template FAIL 608 605 3/608 subtests failed 2026-04-12 +HTML::TreeBuilder FAIL 399 0 593/399 subtests failed 2026-04-12 +HTML::Widgets::NavMenu FAIL 46 0 321/46 subtests failed 2026-04-12 +HTTP::Headers::ActionPack FAIL 448 445 3/448 subtests failed 2026-04-12 +HTTP::Server::Simple FAIL 14 0 76/14 subtests failed 2026-04-12 +I18N::String FAIL Unknown test outcome 2026-04-12 +IO::Infiles FAIL 8 6 2/8 subtests failed 2026-04-12 +IO::Pipe FAIL 16586 8581 8005/16586 subtests failed 2026-04-12 +Iterator::Array::Jagged FAIL Unknown test outcome 2026-04-12 +Iterator::Simple FAIL Configure failed 2026-04-12 +Iterator::Simple::Lookahead FAIL 1 1 Missing: Iterator/Simple.pm 2026-04-12 +JSON::RPC FAIL 30 21 9/30 subtests failed 2026-04-12 +JSON::Validator::Ref FAIL Unknown test outcome 2026-04-12 +Jcode FAIL Missing: diagnostics.pm 2026-04-12 +LabKey::Query FAIL 12 6 6/12 subtests failed 2026-04-12 +LinuxRealTime FAIL 1 0 1/1 subtests failed 2026-04-12 +List::MoreUtils FAIL Unknown test outcome 2026-04-12 +Mac::SystemDirectory FAIL Configure failed 2026-04-12 +Mixin::Linewise::Readers FAIL 1 1 2026-04-12 +Module::Build::XSUtil FAIL 3 1 2/3 subtests failed 2026-04-12 +MooX::BuildArgs FAIL 2026-04-12 +MooX::Enumeration FAIL Configure failed 2026-04-12 +Moose FAIL Configure failed 2026-04-12 +Moose::Meta::TypeConstraint::Role FAIL Configure failed 2026-04-12 +Moose::Util::TypeConstraints FAIL Configure failed 2026-04-12 +MooseX::Attribute::Localize FAIL 2 0 10/2 subtests failed 2026-04-12 +MooseX::DOM FAIL Configure failed 2026-04-12 +MooseX::NonMoose FAIL 1 1 Missing: Moose.pm 2026-04-12 +MooseX::Params::Validate FAIL 5 1 4/5 subtests failed 2026-04-12 +MooseX::StrictConstructor FAIL 1 1 Missing: Test/Moose.pm 2026-04-12 +MooseX::Types FAIL 11 4 7/11 subtests failed 2026-04-12 +MooseX::Types::Moose FAIL 11 4 7/11 subtests failed 2026-04-12 +MooseX::Types::Path::Class FAIL 3 3 Missing: Moose.pm 2026-04-12 +MouseX::Types FAIL Configure failed 2026-04-12 +Net::DNS FAIL Unknown test outcome 2026-04-12 +OpenGL::XScreenSaver FAIL 1 0 11/1 subtests failed 2026-04-12 +OpusVL::SimpleCrypto FAIL 1 1 2026-04-12 +OvhApi FAIL 1 0 1/1 subtests failed 2026-04-12 +PDF::FromHTML FAIL Configure failed 2026-04-12 +PONAPI::Document FAIL Unknown test outcome 2026-04-12 +Package::Variant FAIL 2026-04-12 +PadWalker FAIL 2026-04-12 +Params::Validate FAIL Build failed 2026-04-12 +PerlIO::utf8_strict FAIL 5816 2389 3427/5816 subtests failed 2026-04-12 +PkgConfig FAIL Unknown test outcome 2026-04-12 +Pod::Coverage FAIL Unknown test outcome 2026-04-12 +Pod::Coverage::TrustPod FAIL 5 1 4/5 subtests failed 2026-04-12 +Pod::Eventual::Simple FAIL 1 0 4/1 subtests failed 2026-04-12 +Pod::Find FAIL 25 24 1/25 subtests failed 2026-04-12 +Pod::Spell FAIL 45 45 2026-04-12 +REST::Client FAIL 2 2 2026-04-12 +Router::Simple FAIL 1 0 1/1 subtests failed 2026-04-12 +SWIFT::Factory::Tag::Tag30 FAIL Unknown test outcome 2026-04-12 +SWIFT::Factory::Tag::Tag30T FAIL Unknown test outcome 2026-04-12 +Smart::Args FAIL 1 0 1/1 subtests failed 2026-04-12 +Smart::Comments FAIL Unknown test outcome 2026-04-12 +SpamMonkey FAIL Missing: File/Path/Expand.pm 2026-04-12 +Spiffy FAIL 168 148 20/168 subtests failed 2026-04-12 +String::CamelCase FAIL 31 27 4/31 subtests failed 2026-04-12 +Sub::Exporter::ForMethods FAIL 10 6 4/10 subtests failed 2026-04-12 +SystemTray::Applet FAIL No parseable output 2026-04-12 +Test::Base FAIL Unknown test outcome 2026-04-12 +Test::Differences FAIL 49 45 4/49 subtests failed 2026-04-12 +Test::Memory::Cycle FAIL 38 19 19/38 subtests failed 2026-04-12 +Test::Spelling FAIL 23 6 17/23 subtests failed 2026-04-12 +Test::TempDir FAIL 7 1 6/7 subtests failed 2026-04-12 +Text::Template FAIL 163 100 63/163 subtests failed 2026-04-12 +Tie::File FAIL 4725 4389 336/4725 subtests failed 2026-04-12 +Types::Serialiser FAIL Missing: common/sense.pm 2026-04-12 +UNIX::Cal FAIL Configure failed 2026-04-12 +URI::Find FAIL 619 617 2/619 subtests failed 2026-04-12 +URI::Template::Restrict FAIL Configure failed 2026-04-12 +VM::CloudAtCost FAIL 1 0 1/1 subtests failed 2026-04-12 +WWW::Mechanize FAIL Unknown test outcome 2026-04-12 +WebService::ChatWorkApi FAIL 14 4 10/14 subtests failed 2026-04-12 +Win32::GUI::HyperLink FAIL Configure failed 2026-04-12 +WordList::ID::KBBI::ByClass::Noun FAIL No parseable output 2026-04-12 +XML::GDOME FAIL Missing: XML/GDOME.pm 2026-04-12 +XML::SAX FAIL 109 105 4/109 subtests failed 2026-04-12 +XML::SAX::Base FAIL Unknown test outcome 2026-04-12 +XML::Twig FAIL Unknown test outcome 2026-04-12 +XML::Writer FAIL 273 267 6/273 subtests failed 2026-04-12 +YAML::Syck FAIL Configure failed 2026-04-12 +YAML::XS FAIL 2 0 48/2 subtests failed 2026-04-12 +strictures FAIL 5 4 1/5 subtests failed 2026-04-12 diff --git a/dev/cpan-reports/cpan-compatibility-pass.dat b/dev/cpan-reports/cpan-compatibility-pass.dat new file mode 100644 index 000000000..99dd1e3e5 --- /dev/null +++ b/dev/cpan-reports/cpan-compatibility-pass.dat @@ -0,0 +1,37 @@ +Algorithm::Diff PASS 1004 1004 2026-04-12 cc5efa220 +Alien::Build::Plugin::Download::GitLab PASS 2 2 2026-04-12 cc5efa220 +Array::Utils PASS 17 17 2026-04-12 cc5efa220 +CGI::Application PASS 189 189 2026-04-12 cc5efa220 +CGI::Application::Plugin::AbstractCallback PASS 2 2 2026-04-12 cc5efa220 +Canary::Stability PASS 1 1 2026-04-12 cc5efa220 +Class::ErrorHandler PASS 10 10 2026-04-12 cc5efa220 +Class::ISA PASS 4 4 2026-04-12 cc5efa220 +Crypt::SaltedHash PASS 10 10 2026-04-12 cc5efa220 +Cwd::Guard PASS 6 6 2026-04-12 cc5efa220 +DBIx::MyPassword PASS 13 13 2026-04-12 d833a1ecb +Data::MethodProxy PASS 10 10 2026-04-12 cc5efa220 +DateTime::Format::Strptime PASS 143 143 2026-04-12 cc5efa220 +Exception PASS 4 4 2026-04-12 cc5efa220 +File::HomeDir PASS 90 90 2026-04-12 cc5efa220 +File::Slurper PASS 8 8 2026-04-12 cc5efa220 +HTML::Form PASS 223 223 2026-04-12 cc5efa220 +IO::Scalar PASS 127 127 2026-04-12 cc5efa220 +IO::TieCombine PASS 7 7 2026-04-12 cc5efa220 +Image::GIF::Encoder::PP PASS 2 2 2026-04-12 cc5efa220 +Lingua::EN::Inflect PASS 2147 2147 2026-04-12 cc5efa220 +Module::Pluggable::Fast PASS 6 6 2026-04-12 cc5efa220 +Net::Telnet PASS 3 3 2026-04-12 cc5efa220 +OpusVL::Text::Util PASS 50 50 2026-04-12 cc5efa220 +OurNet::BBSAgent PASS 2 2 2026-04-12 cc5efa220 +Parse::RecDescent PASS 139 139 2026-04-12 cc5efa220 +PlayStation::MemoryCard PASS 1 1 2026-04-12 cc5efa220 +String::RewritePrefix PASS 39 39 2026-04-12 cc5efa220 +TAP::Harness::Metrics PASS 4 4 2026-04-12 cc5efa220 +Test::More::UTF8 PASS 14 14 2026-04-12 cc5efa220 +Test::Most PASS 88 88 2026-04-12 cc5efa220 +Test::Output PASS 1217 1217 2026-04-12 cc5efa220 +Test::Strict PASS 80 80 2026-04-12 cc5efa220 +Text::Diff PASS 33 33 2026-04-12 cc5efa220 +Tie::ToObject PASS 10 10 2026-04-12 cc5efa220 +UNIVERSAL::require PASS 25 25 2026-04-12 cc5efa220 +XML::NamespaceSupport PASS 49 49 2026-04-12 cc5efa220 diff --git a/dev/cpan-reports/cpan-compatibility-skip.dat b/dev/cpan-reports/cpan-compatibility-skip.dat new file mode 100644 index 000000000..e69de29bb diff --git a/dev/cpan-reports/cpan-compatibility.md b/dev/cpan-reports/cpan-compatibility.md new file mode 100644 index 000000000..5e1369e94 --- /dev/null +++ b/dev/cpan-reports/cpan-compatibility.md @@ -0,0 +1,245 @@ +# CPAN Module Compatibility Report for PerlOnJava + +> Auto-generated by `dev/tools/cpan_random_tester.pl` on 2026-04-12 15:28:12 +> +> Modules are randomly selected from the full CPAN index and tested +> with `./jcpan -t`. Dependencies are tested too; every module that +> appears in the output is recorded. + +## Summary + +| Metric | Count | +|--------|-------| +| **Modules Tested** | 171 | +| **Pass** | 37 (21.6%) | +| **Fail** | 134 | +| **Skipped (XS-only)** | 0 | + +## Modules That Pass All Tests + +| Module | Subtests | Date | Git Commit | +|--------|----------|------|------------| +| Algorithm::Diff | 1004 | 2026-04-12 | cc5efa220 | +| Alien::Build::Plugin::Download::GitLab | 2 | 2026-04-12 | cc5efa220 | +| Array::Utils | 17 | 2026-04-12 | cc5efa220 | +| CGI::Application | 189 | 2026-04-12 | cc5efa220 | +| CGI::Application::Plugin::AbstractCallback | 2 | 2026-04-12 | cc5efa220 | +| Canary::Stability | 1 | 2026-04-12 | cc5efa220 | +| Class::ErrorHandler | 10 | 2026-04-12 | cc5efa220 | +| Class::ISA | 4 | 2026-04-12 | cc5efa220 | +| Crypt::SaltedHash | 10 | 2026-04-12 | cc5efa220 | +| Cwd::Guard | 6 | 2026-04-12 | cc5efa220 | +| DBIx::MyPassword | 13 | 2026-04-12 | d833a1ecb | +| Data::MethodProxy | 10 | 2026-04-12 | cc5efa220 | +| DateTime::Format::Strptime | 143 | 2026-04-12 | cc5efa220 | +| Exception | 4 | 2026-04-12 | cc5efa220 | +| File::HomeDir | 90 | 2026-04-12 | cc5efa220 | +| File::Slurper | 8 | 2026-04-12 | cc5efa220 | +| HTML::Form | 223 | 2026-04-12 | cc5efa220 | +| IO::Scalar | 127 | 2026-04-12 | cc5efa220 | +| IO::TieCombine | 7 | 2026-04-12 | cc5efa220 | +| Image::GIF::Encoder::PP | 2 | 2026-04-12 | cc5efa220 | +| Lingua::EN::Inflect | 2147 | 2026-04-12 | cc5efa220 | +| Module::Pluggable::Fast | 6 | 2026-04-12 | cc5efa220 | +| Net::Telnet | 3 | 2026-04-12 | cc5efa220 | +| OpusVL::Text::Util | 50 | 2026-04-12 | cc5efa220 | +| OurNet::BBSAgent | 2 | 2026-04-12 | cc5efa220 | +| Parse::RecDescent | 139 | 2026-04-12 | cc5efa220 | +| PlayStation::MemoryCard | 1 | 2026-04-12 | cc5efa220 | +| String::RewritePrefix | 39 | 2026-04-12 | cc5efa220 | +| TAP::Harness::Metrics | 4 | 2026-04-12 | cc5efa220 | +| Test::More::UTF8 | 14 | 2026-04-12 | cc5efa220 | +| Test::Most | 88 | 2026-04-12 | cc5efa220 | +| Test::Output | 1217 | 2026-04-12 | cc5efa220 | +| Test::Strict | 80 | 2026-04-12 | cc5efa220 | +| Text::Diff | 33 | 2026-04-12 | cc5efa220 | +| Tie::ToObject | 10 | 2026-04-12 | cc5efa220 | +| UNIVERSAL::require | 25 | 2026-04-12 | cc5efa220 | +| XML::NamespaceSupport | 49 | 2026-04-12 | cc5efa220 | + +## Modules That Fail Tests + +### Configure Failed (22 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| Announcements | | Configure failed | 2026-04-12 | +| CGI::PSGI | | Configure failed | 2026-04-12 | +| Class::Accessor::Lite | | Configure failed | 2026-04-12 | +| DBIx::Class::Schema::PopulateMore | | Configure failed | 2026-04-12 | +| DBIx::Class::TimeStamp | | Configure failed | 2026-04-12 | +| DBIx::Class::UUIDColumns | | Configure failed | 2026-04-12 | +| Devel::PPPort | | Configure failed | 2026-04-12 | +| Devel::Symdump | | Configure failed | 2026-04-12 | +| Geo::IP | | Configure failed | 2026-04-12 | +| Iterator::Simple | | Configure failed | 2026-04-12 | +| Mac::SystemDirectory | | Configure failed | 2026-04-12 | +| MooX::Enumeration | | Configure failed | 2026-04-12 | +| Moose | | Configure failed | 2026-04-12 | +| Moose::Meta::TypeConstraint::Role | | Configure failed | 2026-04-12 | +| Moose::Util::TypeConstraints | | Configure failed | 2026-04-12 | +| MooseX::DOM | | Configure failed | 2026-04-12 | +| MouseX::Types | | Configure failed | 2026-04-12 | +| PDF::FromHTML | | Configure failed | 2026-04-12 | +| UNIX::Cal | | Configure failed | 2026-04-12 | +| URI::Template::Restrict | | Configure failed | 2026-04-12 | +| Win32::GUI::HyperLink | | Configure failed | 2026-04-12 | +| YAML::Syck | | Configure failed | 2026-04-12 | + +### Missing Dependencies (9 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| Font::Metrics::Courier | 2/2 | Missing: Font/AFM.pm | 2026-04-12 | +| Iterator::Simple::Lookahead | 1/1 | Missing: Iterator/Simple.pm | 2026-04-12 | +| Jcode | | Missing: diagnostics.pm | 2026-04-12 | +| MooseX::NonMoose | 1/1 | Missing: Moose.pm | 2026-04-12 | +| MooseX::StrictConstructor | 1/1 | Missing: Test/Moose.pm | 2026-04-12 | +| MooseX::Types::Path::Class | 3/3 | Missing: Moose.pm | 2026-04-12 | +| SpamMonkey | | Missing: File/Path/Expand.pm | 2026-04-12 | +| Types::Serialiser | | Missing: common/sense.pm | 2026-04-12 | +| XML::GDOME | | Missing: XML/GDOME.pm | 2026-04-12 | + +### Other (32 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| A1z::Html | | Unknown test outcome | 2026-04-12 | +| Alien::Base::Wrapper | | Unknown test outcome | 2026-04-12 | +| AnnoCPAN::Perldoc::SyncDB | | No parseable output | 2026-04-12 | +| DBIx::Tree::NestedSet | | | 2026-04-12 | +| Data::Dump | | | 2026-04-12 | +| DateTime::Format::Flexible | | Unknown test outcome | 2026-04-12 | +| Digest::SHA1 | | | 2026-04-12 | +| Directory::Scratch | | Unknown test outcome | 2026-04-12 | +| FFI::CheckLib | | Unknown test outcome | 2026-04-12 | +| File::Copy::Recursive::Reduced | | | 2026-04-12 | +| HTML::Summary | | | 2026-04-12 | +| I18N::String | | Unknown test outcome | 2026-04-12 | +| Iterator::Array::Jagged | | Unknown test outcome | 2026-04-12 | +| JSON::Validator::Ref | | Unknown test outcome | 2026-04-12 | +| List::MoreUtils | | Unknown test outcome | 2026-04-12 | +| MooX::BuildArgs | | | 2026-04-12 | +| Net::DNS | | Unknown test outcome | 2026-04-12 | +| PONAPI::Document | | Unknown test outcome | 2026-04-12 | +| Package::Variant | | | 2026-04-12 | +| PadWalker | | | 2026-04-12 | +| Params::Validate | | Build failed | 2026-04-12 | +| PkgConfig | | Unknown test outcome | 2026-04-12 | +| Pod::Coverage | | Unknown test outcome | 2026-04-12 | +| SWIFT::Factory::Tag::Tag30 | | Unknown test outcome | 2026-04-12 | +| SWIFT::Factory::Tag::Tag30T | | Unknown test outcome | 2026-04-12 | +| Smart::Comments | | Unknown test outcome | 2026-04-12 | +| SystemTray::Applet | | No parseable output | 2026-04-12 | +| Test::Base | | Unknown test outcome | 2026-04-12 | +| WWW::Mechanize | | Unknown test outcome | 2026-04-12 | +| WordList::ID::KBBI::ByClass::Noun | | No parseable output | 2026-04-12 | +| XML::SAX::Base | | Unknown test outcome | 2026-04-12 | +| XML::Twig | | Unknown test outcome | 2026-04-12 | + +### PerlOnJava Limits (1 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| HTML::FormatText::Any | | PerlOnJava: register limit exceeded | 2026-04-12 | + +### Test Failures (70 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| App::Cmd::Setup | 31/57 | 26/57 subtests failed | 2026-04-12 | +| Asm::Preproc | 0/1 | 1/1 subtests failed | 2026-04-12 | +| BerkeleyDB | 3/3 | | 2026-04-12 | +| Bit::Vector | 0/1 | 14/1 subtests failed | 2026-04-12 | +| CGI::Emulate::PSGI | 28/41 | 13/41 subtests failed | 2026-04-12 | +| Carp::Clan | 58/116 | 58/116 subtests failed | 2026-04-12 | +| Child | 8/8 | | 2026-04-12 | +| Class::Accessor | 137/139 | 2/139 subtests failed | 2026-04-12 | +| Class::Load | 70/86 | 16/86 subtests failed | 2026-04-12 | +| Class::XSAccessor | 0/10 | 184/10 subtests failed | 2026-04-12 | +| Const::Fast | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Crypt::Sodium | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Dancer2::Plugin::CSRF | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Dancer2::Plugin::SlapbirdAPM | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Data::Alias | 0/1 | 635/1 subtests failed | 2026-04-12 | +| Data::Serializer::JSON | 231/480 | 249/480 subtests failed | 2026-04-12 | +| Data::Stream::Bulk | 0/13 | 13/13 subtests failed | 2026-04-12 | +| Data::UUID | 0/1 | 32/1 subtests failed | 2026-04-12 | +| Data::Visitor | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Date::Calc | 2951/2997 | 46/2997 subtests failed | 2026-04-12 | +| DateTime::Format::Builder | 9/11 | 2/11 subtests failed | 2026-04-12 | +| DateTime::Format::Duration::XSD | 0/1 | 37/1 subtests failed | 2026-04-12 | +| Devel::Caller | 0/1 | 72/1 subtests failed | 2026-04-12 | +| Devel::CheckCompiler | 4/7 | 3/7 subtests failed | 2026-04-12 | +| File::Path::Expand | 0/1 | 8/1 subtests failed | 2026-04-12 | +| Filter::signatures | 0/10 | 59/10 subtests failed | 2026-04-12 | +| HTML::FormatText | 11/29 | 18/29 subtests failed | 2026-04-12 | +| HTML::Template | 605/608 | 3/608 subtests failed | 2026-04-12 | +| HTML::TreeBuilder | 0/399 | 593/399 subtests failed | 2026-04-12 | +| HTML::Widgets::NavMenu | 0/46 | 321/46 subtests failed | 2026-04-12 | +| HTTP::Headers::ActionPack | 445/448 | 3/448 subtests failed | 2026-04-12 | +| HTTP::Server::Simple | 0/14 | 76/14 subtests failed | 2026-04-12 | +| IO::Infiles | 6/8 | 2/8 subtests failed | 2026-04-12 | +| IO::Pipe | 8581/16586 | 8005/16586 subtests failed | 2026-04-12 | +| JSON::RPC | 21/30 | 9/30 subtests failed | 2026-04-12 | +| LabKey::Query | 6/12 | 6/12 subtests failed | 2026-04-12 | +| LinuxRealTime | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Mixin::Linewise::Readers | 1/1 | | 2026-04-12 | +| Module::Build::XSUtil | 1/3 | 2/3 subtests failed | 2026-04-12 | +| MooseX::Attribute::Localize | 0/2 | 10/2 subtests failed | 2026-04-12 | +| MooseX::Params::Validate | 1/5 | 4/5 subtests failed | 2026-04-12 | +| MooseX::Types | 4/11 | 7/11 subtests failed | 2026-04-12 | +| MooseX::Types::Moose | 4/11 | 7/11 subtests failed | 2026-04-12 | +| OpenGL::XScreenSaver | 0/1 | 11/1 subtests failed | 2026-04-12 | +| OpusVL::SimpleCrypto | 1/1 | | 2026-04-12 | +| OvhApi | 0/1 | 1/1 subtests failed | 2026-04-12 | +| PerlIO::utf8_strict | 2389/5816 | 3427/5816 subtests failed | 2026-04-12 | +| Pod::Coverage::TrustPod | 1/5 | 4/5 subtests failed | 2026-04-12 | +| Pod::Eventual::Simple | 0/1 | 4/1 subtests failed | 2026-04-12 | +| Pod::Find | 24/25 | 1/25 subtests failed | 2026-04-12 | +| Pod::Spell | 45/45 | | 2026-04-12 | +| REST::Client | 2/2 | | 2026-04-12 | +| Router::Simple | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Smart::Args | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Spiffy | 148/168 | 20/168 subtests failed | 2026-04-12 | +| String::CamelCase | 27/31 | 4/31 subtests failed | 2026-04-12 | +| Sub::Exporter::ForMethods | 6/10 | 4/10 subtests failed | 2026-04-12 | +| Test::Differences | 45/49 | 4/49 subtests failed | 2026-04-12 | +| Test::Memory::Cycle | 19/38 | 19/38 subtests failed | 2026-04-12 | +| Test::Spelling | 6/23 | 17/23 subtests failed | 2026-04-12 | +| Test::TempDir | 1/7 | 6/7 subtests failed | 2026-04-12 | +| Text::Template | 100/163 | 63/163 subtests failed | 2026-04-12 | +| Tie::File | 4389/4725 | 336/4725 subtests failed | 2026-04-12 | +| URI::Find | 617/619 | 2/619 subtests failed | 2026-04-12 | +| VM::CloudAtCost | 0/1 | 1/1 subtests failed | 2026-04-12 | +| WebService::ChatWorkApi | 4/14 | 10/14 subtests failed | 2026-04-12 | +| XML::SAX | 105/109 | 4/109 subtests failed | 2026-04-12 | +| XML::Writer | 267/273 | 6/273 subtests failed | 2026-04-12 | +| YAML::XS | 0/2 | 48/2 subtests failed | 2026-04-12 | +| strictures | 4/5 | 1/5 subtests failed | 2026-04-12 | + +## How to Reproduce + +```bash +# Test 10 random modules (default — uses jcpan -t) +perl dev/tools/cpan_random_tester.pl + +# Test more modules +perl dev/tools/cpan_random_tester.pl --count 50 + +# Install mode (deps stay installed for future runs) +perl dev/tools/cpan_random_tester.pl --install --count 20 + +# Regenerate this report from existing data +perl dev/tools/cpan_random_tester.pl --report-only + +# Reproducible random seed +perl dev/tools/cpan_random_tester.pl --seed 42 --count 20 +``` + +## Data Files + +- `dev/cpan-reports/cpan-compatibility-pass.dat` — Pass list (TSV, includes git commit) +- `dev/cpan-reports/cpan-compatibility-fail.dat` — Fail list (TSV) +- `dev/cpan-reports/cpan-compatibility-skip.dat` — Skip list (TSV) +- `/tmp/cpan_random_logs/.log` — Per-module test output diff --git a/dev/prompts/cpan-compatibility-testing.md b/dev/prompts/cpan-compatibility-testing.md new file mode 100644 index 000000000..c3a6cd631 --- /dev/null +++ b/dev/prompts/cpan-compatibility-testing.md @@ -0,0 +1,128 @@ +# CPAN Compatibility Testing + +## Goal + +Maintain a continuously updated report of which CPAN modules pass all tests on PerlOnJava. This helps: +- Track compatibility progress over time +- Identify common failure patterns to prioritize fixes +- Give users confidence about which modules work +- Bisect regressions (PASS results record the git commit hash) + +## Quick Start + +```bash +# Build PerlOnJava first +make dev + +# Test 10 random CPAN modules (uses jcpan -t by default) +perl dev/tools/cpan_random_tester.pl + +# Test a larger batch (e.g., 50 modules) +perl dev/tools/cpan_random_tester.pl --count 50 + +# Install mode (deps stay installed for future runs) +perl dev/tools/cpan_random_tester.pl --install --count 20 + +# Regenerate the Markdown report from existing data +perl dev/tools/cpan_random_tester.pl --report-only +``` + +## How It Works + +1. **Module selection**: Reads the full CPAN index (~43K distributions), picks N random modules that haven't passed yet. Previously-failed modules are re-eligible so they can be retried once their deps get installed. + +2. **Testing**: Each target is run with `./jcpan -t ` (default) which always runs tests even for already-installed modules. Use `--install` to install instead (deps stay for future runs, but already-installed modules are skipped). + +3. **Dependency harvesting**: The script parses the **full** jcpan output and extracts results for **every** module that appears — not just the target, but all its dependencies too. One target often yields 5-20 additional data points. + +4. **Smart updates**: When a module is re-tested: + - FAIL → PASS: the record is **upgraded** (moved from fail.dat to pass.dat) + - PASS stays PASS: date and commit are updated silently + - PASS is never downgraded to FAIL (to protect against flaky tests) + +5. **Git commit tracking**: Every PASS records the git commit hash. If a future run detects a PASS→FAIL regression, you can bisect between the known-good commit and HEAD. + +6. **Crash-safe persistence**: Results are saved to `.dat` files after each target module, so partial runs are never lost. + +7. **Report**: A Markdown report is generated at `dev/cpan-reports/cpan-compatibility.md`. + +## Files + +| File | Purpose | +|------|---------| +| `dev/tools/cpan_random_tester.pl` | Main test script (run with `perl`, not `jperl`) | +| `dev/cpan-reports/cpan-compatibility.md` | Human-readable report | +| `dev/cpan-reports/cpan-compatibility-pass.dat` | Pass list (TSV, includes git commit) | +| `dev/cpan-reports/cpan-compatibility-fail.dat` | Fail list (TSV) | +| `dev/cpan-reports/cpan-compatibility-skip.dat` | Skip list (TSV) | +| `/tmp/cpan_random_logs/` | Per-module test output logs | + +## Workflow for Growing the Report + +### Initial Seeding + +Run a large batch to establish a baseline: + +```bash +perl dev/tools/cpan_random_tester.pl --count 100 --seed 1 +``` + +### Incremental Testing + +Each run adds more modules. Failed modules are automatically retried in future runs (their deps may have been installed since): + +```bash +# Each invocation picks new random targets + retries eligible failures +perl dev/tools/cpan_random_tester.pl --count 20 +``` + +### After a PerlOnJava Improvement + +Just run again. Modules that previously failed will be retried automatically. The script will show `UPGRADE` for any FAIL→PASS transitions: + +```bash +make dev # rebuild +perl dev/tools/cpan_random_tester.pl --count 50 +``` + +### Updating the PR + +After testing, commit the updated report and data files: + +```bash +git add dev/cpan-reports/cpan-compatibility.md +git add dev/cpan-reports/cpan-compatibility-pass.dat +git add dev/cpan-reports/cpan-compatibility-fail.dat +git add dev/cpan-reports/cpan-compatibility-skip.dat +git commit -m "docs: update CPAN compatibility report" +``` + +## Analyzing Failures + +The fail list is categorized by error type in the .md report. Common patterns: + +- **Missing Dependencies**: Module needs another module that PerlOnJava can't install. Fixing the dep often unlocks many modules downstream. +- **Test Failures**: Module installs but some tests fail. Best candidates for targeted fixes (often close to passing). +- **Configure Failed**: `Makefile.PL` failed. Often due to XS detection or parser issues. +- **Timeout**: Module takes too long (>300s). May indicate infinite loops. +- **Stack/Memory**: JVM resource limits hit. Try `JPERL_OPTS="-Xss256m -Xmx2g"`. +- **Syntax Error**: PerlOnJava parser limitation. File a bug or check existing issues. + +## Regression Bisecting + +When a module transitions from PASS to FAIL: + +1. Find the last-known-good commit in `cpan-compatibility-pass.dat` (8th column) +2. Bisect: + ```bash + git bisect start HEAD + git bisect run sh -c './jcpan -t Some::Module 2>&1 | grep -q "Result: PASS"' + ``` + +## Tips + +- Run with `perl` (not `jperl`) since the script uses `fork` and backticks. +- The CPAN index must exist at `~/.cpan/sources/modules/02packages.details.txt.gz`. Run `./jcpan` once interactively if it's missing. +- Use `--seed N` for reproducible random selection. +- Logs for individual modules are in `/tmp/cpan_random_logs/.log`. +- The existing `dev/tools/cpan_smoke_test.pl` tests a curated list of known modules. This script complements it by discovering new compatible modules from the full CPAN index. diff --git a/dev/tools/cpan_random_tester.pl b/dev/tools/cpan_random_tester.pl new file mode 100644 index 000000000..77be35857 --- /dev/null +++ b/dev/tools/cpan_random_tester.pl @@ -0,0 +1,824 @@ +#!/usr/bin/env perl +# +# cpan_random_tester.pl - Random CPAN Module Tester for PerlOnJava +# +# Picks random modules from the CPAN index, installs them with jcpan +# (which also installs and tests dependencies), and maintains a +# persistent Markdown report of pass/fail results. +# +# Key features: +# - Every module tested during a run (including dependencies) is +# recorded separately, so each run yields many data points. +# - Results are updated on re-test: if a dep that previously failed +# now passes (because its own deps got installed), the record moves +# from FAIL → PASS. +# - PASS results record the git commit hash so that PASS→FAIL +# regressions can be bisected. +# +# Usage: +# perl dev/tools/cpan_random_tester.pl # Test 10 random modules +# perl dev/tools/cpan_random_tester.pl --count 50 # Test 50 random modules +# perl dev/tools/cpan_random_tester.pl --report-only # Regenerate .md from .dat +# perl dev/tools/cpan_random_tester.pl --timeout 120 # 2 min timeout per module +# perl dev/tools/cpan_random_tester.pl --install # Install mode (deps stay) +# +# Output: +# - dev/cpan-reports/cpan-compatibility.md (human-readable report) +# - dev/cpan-reports/cpan-compatibility-pass.dat (machine-readable pass list) +# - dev/cpan-reports/cpan-compatibility-fail.dat (machine-readable fail list) +# - dev/cpan-reports/cpan-compatibility-skip.dat (machine-readable skip list) +# - Per-module logs to /tmp/cpan_random_logs/ +# +# Run with `perl` (not jperl) because this script uses fork and backticks. +# +# Prerequisites: +# - Build PerlOnJava first: make dev +# - CPAN index must exist: ~/.cpan/sources/modules/02packages.details.txt.gz +# (run `./jcpan` once interactively if missing) + +use strict; +use warnings; +$| = 1; # autoflush STDOUT so progress is visible when redirected to a file +use File::Basename; +use File::Spec; +use File::Path qw(make_path); +use Getopt::Long; +use POSIX qw(strftime WNOHANG); + +# ────────────────────────────────────────────────────────────────────── +# Paths +# ────────────────────────────────────────────────────────────────────── +my $script_dir = dirname(File::Spec->rel2abs($0)); +my $project_root = File::Spec->catdir($script_dir, '..', '..'); +my $jcpan = File::Spec->catfile($project_root, 'jcpan'); +my $report_dir = File::Spec->catdir($project_root, 'dev', 'cpan-reports'); +my $report_md = File::Spec->catfile($report_dir, 'cpan-compatibility.md'); +my $pass_dat = File::Spec->catfile($report_dir, 'cpan-compatibility-pass.dat'); +my $fail_dat = File::Spec->catfile($report_dir, 'cpan-compatibility-fail.dat'); +my $skip_dat = File::Spec->catfile($report_dir, 'cpan-compatibility-skip.dat'); +my $log_dir = '/tmp/cpan_random_logs'; + +# CPAN package index +my $packages_gz = glob('~/.cpan/sources/modules/02packages.details.txt.gz'); + +# ────────────────────────────────────────────────────────────────────── +# CLI options +# ────────────────────────────────────────────────────────────────────── +my $count = 10; +my $timeout = 300; +my $report_only = 0; +my $install = 0; # --install: use jcpan (install) instead of jcpan -t +my $help = 0; +my $seed; + +GetOptions( + 'count|n=i' => \$count, + 'timeout=i' => \$timeout, + 'report-only' => \$report_only, + 'install' => \$install, + 'seed=i' => \$seed, + 'help|h' => \$help, +) or die "Error in command line arguments\n"; + +if ($help) { + print_usage(); + exit 0; +} + +# ────────────────────────────────────────────────────────────────────── +# Setup +# ────────────────────────────────────────────────────────────────────── +make_path($report_dir) unless -d $report_dir; +make_path($log_dir) unless -d $log_dir; + +srand($seed) if defined $seed; + +# Get current git commit hash (recorded with PASS results for bisecting) +my $git_commit = `git -C '$project_root' rev-parse --short HEAD 2>/dev/null`; +chomp $git_commit; +$git_commit ||= 'unknown'; + +# Load existing results +my %pass_modules = load_dat($pass_dat); +my %fail_modules = load_dat($fail_dat); +my %skip_modules = load_dat($skip_dat); + +if ($report_only) { + generate_report(); + print "Report updated: $report_md\n"; + exit 0; +} + +# ────────────────────────────────────────────────────────────────────── +# Load CPAN index and extract distribution-level module names +# ────────────────────────────────────────────────────────────────────── +print "Loading CPAN package index...\n"; +die "CPAN index not found at $packages_gz\nRun ./jcpan once to download it.\n" + unless -f $packages_gz; + +my %dist_to_module; +my %module_to_dist; + +open my $gz, '-|', "gzcat '$packages_gz'" or die "Cannot read $packages_gz: $!\n"; +while (<$gz>) { + next if /^\s*$/ || /^[A-Z][a-z-]+:/ || /^\s/; # skip header + chomp; + my ($module, $version, $dist) = split /\s+/, $_, 3; + next unless $module && $dist; + + next if $module =~ /^[a-z]/; # pragmas + next if $module =~ /^perl$/i; + next if $module =~ /^Acme::/; # joke modules + next if $module =~ /^Bundle::/; + next if $module =~ /^Task::/; + + $module_to_dist{$module} = $dist; + $dist_to_module{$dist} //= $module; +} +close $gz; + +my @all_modules = sort values %dist_to_module; +printf "Loaded %d unique distributions (%d total packages)\n", + scalar @all_modules, scalar keys %module_to_dist; + +# Remove already-tested modules (only PASS — re-test FAILs in case deps are now available) +my @candidates; +for my $mod (@all_modules) { + next if $pass_modules{$mod}; + next if $skip_modules{$mod}; + push @candidates, $mod; +} +printf "Candidates (not yet passing): %d\n", scalar @candidates; + +if (!@candidates) { + print "All modules have been tested! Use --report-only to regenerate the report.\n"; + generate_report(); + exit 0; +} + +# ────────────────────────────────────────────────────────────────────── +# Randomly select modules to test +# ────────────────────────────────────────────────────────────────────── +my @selected; +if ($count >= scalar @candidates) { + @selected = @candidates; +} else { + my @pool = @candidates; + for my $i (0 .. $count - 1) { + my $j = $i + int(rand(scalar(@pool) - $i)); + @pool[$i, $j] = @pool[$j, $i]; + } + @selected = @pool[0 .. $count - 1]; +} +@selected = sort @selected; + +printf "\nTesting %d randomly selected modules (timeout: %ds, commit: %s):\n", + scalar @selected, $timeout, $git_commit; +print "=" x 70, "\n\n"; + +# ────────────────────────────────────────────────────────────────────── +# Test each module and harvest results for all deps too +# ────────────────────────────────────────────────────────────────────── +my $target_count = 0; +my $new_pass = 0; +my $new_fail = 0; +my $upgraded = 0; # FAIL→PASS transitions + +for my $module (@selected) { + $target_count++; + my $mode = $install ? '' : '-t'; + printf "[%d/%d] jcpan %s %s\n", $target_count, scalar @selected, $mode, $module; + + my $start = time(); + my $cmd = $install ? "$jcpan $module" : "$jcpan -t $module"; + + my ($output, $timed_out) = run_with_timeout($cmd, $timeout); + + my $elapsed = sprintf('%.1f', time() - $start); + + save_log($module, $output); + + # Parse ALL module results from the output (target + deps) + my @all_results = parse_all_module_results($output); + + # If nothing parsed, check for special cases before recording failure + if (!@all_results) { + if ($timed_out) { + push @all_results, { + module => $module, status => 'FAIL', + tests => undef, pass_count => undef, + error => "TIMEOUT (>${timeout}s)", + }; + } elsif ($output =~ /\Q$module\E is up to date/) { + # Already installed, jcpan skipped it — not a failure + printf " (already installed, skipped)\n\n"; + next; + } else { + # Check for PerlOnJava-specific errors in the raw output + my $error = 'No parseable output'; + if ($output =~ /Too many registers/) { + $error = 'PerlOnJava: register limit exceeded'; + } elsif ($output =~ /StackOverflowError/) { + $error = 'StackOverflowError'; + } elsif ($output =~ /OutOfMemoryError/) { + $error = 'OutOfMemoryError'; + } elsif ($output =~ /Can't locate (\S+\.pm)/m) { + $error = "Missing: $1"; + } elsif ($output =~ /Syntax error[^\n]*/mi) { + $error = 'Syntax error'; + } + push @all_results, { + module => $module, status => 'FAIL', + tests => undef, pass_count => undef, + error => $error, + }; + } + } + + # Record each discovered module + for my $r (@all_results) { + my $mod = $r->{module}; + next unless $mod; + + $r->{date} = strftime('%Y-%m-%d', localtime); + + if ($r->{status} eq 'PASS') { + $r->{git_commit} = $git_commit; + + # Was it previously a FAIL? That's an upgrade. + if ($fail_modules{$mod}) { + delete $fail_modules{$mod}; + $upgraded++; + printf " ^ UPGRADE %-38s FAIL -> PASS", $mod; + } elsif ($pass_modules{$mod}) { + # Already known PASS — update date/commit silently + $pass_modules{$mod} = $r; + next; + } else { + $new_pass++; + printf " + PASS %-38s", $mod; + } + printf " (%s subtests)", $r->{tests} if $r->{tests}; + print "\n"; + $pass_modules{$mod} = $r; + + } elsif ($r->{status} eq 'SKIP') { + next if $skip_modules{$mod}; + $skip_modules{$mod} = $r; + printf " - SKIP %-38s (%s)\n", $mod, $r->{reason} // ''; + + } else { + # Don't downgrade a PASS to FAIL (would need --retest-pass) + next if $pass_modules{$mod}; + # Already a known FAIL — update silently + if ($fail_modules{$mod}) { + $fail_modules{$mod} = $r; + next; + } + $new_fail++; + $fail_modules{$mod} = $r; + printf " - FAIL %-38s", $mod; + printf " (%s/%s)", $r->{pass_count} // '?', $r->{tests} if $r->{tests}; + if ($r->{error}) { + my $err = $r->{error}; + $err = substr($err, 0, 45) . '...' if length($err) > 48; + printf " [%s]", $err; + } + print "\n"; + } + } + + printf " (%ss, %d modules in output)\n\n", $elapsed, scalar @all_results; + + # Save after each target (crash-safe) + save_dat($pass_dat, \%pass_modules); + save_dat($fail_dat, \%fail_modules); + save_dat($skip_dat, \%skip_modules); + generate_report(); # keep .md in sync with .dat files +} + +# ────────────────────────────────────────────────────────────────────── +# Summary +# ────────────────────────────────────────────────────────────────────── +print "=" x 70, "\n"; +printf "This run: %d targets | +%d pass | +%d fail | %d upgraded (FAIL->PASS)\n", + $target_count, $new_pass, $new_fail, $upgraded; +printf "Cumulative: %d pass | %d fail | %d skip | %d total\n", + scalar keys %pass_modules, scalar keys %fail_modules, + scalar keys %skip_modules, + scalar(keys %pass_modules) + scalar(keys %fail_modules) + scalar(keys %skip_modules); + +generate_report(); +print "\nReport: $report_md\n"; +print "Logs: $log_dir/\n"; + + +# ══════════════════════════════════════════════════════════════════════ +# Output parser — extracts per-module results from full jcpan output +# +# CPAN.pm output is NOT contiguous per module: it starts installing +# module A, discovers a dep B, switches to B, finishes B, then comes +# back to A. So we use two strategies: +# +# 1) "Running make test for AUTHOR/Dist-Name-Ver.tar.gz" blocks — +# these ARE contiguous and contain Files=/Result:/make test lines. +# We map dist-name back to the module name. +# +# 2) "Running install for module 'Foo::Bar'" + configure failures — +# catches modules that never reached the test phase. +# ══════════════════════════════════════════════════════════════════════ + +sub parse_all_module_results { + my ($output) = @_; + my @results; + my %seen; # module => 1, to avoid duplicates + + # --- Pass 1: map dist paths to module names --- + # "Running install for module 'Foo::Bar'" is followed later by + # "Configuring A/AU/AUTHOR/Foo-Bar-1.0.tar.gz with ..." + my %dist_to_mod; # "Foo-Bar-1.0" => "Foo::Bar" + my $last_mod; + for my $line (split /\n/, $output) { + if ($line =~ /Running (?:test|install) for module '([^']+)'/) { + $last_mod = $1; + } + # "Configuring A/AU/AUTHOR/Dist-Name-1.0.tar.gz with ..." + if ($last_mod && $line =~ m{Configuring \S+/(\S+)\.tar\.gz}) { + $dist_to_mod{$1} = $last_mod; + } + } + + # --- Pass 2: parse "Running make test for ..." blocks --- + # These are contiguous and contain the actual test results. + # Format: "Running make test for AUTHOR/Dist-Name-1.0.tar.gz" + # (also: "Running Build test for ...") + my @test_blocks; + { + my $cur_dist; + my $cur_text = ''; + for my $line (split /\n/, $output) { + if ($line =~ m{Running (?:make|Build) test for \S+/(\S+)\.tar\.gz}) { + if ($cur_dist) { + push @test_blocks, { dist => $cur_dist, text => $cur_text }; + } + $cur_dist = $1; + $cur_text = "$line\n"; + } elsif ($cur_dist) { + $cur_text .= "$line\n"; + # End block on the definitive "make test -- OK/NOT OK" line + if ($line =~ /(?:make|Build) test -- (?:OK|NOT OK)/) { + push @test_blocks, { dist => $cur_dist, text => $cur_text }; + $cur_dist = undef; + $cur_text = ''; + } + } + } + # Save final block if still open + if ($cur_dist) { + push @test_blocks, { dist => $cur_dist, text => $cur_text }; + } + } + + for my $block (@test_blocks) { + my $dist = $block->{dist}; + my $text = $block->{text}; + my $mod = $dist_to_mod{$dist}; + + # If we couldn't map dist→module, derive from dist name + unless ($mod) { + ($mod = $dist) =~ s/-[\d.]+$//; # strip version + $mod =~ s/-/::/g; # Foo-Bar → Foo::Bar + } + next if $seen{$mod}++; + + my %r = ( + module => $mod, + status => 'UNKNOWN', + tests => undef, + pass_count => undef, + error => '', + reason => '', + ); + + my $total_tests = 0; + my $subtests_fail = 0; + + if ($text =~ /Files=\d+, Tests=(\d+)/) { + $total_tests = $1; + } + while ($text =~ /Failed\s+(\d+)\/(\d+)\s+subtests/g) { + $subtests_fail += $1; + } + + if ($text =~ /All tests successful/ || $text =~ /Result: PASS/) { + $r{status} = 'PASS'; + $r{tests} = $total_tests || undef; + $r{pass_count} = $total_tests || undef; + push @results, \%r; + next; + } + + if ($text =~ /Result: FAIL/ || $text =~ /(?:make|Build) test -- NOT OK/) { + $r{status} = 'FAIL'; + if ($total_tests > 0) { + $r{tests} = $total_tests; + $r{pass_count} = $total_tests > $subtests_fail + ? $total_tests - $subtests_fail : 0; + $r{error} = sprintf('%d/%d subtests failed', + $subtests_fail, $total_tests) if $subtests_fail; + } + if (!$r{error}) { + if ($text =~ /Can't locate (\S+\.pm)/m) { + $r{error} = "Missing: $1"; + } elsif ($text =~ /StackOverflowError/) { + $r{error} = 'StackOverflowError'; + } elsif ($text =~ /OutOfMemoryError/) { + $r{error} = 'OutOfMemoryError'; + } elsif ($text =~ /syntax error/i) { + $r{error} = 'Syntax error'; + } + } + push @results, \%r; + next; + } + + # Fallback + $r{status} = 'FAIL'; + $r{error} = 'Unknown test outcome'; + push @results, \%r; + } + + # --- Pass 3: catch modules that never reached the test phase --- + # (configure failures, build failures, etc.) + for my $line (split /\n/, $output) { + if ($line =~ /Running (?:test|install) for module '([^']+)'/) { + $last_mod = $1; + } + + # Configure failed + if ($last_mod && !$seen{$last_mod} + && $line =~ /(?:Makefile\.PL|Build\.PL) -- NOT OK/) { + $seen{$last_mod}++; + my %r = ( + module => $last_mod, status => 'FAIL', + tests => undef, pass_count => undef, + error => 'Configure failed', reason => '', + ); + push @results, \%r; + } + + # Build failed (not test — that's caught in Pass 2) + if ($last_mod && !$seen{$last_mod} + && $line =~ /(?:jperl|perl) Build -- NOT OK/) { + $seen{$last_mod}++; + my %r = ( + module => $last_mod, status => 'FAIL', + tests => undef, pass_count => undef, + error => 'Build failed', reason => '', + ); + push @results, \%r; + } + } + + return @results; +} + + +# ══════════════════════════════════════════════════════════════════════ +# Helpers +# ══════════════════════════════════════════════════════════════════════ + +# Run a command with a hard timeout. On timeout, kills the entire +# process group so no orphaned jperl/java children survive. +# Returns ($output, $timed_out). +# +# Strategy (borrowed from perl_test_runner.pl): +# 1. Prefer external `timeout` / `gtimeout` — they send SIGTERM to the +# process group and handle cleanup natively. +# 2. Fallback: fork + setpgrp + kill(-$pid) for platforms without +# coreutils. +sub run_with_timeout { + my ($cmd, $secs) = @_; + + # --- Strategy 1: external timeout command --- + my $timeout_cmd = _find_timeout_cmd(); + if ($timeout_cmd) { + my $full = "$timeout_cmd ${secs}s $cmd 2>&1"; + my $output = `$full`; + my $exit_code = $? >> 8; + my $timed_out = ($exit_code == 124); # timeout exits 124 + return ($output // '', $timed_out); + } + + # --- Strategy 2: fork + process-group kill --- + my $output = ''; + my $timed_out = 0; + + my $pid = open my $pipe, '-|'; + if (!defined $pid) { + warn "fork failed: $!\n"; + return ('', 0); + } + + if ($pid == 0) { + # Child: run in its own process group so kill(-pid) reaches + # the entire tree (jcpan → jperl → java, make, etc.) + setpgrp(0, 0); + open STDERR, '>&', \*STDOUT; + exec('/bin/sh', '-c', $cmd); + exit 127; + } + + # Parent: read with alarm timeout + eval { + local $SIG{ALRM} = sub { die "TIMEOUT\n" }; + alarm($secs); + local $/; + $output = <$pipe>; + alarm(0); + }; + + if ($@ && $@ =~ /TIMEOUT/) { + $timed_out = 1; + # Kill the entire process group (negative PID) + kill 'TERM', -$pid; + # Give children a moment to exit, then force-kill + my $reaped = 0; + for (1..10) { + if (waitpid($pid, WNOHANG) > 0) { + $reaped = 1; + last; + } + select(undef, undef, undef, 0.2); + } + unless ($reaped) { + kill 'KILL', -$pid; + waitpid($pid, 0); + } + } + + close $pipe; # always close to avoid FD leak + # Reap child if not already reaped (close may have done it, but + # waitpid on an already-reaped pid is harmless) + waitpid($pid, WNOHANG) unless $timed_out; + + return ($output // '', $timed_out); +} + +{ + my $_timeout_cmd; # cached result (undef = not checked yet) + my $_checked = 0; + + sub _find_timeout_cmd { + return $_timeout_cmd if $_checked; + $_checked = 1; + for my $candidate (qw(timeout gtimeout)) { + if (system("which $candidate >/dev/null 2>&1") == 0) { + $_timeout_cmd = $candidate; + return $_timeout_cmd; + } + } + return undef; + } +} + +sub save_log { + my ($module, $output) = @_; + (my $safe = $module) =~ s/::/-/g; + my $path = "$log_dir/${safe}.log"; + if (open my $fh, '>', $path) { + print $fh $output; + close $fh; + } +} + +# ────────────────────────────────────────────────────────────────────── +# Persistent .dat file I/O +# Format: modulestatustestspass_counterrordatereasongit_commit +# ────────────────────────────────────────────────────────────────────── +sub load_dat { + my ($file) = @_; + my %data; + return %data unless -f $file; + open my $fh, '<', $file or return %data; + while (<$fh>) { + chomp; + my ($mod, $status, $tests, $pass, $error, $date, $reason, $commit) + = split /\t/, $_, 8; + next unless $mod; + $data{$mod} = { + module => $mod, + status => $status // 'UNKNOWN', + tests => (defined $tests && $tests ne '') ? $tests : undef, + pass_count => (defined $pass && $pass ne '') ? $pass : undef, + error => $error // '', + date => $date // '', + reason => $reason // '', + git_commit => $commit // '', + }; + } + close $fh; + return %data; +} + +sub save_dat { + my ($file, $data) = @_; + open my $fh, '>', $file or die "Cannot write $file: $!\n"; + for my $mod (sort keys %$data) { + my $r = $data->{$mod}; + printf $fh "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + $mod, + $r->{status} // '', + $r->{tests} // '', + $r->{pass_count} // '', + $r->{error} // '', + $r->{date} // '', + $r->{reason} // '', + $r->{git_commit} // ''; + } + close $fh; +} + +# ────────────────────────────────────────────────────────────────────── +# Markdown report generator +# ────────────────────────────────────────────────────────────────────── +sub generate_report { + my $now = strftime('%Y-%m-%d %H:%M:%S', localtime); + my $total_pass = scalar keys %pass_modules; + my $total_fail = scalar keys %fail_modules; + my $total_skip = scalar keys %skip_modules; + my $total = $total_pass + $total_fail + $total_skip; + my $pass_pct = $total > 0 ? sprintf('%.1f', $total_pass / $total * 100) : '0.0'; + + open my $fh, '>', $report_md or die "Cannot write $report_md: $!\n"; + + print $fh < Auto-generated by `dev/tools/cpan_random_tester.pl` on $now +> +> Modules are randomly selected from the full CPAN index and tested +> with `./jcpan -t`. Dependencies are tested too; every module that +> appears in the output is recorded. + +## Summary + +| Metric | Count | +|--------|-------| +| **Modules Tested** | $total | +| **Pass** | $total_pass ($pass_pct%) | +| **Fail** | $total_fail | +| **Skipped (XS-only)** | $total_skip | + +HEADER + + # ── Pass list ── + print $fh "## Modules That Pass All Tests\n\n"; + if ($total_pass > 0) { + print $fh "| Module | Subtests | Date | Git Commit |\n"; + print $fh "|--------|----------|------|------------|\n"; + for my $mod (sort keys %pass_modules) { + my $r = $pass_modules{$mod}; + my $tests = defined $r->{tests} ? $r->{tests} : '-'; + my $date = $r->{date} // ''; + my $commit = $r->{git_commit} // ''; + print $fh "| $mod | $tests | $date | $commit |\n"; + } + } else { + print $fh "_No modules have passed yet._\n"; + } + print $fh "\n"; + + # ── Fail list grouped by error type ── + print $fh "## Modules That Fail Tests\n\n"; + if ($total_fail > 0) { + my %by_error; + for my $mod (sort keys %fail_modules) { + my $r = $fail_modules{$mod}; + my $cat = categorize_error($r); + push @{$by_error{$cat}}, $r; + } + + for my $cat (sort keys %by_error) { + my @mods = @{$by_error{$cat}}; + printf $fh "### %s (%d modules)\n\n", $cat, scalar @mods; + print $fh "| Module | Pass/Total | Error | Date |\n"; + print $fh "|--------|-----------|-------|------|\n"; + for my $r (sort { $a->{module} cmp $b->{module} } @mods) { + my $tests = ''; + if (defined $r->{tests} && $r->{tests} > 0) { + $tests = ($r->{pass_count} // '?') . '/' . $r->{tests}; + } + my $error = $r->{error} // ''; + $error =~ s/\|/\\|/g; + my $date = $r->{date} // ''; + print $fh "| $r->{module} | $tests | $error | $date |\n"; + } + print $fh "\n"; + } + } else { + print $fh "_No failures recorded yet._\n"; + } + + # ── Skip list ── + if ($total_skip > 0) { + print $fh "## Skipped Modules (XS-only)\n\n"; + print $fh "These modules require XS/C extensions and cannot work with PerlOnJava "; + print $fh "unless a Java backend is implemented.\n\n"; + print $fh "| Module | Reason | Date |\n"; + print $fh "|--------|--------|------|\n"; + for my $mod (sort keys %skip_modules) { + my $r = $skip_modules{$mod}; + print $fh "| $mod | $r->{reason} | $r->{date} |\n"; + } + print $fh "\n"; + } + + print $fh <.log` — Per-module test output +FOOTER + + close $fh; +} + +sub categorize_error { + my ($r) = @_; + my $err = $r->{error} // ''; + + return 'Timeout' if $err =~ /TIMEOUT/i; + return 'Missing Dependencies' if $err =~ /Missing|Can't locate/i; + return 'Configure Failed' if $err =~ /Configure/i; + return 'Stack/Memory' if $err =~ /StackOverflow|OutOfMemory/i; + return 'PerlOnJava Limits' if $err =~ /register limit/i; + return 'Syntax Error' if $err =~ /Syntax error/i; + return 'Test Failures' if $err =~ /subtests failed/i + || (defined $r->{tests} && $r->{tests} > 0); + return 'Other'; +} + +sub print_usage { + print <<'USAGE'; +cpan_random_tester.pl - Random CPAN Module Tester for PerlOnJava + +Usage: + perl dev/tools/cpan_random_tester.pl [options] + +Options: + --count N, -n N Number of random target modules to test (default: 10) + Dependencies are tested too, so actual module count is higher. + --timeout N Timeout per target module in seconds (default: 300) + --install Use jcpan (install) instead of jcpan -t (test only). + Deps stay installed for future runs, but already-installed + modules are skipped (no re-test). + --report-only Regenerate .md report from existing .dat files + --seed N Random seed for reproducible module selection + --help Show this help + +Behavior: + - Default: uses jcpan -t (always runs tests, even for installed modules). + - Targets are randomly chosen from modules that haven't passed yet. + - Dependencies discovered during a run are recorded too (PASS/FAIL). + - If a previously-failed module now passes (e.g., its deps got + installed), the record is upgraded from FAIL to PASS. + - PASS results include the git commit hash for regression bisecting. + - Results accumulate across runs (never discarded). + +Examples: + perl dev/tools/cpan_random_tester.pl # 10 targets + perl dev/tools/cpan_random_tester.pl --count 50 # 50 targets + perl dev/tools/cpan_random_tester.pl --seed 42 -n 20 # reproducible + perl dev/tools/cpan_random_tester.pl --report-only # regen report + +Output: + dev/cpan-reports/cpan-compatibility.md Markdown report + dev/cpan-reports/cpan-compatibility-pass.dat Pass list (TSV) + dev/cpan-reports/cpan-compatibility-fail.dat Fail list (TSV) + dev/cpan-reports/cpan-compatibility-skip.dat Skip list (TSV) + /tmp/cpan_random_logs/ Per-module logs + +USAGE +} diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index e08bd9222..8674dcb14 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "01c6c2342"; + public static final String gitCommitId = "d833a1ecb"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 11 2026 13:32:38"; + public static final String buildTimestamp = "Apr 12 2026 14:02:47"; // Prevent instantiation private Configuration() {