Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dev/cpan-reports/cpan-compatibility-fail.dat
Original file line number Diff line number Diff line change
Expand Up @@ -1706,7 +1706,7 @@ Catmandu::Importer::WoS FAIL TIMEOUT (runtime >300s; last output 5s ago) 2026-
Catmandu::Importer::Zotero FAIL 4 4 subtests failed in test files 2026-08-15
Catmandu::PICAplus FAIL 4 2 subtests failed in test files 2026-08-17
Catmandu::WoS::AuthWSDL FAIL TIMEOUT (runtime >300s; last output 1s ago) 2026-08-15
CayleyDickson FAIL No parseable output 2026-05-11
CayleyDickson FAIL No parseable output 2026-08-17
Cdk FAIL Unknown test outcome 2026-05-18
CellBIS::SQL::Abstract FAIL 40 40 2026-05-17
CentOS::Releases FAIL TIMEOUT (runtime >300s; last output 1s ago) 2026-08-14
Expand Down Expand Up @@ -2544,6 +2544,7 @@ DarkPAN::Compare FAIL TIMEOUT (soft limit 1200s exceeded; no output for 792s)
DarkSky::API FAIL 2026-05-17
Data::Alias FAIL 1 29/33 test programs failed; 0/1 subtests failed 2026-06-18
Data::CEFACT::Unit FAIL 3 1 2/3 subtests failed 2026-05-19
Data::CSel::WrapStruct FAIL TIMEOUT (runtime >300s; last output 30s ago) 2026-08-17
Data::Checker FAIL 15 2/9 test programs failed; 0/15 subtests failed 2026-08-14
Data::Checks FAIL Build failed 2026-08-15
Data::Clean FAIL 10 9 1/10 subtests failed 2026-05-10
Expand Down Expand Up @@ -7961,6 +7962,7 @@ Throwable::SugarFactory FAIL Unknown test outcome 2026-04-21
Thrust FAIL 2026-05-18
TiVo::Calypso FAIL 7 6 1/7 subtests failed 2026-05-12
Tic::Bindings FAIL No parseable output 2026-08-14
Tickit::Async FAIL TIMEOUT (runtime >300s; last output 19s ago) 2026-08-17
Tickit::Widget::Choice FAIL Unknown test outcome 2026-08-13
Tickit::Widget::Decoration FAIL 9 7 2/9 subtests failed; 2/2 test programs failed 2026-08-11
Tickit::Widget::Table FAIL 15 13 2/15 subtests failed; 2/3 test programs failed 2026-08-17
Expand Down
1 change: 1 addition & 0 deletions dev/cpan-reports/cpan-compatibility-pass.dat
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ Class::Util PASS 344 344 2026-06-18 c3e9fa049
Class::Utils PASS 22 22 2026-06-18 c3e9fa049
Class::Virtual PASS 34 34 2026-05-19 b5536d190
Class::WeakSingleton PASS 18 18 2026-08-13 cc69cbb4c
Class::XPath PASS 230 230 2026-08-17 6c5545d16
Class::orMapper PASS 1 1 2026-08-14 424ad3c8f
ClassLoader PASS 5 5 2026-05-12 312b4f902
ClearCase::Region_Cfg_Parser PASS 1 1 2026-05-10 1e95a0902
Expand Down
15 changes: 9 additions & 6 deletions dev/cpan-reports/cpan-compatibility.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CPAN Module Compatibility Report for PerlOnJava

> Auto-generated by `dev/tools/cpan_random_tester.pl` on 2026-08-17 14:02:07
> Auto-generated by `dev/tools/cpan_random_tester.pl` on 2026-08-17 14:15:01
>
> Modules are randomly selected from the full CPAN index and tested
> with `./jcpan -t`. Dependencies are tested too; every module that
Expand All @@ -10,9 +10,9 @@

| Metric | Count |
|--------|-------|
| **Modules Tested** | 14983 |
| **Pass** | 5635 (37.6%) |
| **Fail** | 9246 |
| **Modules Tested** | 14986 |
| **Pass** | 5636 (37.6%) |
| **Fail** | 9248 |
| **Skipped** | 102 |

## Modules That Pass All Tests
Expand Down Expand Up @@ -994,6 +994,7 @@
| Class::Utils | 22 | 2026-06-18 | c3e9fa049 |
| Class::Virtual | 34 | 2026-05-19 | b5536d190 |
| Class::WeakSingleton | 18 | 2026-08-13 | cc69cbb4c |
| Class::XPath | 230 | 2026-08-17 | 6c5545d16 |
| Class::orMapper | 1 | 2026-08-14 | 424ad3c8f |
| ClassLoader | 5 | 2026-05-12 | 312b4f902 |
| ClearCase::Region_Cfg_Parser | 1 | 2026-05-10 | 1e95a0902 |
Expand Down Expand Up @@ -6798,7 +6799,7 @@
| Catmandu::Exporter::HTML | | | 2026-08-15 |
| Catmandu::Exporter::PNX | | | 2026-08-16 |
| Catmandu::Fix::rkd_name | | Unknown test outcome | 2026-08-13 |
| CayleyDickson | | No parseable output | 2026-05-11 |
| CayleyDickson | | No parseable output | 2026-08-17 |
| Cdk | | Unknown test outcome | 2026-05-18 |
| Cfwp::fio | | Unknown test outcome | 2026-08-13 |
| Char::UTF2 | | Unknown test outcome | 2026-08-13 |
Expand Down Expand Up @@ -14376,7 +14377,7 @@
| utf8::all | 0/4 | 40/4 subtests failed | 2026-04-22 |
| warnings::illegalproto | 5/6 | 1/6 subtests failed; 1/4 test programs failed | 2026-06-18 |

### Timeout (557 modules)
### Timeout (559 modules)

| Module | Pass/Total | Error | Date |
|--------|-----------|-------|------|
Expand Down Expand Up @@ -14621,6 +14622,7 @@
| Dallycot | | TIMEOUT (runtime >300s; last output 6s ago) | 2026-08-15 |
| Dancer2::Plugin::Interchange6 | | TIMEOUT (runtime >300s; last output 0s ago) | 2026-08-13 |
| DarkPAN::Compare | | TIMEOUT (soft limit 1200s exceeded; no output for 792s) | 2026-06-14 |
| Data::CSel::WrapStruct | | TIMEOUT (runtime >300s; last output 30s ago) | 2026-08-17 |
| Data::Dmp::Prune | | TIMEOUT (soft limit 120s exceeded; no output for 116s) | 2026-08-14 |
| Data::Image | | TIMEOUT (runtime >300s; last output 1s ago) | 2026-08-15 |
| Data::Object::Role::Throwable | | TIMEOUT (soft limit 1200s exceeded; no output for 601s) | 2026-06-10 |
Expand Down Expand Up @@ -14904,6 +14906,7 @@
| Test::Mountebank | | TIMEOUT (runtime >300s; last output 0s ago) | 2026-08-17 |
| Test::RDF | | TIMEOUT (runtime >5400s; last output 0s ago) | 2026-06-18 |
| Test::WWW::Mechanize::Catalyst::Aux | | TIMEOUT (runtime >300s; last output 3s ago) | 2026-08-15 |
| Tickit::Async | | TIMEOUT (runtime >300s; last output 19s ago) | 2026-08-17 |
| Tie::Amazon::S3 | | TIMEOUT (runtime >300s; last output 5s ago) | 2026-08-11 |
| Time::OlsonTZ::Clustered | | TIMEOUT (soft limit 120s exceeded; no output for 68s) | 2026-08-11 |
| TipJar::MTA | | TIMEOUT (soft limit 120s exceeded; no output for 114s) | 2026-08-13 |
Expand Down
52 changes: 46 additions & 6 deletions dev/design/feature-audit-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,34 @@ The canonical status source is
Other documentation must either agree with it or link to a more specific,
current design or module note.

## Initial Evidence

The first rerunnable probes are under
[`dev/tools/feature-audit/`](../tools/feature-audit/). Native Perl was run
first for every probe, followed by the JVM and interpreter backends. Captured
logs are in `/tmp/feature-audit-*` and are not committed by default.

| Feature | Native Perl | JVM backend | Interpreter backend | Finding |
|---|---|---|---|---|
| Lexical buffered file auto-close | pass | pass | pass | Basic flush behavior passes everywhere. |
| File descriptor closure at lexical scope exit | `reopen=no` | `reopen=no` | `reopen=yes` | Partial support; interpreter backend retains the fd. |
| Smartmatch / `given` / `when` | pass | pass | pass | Matrix claim was stale; promoted to supported. See the [probe](../tools/feature-audit/remaining_semantics.t). |
| Restricted hashes | enforced | not enforced | not enforced | Still unsupported on both backends. |
| DBM round trip | pass | explicit `dbmopen` not implemented | explicit `dbmopen` not implemented | Still unsupported. |
| `fork` returns a child PID | pass | not defined | not defined | Still unsupported; true OS fork remains unavailable. |
| `ops` module loads | pass | module missing | module missing | Still unsupported. |
| Full 11-field `caller` tuple | pass | pass | pass | Key package, filename, line, and subroutine fields pass; exact hint values remain context-dependent. |
| Basic multibyte `seek`/`tell` | pass | pass | pass | Only the basic case is covered; full positioning semantics remain open. |
| Executable regex conditionals | pass | pass | pass | Tested true and false callback branches; other conditional forms remain open. |
| `\X` and `Extended_Pictographic` | pass | pass | pass | Tested representative grapheme and emoji cases; broader Unicode aliases remain open. |
| `postderef_qq` interpolation | pass | pass | pass | Existing `postderef_interpolation.t` passes all four assertions. |
| `unicode_eval` / `evalbytes` | pass | pass | pass | Existing `evalbytes.t` passes all three assertions. |
| Container `refaliasing` | pass | pass | pass | Existing `refaliasing_containers.t` passes both aliasing assertions. |

The exact commands and test names are documented in the probe README. A
passing probe does not promote a broad feature automatically: semantic and
edge-case coverage must be sufficient for the documented claim.

## Audit Method

### 1. Build the inventory
Expand Down Expand Up @@ -166,7 +194,7 @@ documentation-only planning change.

## Progress Tracking

### Current Status: Plan created; audit not started
### Current Status: Initial probe batch complete; broader audit in progress

### Completed Phases

Expand All @@ -175,14 +203,26 @@ documentation-only planning change.
- [x] Documentation synchronization rules defined.
- [x] CPAN compatibility reports identified as dated snapshots rather than
feature-level proof.
- [x] Created rerunnable `Test::More` TAP probes under `dev/tools/feature-audit/`.
- [x] Audited initial file lifecycle, operator, pragma, process, DBM, and
multibyte I/O cases with native Perl and both backends.
- [x] Corrected the stale smartmatch and auto-close matrix claims from the
initial evidence.
- [x] Revalidated existing unit coverage for `postderef_qq`, `unicode_eval`,
and container `refaliasing`; corrected their stale matrix status.
- [x] Validated the full 11-field `caller()` tuple and key subroutine metadata
with a dedicated native/JVM/interpreter probe.

### Next Steps

1. Build the feature-to-probe inventory from the feature matrix.
2. Reconcile stale and duplicate documentation claims.
3. Add and validate minimal probes with system Perl.
4. Run the probes on both PerlOnJava backends.
5. Update the matrix and related documentation from the evidence.
1. Add probes for the remaining language, pragma, overload, regex, debugger,
module, XS, and JSR-223 gaps.
2. Validate full `caller` fields and multibyte `seek`/`tell`/`truncate`
semantics rather than only their basic cases.
3. Reconcile remaining stale and duplicate documentation claims.
4. Update related module and design pages from confirmed evidence.
5. Route genuine backend regressions, such as interpreter fd retention, to
implementation-specific follow-up work.

### Open Questions and Blockers

Expand Down
57 changes: 57 additions & 0 deletions dev/tools/feature-audit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# PerlOnJava Feature Audit Probes

Small, rerunnable probes for validating documented PerlOnJava compatibility
gaps against system Perl and both PerlOnJava execution backends.

These are audit probes, not the project’s conformance-test suite. They are
intentionally focused on one behavior and emit standard TAP through
`Test::More`, so the results can be captured or consumed by existing Perl test
tools.

## Running the probes

Run system Perl first to establish the expected behavior. A passing TAP run
under native Perl is required before treating the probe as an oracle:

```bash
probe=dev/tools/feature-audit/autoclose_scope.t
timeout 60 perl "$probe" > /tmp/feature-audit-perl.txt 2>&1
```

Then run both PerlOnJava backends:

```bash
timeout 60 ./jperl "$probe" > /tmp/feature-audit-jvm.txt 2>&1
timeout 60 ./jperl --interpreter "$probe" > /tmp/feature-audit-interpreter.txt 2>&1
```

Always use a timeout for `jperl`, `jcpan`, and `prove` probes. Capture full
output to files, compare the native result with both backends, and record the
Perl version and platform when behavior is version- or platform-dependent.

## Probes

| Probe | Coverage |
|---|---|
| `autoclose_scope.t` | Buffered write and lexical filehandle scope exit |
| `autoclose_fd.t` | Whether an fd can be reopened after the lexical handle exits |
| `remaining_semantics.t` | Focused checks for smartmatch, restricted hashes, overload, `caller`, `ops`, DBM, `fork`, `dump`, and multibyte file positioning |
| `regex_remaining.t` | Executable conditional expressions, grapheme clusters, and `Extended_Pictographic` |
| `caller_fields.t` | Full 11-field `caller()` tuple and subroutine metadata |

## Interpreting results

- `yes`/matching output across native Perl and both backends means the tested
contract is supported.
- A backend-only mismatch is a backend regression or partial-support finding,
not proof that the whole feature is missing.
- A JVM-specific difference caused by the execution model should be recorded
as a JVM limitation.
- A probe result alone does not justify changing the feature matrix when the
probe only checks syntax or module loading; add a semantic probe first.

New probes must pass under system Perl before their output is used as the
expected-behavior oracle. A failing TAP run under PerlOnJava is useful audit
evidence for a gap; it is not itself a regression test expectation. Store
captured logs outside the repository unless a small, stable result is needed
as permanent evidence in a design document.
20 changes: 20 additions & 0 deletions dev/tools/feature-audit/autoclose_fd.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use strict;
use warnings;
use File::Temp qw(tempfile);
use Test::More tests => 2;

my ($seed, $path) = tempfile(UNLINK => 0);
close $seed or die "close seed: $!";

my $fd;
{
open my $out, '>', $path or die "open write: $!";
$fd = fileno($out);
print {$out} "scope-exit-payload" or die "write: $!";
}

my $dup_ok = open my $dup, ">&$fd";
close $dup if $dup_ok;
unlink $path or die "unlink $path: $!";
ok(defined($fd), 'filehandle received an fd');
ok(!$dup_ok, 'fd cannot be reopened after lexical scope exit');
25 changes: 25 additions & 0 deletions dev/tools/feature-audit/autoclose_scope.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use strict;
use warnings;
use File::Temp qw(tempfile);
use Test::More tests => 2;

my ($seed, $path) = tempfile(UNLINK => 0);
close $seed or die "close seed: $!";

{
open my $out, '>', $path or die "open write: $!";
binmode $out;
print {$out} "scope-exit-payload" or die "write: $!";
# Deliberately do not close or undef $out.
}

open my $in, '<', $path or die "open read: $!";
binmode $in;
local $/;
my $got = <$in> // '';
close $in or die "close read: $!";
unlink $path or die "unlink $path: $!";

diag('bytes=' . length($got));
is(length($got), 18, 'scope-exit file contents are readable');
is($got, 'scope-exit-payload', 'lexical filehandle flushes at scope exit');
13 changes: 13 additions & 0 deletions dev/tools/feature-audit/caller_fields.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use strict;
use warnings;
use Test::More tests => 4;

sub audit_caller_fields {
my @fields = caller(0);
is(scalar(@fields), 11, 'caller returns the full 11-field tuple');
is($fields[0], 'main', 'caller package is preserved');
like($fields[1], qr/caller_fields\.t$/, 'caller filename is preserved');
is($fields[3], 'main::audit_caller_fields', 'caller subroutine name is preserved');
}

audit_caller_fields();
14 changes: 14 additions & 0 deletions dev/tools/feature-audit/regex_remaining.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use strict;
use warnings;
use Test::More tests => 4;

use re 'eval';

my $true_condition = qr/(?(?{ 1 })yes|no)/;
my $false_condition = qr/(?(?{ 0 })yes|no)/;
like('yes', $true_condition, 'executable conditional true branch');
like('no', $false_condition, 'executable conditional false branch');

my $grapheme = "e\x{301}";
is(scalar(() = $grapheme =~ /\X/g), 1, 'extended grapheme cluster');
ok("\x{1F600}" =~ /\p{Extended_Pictographic}/, 'Extended_Pictographic property');
81 changes: 81 additions & 0 deletions dev/tools/feature-audit/remaining_semantics.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use strict;
use warnings;
use File::Temp qw(tempfile tempdir);
use Test::More tests => 9;

sub check {
my ($name, $code) = @_;
my $result = eval $code;
my $error = $@;
diag "$name: $error" if $error ne '';
ok(defined($result) && $result, $name);
}

check('smartmatch_value', q{
use feature 'switch';
my $x = 2; my $matched = 0;
given ($x) { when (2) { $matched = 1 } }
$matched;
});

check('restricted_hash_enforced', q{
require Hash::Util;
my %h = (a => 1);
Hash::Util::lock_keys(\%h, 'a');
eval { $h{b} = 2; 1 } ? 0 : 1;
});

check('overload_increment_value', q{
{ package Audit::Inc;
use overload '++' => sub { $_[0]->{value}++; $_[0] }, '0+' => sub { $_[0]->{value} };
use overload '+' => sub { bless { value => $_[0]->{value} + ($_[1] // 0) }, ref($_[0]) },
'0+' => sub { $_[0]->{value} }, '""' => sub { "$_[0]->{value}" };
sub new { bless { value => $_[1] }, $_[0] }
}
my $x = Audit::Inc->new(1); ++$x; "$x" eq '2';
});

check('caller_extended_count', q{
sub audit_caller_count { my @c = caller(0); scalar(@c) }
audit_caller_count() >= 9;
});

check('dbm_round_trip', q{
my $dir = tempdir(CLEANUP => 1);
my $base = "$dir/audit-db";
my %db;
dbmopen(%db, $base, 0666) or die "dbmopen: $!";
$db{key} = 'value';
dbmclose(%db) or die "dbmclose: $!";
dbmopen(%db, $base, 0666) or die "dbmopen 2: $!";
my $ok = $db{key} eq 'value';
dbmclose(%db);
$ok;
});

check('fork_defined', q{
my $pid = fork();
if (defined $pid && $pid == 0) { exit 0 }
defined $pid;
});

check('dump_statement', q{
eval 'dump';
1;
});

check('multibyte_seek_tell', q{
my ($fh, $path) = tempfile(UNLINK => 1);
binmode $fh, ':encoding(UTF-8)';
print {$fh} "éx";
seek($fh, 0, 0) or die "seek: $!";
my $pos = tell($fh);
defined $pos;
});

check('ops_module_load', q{
require ops;
1;
});

diag "perl_version=$^V";
Loading
Loading