diff --git a/.agents/skills/port-cpan-module/SKILL.md b/.agents/skills/port-cpan-module/SKILL.md index 3bb3684fb..ae1c2f861 100644 --- a/.agents/skills/port-cpan-module/SKILL.md +++ b/.agents/skills/port-cpan-module/SKILL.md @@ -467,6 +467,7 @@ public static RuntimeList myMethod(RuntimeArray args, int ctx) { ### Documentation - [ ] Add POD with AUTHOR and COPYRIGHT sections - [ ] Credit original authors +- [ ] Update `docs/reference/bundled-modules.md` — add the module to the appropriate category table - [ ] Update `docs/about/changelog.md` — add module to "Add modules:" list in the current unreleased version - [ ] Update `docs/reference/feature-matrix.md` — add entry in the appropriate section (Core modules / Non-core modules) with status icon and description - [ ] Update `README.md` if the module is notable enough to mention in the Features list diff --git a/.cognition/skills/debug-exiftool/SKILL.md b/.cognition/skills/debug-exiftool/SKILL.md deleted file mode 100644 index c26796118..000000000 --- a/.cognition/skills/debug-exiftool/SKILL.md +++ /dev/null @@ -1,477 +0,0 @@ ---- -name: debug-exiftool -description: Debug and fix Image::ExifTool test failures in PerlOnJava -argument-hint: "[test-name or test-file]" -triggers: - - user - - model ---- - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -# Debugging Image::ExifTool Tests in PerlOnJava - -You are debugging failures in the Image::ExifTool test suite running under PerlOnJava (a Perl-to-JVM compiler/interpreter). Failures typically stem from missing Perl features or subtle behavior differences in PerlOnJava, not bugs in ExifTool itself. - -## Git Workflow - -**IMPORTANT: Never push directly to master. Always use feature branches and PRs.** - -**IMPORTANT: Always commit or stash changes BEFORE switching branches.** If `git stash pop` has conflicts, uncommitted changes may be lost. - -```bash -git checkout -b fix/exiftool-issue-name -# ... make changes ... -git push origin fix/exiftool-issue-name -gh pr create --title "Fix: description" --body "Details" -``` - -## Project Layout - -- **PerlOnJava source**: `src/main/java/org/perlonjava/` (compiler, bytecode interpreter, runtime) -- **ExifTool distribution**: `Image-ExifTool-13.44/` (unmodified upstream) -- **ExifTool tests**: `Image-ExifTool-13.44/t/*.t` -- **ExifTool test lib**: `Image-ExifTool-13.44/t/TestLib.pm` (exports `check`, `writeCheck`, `writeInfo`, `testCompare`, `binaryCompare`, `testVerbose`, `notOK`, `done`) -- **ExifTool test data**: `Image-ExifTool-13.44/t/images/` (reference images) -- **ExifTool reference output**: `Image-ExifTool-13.44/t/_N.out` (expected tag output per sub-test) -- **PerlOnJava unit tests**: `src/test/resources/unit/*.t` (make suite, 154 tests) -- **Perl5 core tests**: `perl5_t/t/` (Perl 5 compatibility suite, run via `make test-gradle`) -- **Fat JAR**: `target/perlonjava-3.0.0.jar` -- **Launcher script**: `./jperl` (resolves JAR path, sets `$^X`) - -## Building PerlOnJava - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during debugging) | - -```bash -make # Standard build - compiles and runs tests -make dev # Quick build - compiles only, NO tests -``` - -## Running ExifTool Tests - -### Single test -```bash -cd Image-ExifTool-13.44 -java -jar ../target/perlonjava-3.0.0.jar -Ilib t/Writer.t -# Or using the launcher: -cd Image-ExifTool-13.44 -../jperl -Ilib t/Writer.t -``` - -### Single test with timeout (prevents infinite loops) -```bash -cd Image-ExifTool-13.44 -timeout 120 java -jar ../target/perlonjava-3.0.0.jar -Ilib t/XMP.t -``` - -### All ExifTool tests in parallel with summary -```bash -cd Image-ExifTool-13.44 -mkdir -p /tmp/exiftool_results -for t in t/*.t; do - name=$(basename "$t" .t) - ( output=$(timeout 120 java -jar ../target/perlonjava-3.0.0.jar -Ilib "$t" 2>&1) - ec=$? - if [ $ec -eq 124 ]; then echo "$name TIMEOUT" - else - pass=$(echo "$output" | grep -cE '^ok ') - fail=$(echo "$output" | grep -cE '^not ok ') - plan=$(echo "$output" | grep -oE '^1\.\.[0-9]+' | head -1) - planned=${plan#1..} - echo "$name pass=$pass fail=$fail planned=${planned:-?} exit=$ec" - fi - ) > "/tmp/exiftool_results/$name.txt" & -done -wait -echo "=== RESULTS ===" -cat /tmp/exiftool_results/*.txt | sort -echo "=== TOTALS ===" -cat /tmp/exiftool_results/*.txt | awk '{ - for(i=1;i<=NF;i++) { - if($i~/^pass=/) p+=substr($i,6) - if($i~/^fail=/) f+=substr($i,6) - if($i~/^planned=/) { v=substr($i,9); if(v!="?") pl+=v } - } -} END { printf "PASS=%d FAIL=%d PLANNED=%d RATE=%d%%\n", p, f, pl, (pl>0?p*100/pl:0) }' -``` - -### Running Perl5 core tests (e.g. lexsub.t) -```bash -cd perl5_t/t -../../jperl op/lexsub.t -``` - -### Running Perl5 core tests that use subprocess tests -Tests using `run_multiple_progs()` or `fresh_perl_is()` spawn `jperl` as a subprocess. This requires `jperl` to be in PATH: -```bash -# Using the test runner (handles PATH automatically): -perl dev/tools/perl_test_runner.pl perl5_t/t/op/eval.t - -# Manual running (must set PATH): -PATH="/Users/fglock/projects/PerlOnJava2:$PATH" cd perl5_t/t && ../../jperl op/eval.t -``` - -## Comparing with System Perl - -When debugging, compare PerlOnJava output with native Perl to isolate the difference: - -```bash -# Run with system Perl -cd Image-ExifTool-13.44 -perl -Ilib t/Writer.t 2>&1 | grep -E '^(not )?ok ' > /tmp/perl_results.txt - -# Run with PerlOnJava -java -jar ../target/perlonjava-3.0.0.jar -Ilib t/Writer.t 2>&1 | grep -E '^(not )?ok ' > /tmp/jperl_results.txt - -# Diff -diff /tmp/perl_results.txt /tmp/jperl_results.txt -``` - -For individual Perl constructs: -```bash -# System Perl -perl -e 'my @a = (1,2,3); $_ *= 2 foreach @a; print "@a\n"' - -# PerlOnJava -java -jar target/perlonjava-3.0.0.jar -e 'my @a = (1,2,3); $_ *= 2 foreach @a; print "@a\n"' -``` - -For comparing `.failed` output files against `.out` reference files: -```bash -cd Image-ExifTool-13.44 -diff t/Writer_11.out t/Writer_11.failed -``` - -## Environment Variables - -### Compiler/Interpreter Control -| Variable | Effect | -|----------|--------| -| `JPERL_DISABLE_INTERPRETER_FALLBACK=1` | Disable bytecode interpreter fallback for large subs (force JVM compilation only) | -| `JPERL_SHOW_FALLBACK=1` | Print a message when a sub falls back to the bytecode interpreter | -| `JPERL_EVAL_NO_INTERPRETER=1` | Disable interpreter for `eval STRING` (force JVM compilation) | -| `JPERL_SPILL_SLOTS=N` | Set number of JVM spill slots (default 16) | - -### Debugging/Tracing -| Variable | Effect | -|----------|--------| -| `JPERL_ASM_DEBUG=1` | Print JVM bytecode disassembly when ASM frame computation crashes | -| `JPERL_ASM_DEBUG_CLASS=` | Filter ASM debug output to a specific generated class name | -| `JPERL_BYTECODE_SIZE_DEBUG=1` | Print bytecode size for each generated method | -| `JPERL_EVAL_VERBOSE=1` | Verbose error reporting for eval STRING compilation issues | -| `JPERL_EVAL_TRACE=1` | Trace eval STRING execution path (compile, interpret, fallback) | -| `JPERL_IO_DEBUG=1` | Trace file handle open/dup/write operations | -| `JPERL_STDIO_DEBUG=1` | Trace STDOUT/STDERR flush sequencing | -| `JPERL_REQUIRE_DEBUG=1` | Trace `require`/`use` module loading | -| `JPERL_TRACE_CONTROLFLOW=1` | Trace control flow detection (goto, return, last/next/redo safety) | -| `JPERL_DISASSEMBLE=1` | Disassemble generated bytecode (also `--disassemble` CLI flag) | - -### Perl-level -| Variable | Effect | -|----------|--------| -| `JPERL_UNIMPLEMENTED=warn` | Downgrade unimplemented regex features from fatal to warning | - -### Usage with jperl launcher -```bash -# Pass JVM options via JPERL_OPTS -JPERL_OPTS="-Xmx512m" ./jperl script.pl - -# Combine env vars -JPERL_SHOW_FALLBACK=1 JPERL_EVAL_TRACE=1 java -jar target/perlonjava-3.0.0.jar -Ilib t/Writer.t 2>&1 -``` - -## Test File Anatomy - -ExifTool `.t` files follow a common pattern: -```perl -BEGIN { $| = 1; print "1..N\n"; require './t/TestLib.pm'; t::TestLib->import(); } -END { print "not ok 1\n" unless $loaded; } -use Image::ExifTool; -$loaded = 1; - -# Read test: extract tags and compare against t/_N.out -my $exifTool = Image::ExifTool->new; -my $info = $exifTool->ImageInfo('t/images/SomeFile.ext', @tags); -print 'not ' unless check($exifTool, $info, $testname, $testnum); -print "ok $testnum\n"; - -# Write test: modify tags and verify output -writeInfo($exifTool, 'src.jpg', 'tmp/out.jpg', \@setNewValue_args); - -# Binary compare test: verify exact byte-for-byte match -binaryCompare('output.jpg', 't/images/original.jpg'); -``` - -The `check()` function compares extracted tags against reference files `t/_N.out`. Failed tests leave `t/_N.failed` files for comparison. The `writeInfo()` function calls SetNewValue + WriteInfo. - -## Debugging Workflow - -1. **Run the failing test** and capture full output (stdout + stderr). Look for: - - `not ok N` lines (which specific sub-tests fail) - - Runtime exceptions / stack traces from Java - - `Can't locate ...` (missing module) - - `Undefined subroutine` / `Can't call method` errors - -2. **Identify the failing sub-test number** and find it in the `.t` file. Map it to the ExifTool operation (read vs write, which image format, which tags). - -3. **Check the `.out` vs `.failed` files** to understand the difference: - ```bash - diff t/Writer_11.out t/Writer_11.failed - ``` - -4. **Compare with system Perl** to confirm it's a PerlOnJava issue, not a test environment issue. - -5. **Isolate the Perl construct** causing the failure. Write a minimal reproducer: - ```bash - java -jar target/perlonjava-3.0.0.jar -e 'print pos("abc" =~ /b/g), "\n"' - perl -e 'print pos("abc" =~ /b/g), "\n"' - ``` - -6. **Trace into PerlOnJava source** to find the bug. Use `JPERL_SHOW_FALLBACK=1` to check if large subs are hitting the interpreter path. - -7. **Fix in PerlOnJava**, rebuild (`make dev`), re-run the ExifTool test. - -8. **Verify no regressions**: Run `make` (154 unit tests) and check `perl5_t/t/op/lexsub.t` (sensitive to block/sub emission changes). - -## Interpreter Fallback Architecture - -PerlOnJava has two compilation backends: -- **JVM backend** (default): Compiles Perl AST to JVM bytecode via ASM. Fast, but has a ~64KB method size limit. -- **Bytecode interpreter** (fallback): When a subroutine is too large for JVM (>N lines, typically ~500), it's compiled to PerlOnJava's own bytecode and interpreted. This includes `eval STRING` by default. - -Key files for the interpreter: -- `BytecodeCompiler.java` — compiles AST to interpreter bytecode -- `BytecodeInterpreter.java` — executes interpreter bytecode -- `CompileAssignment.java` — assignment compilation for interpreter -- `Opcodes.java` — opcode definitions -- `InterpretedCode.java` — runtime representation of interpreter-compiled code - -**Closure variables** are the main challenge for the interpreter fallback path. There are two distinct mechanisms: - -1. **Inner named subs within the large sub**: These are compiled by SubroutineParser using the JVM compiler (via `compilerSupplier`). They get full closure support through `RETRIEVE_BEGIN_*` opcodes and `VariableCollectorVisitor.java`. - -2. **The large sub itself accessing outer-scope `my` variables**: This is handled by `detectClosureVariables()` in `BytecodeCompiler.java`. It must: - - Use `getAllVisibleVariables()` (TreeMap, sorted by register index) with the **exact same filtering** as `SubroutineParser` (skip `@_`, empty decl, fields, `&` refs) to ensure the capturedVars ordering matches `withCapturedVars()`. - - Register captured variables in the compiler's **symbol table** via `addVariableWithIndex()` so that ALL variable resolution paths find them — not just `visit(IdentifierNode)`. This is critical because `handleHashElementAccess`, `handleArrayElementAccess`, hash slices, array slices, and assignment targets all have their own variable lookup logic that checks the symbol table. - - Reserve registers (bump `nextRegister`) so local `my` declarations don't collide with captured variable registers. - - Scan AST-referenced non-local variables and add them to `capturedVarIndices` for register recycling protection (prevents `getHighestVariableRegister()` from being too low). - -**The runtime flow for captured variables in the interpreter path:** -1. `compileToInterpreter()` creates `BytecodeCompiler`, calls `compiler.compile(ast, ctx)` which runs `detectClosureVariables()` — this sets up `capturedVarIndices` (name→register mapping) used during bytecode generation -2. `compileToInterpreter()` creates placeholder `capturedVars` (all `RuntimeScalar`) -3. `SubroutineParser.withCapturedVars()` **replaces** the placeholder with actual values from `paramList` (built from `getAllVisibleVariables()` with same filtering) -4. At runtime, `BytecodeInterpreter.execute()` copies `capturedVars[i]` to `registers[3+i]` via `System.arraycopy` -5. The compiled bytecode accesses these registers for captured variable reads/writes - -**Key invariant**: The ordering of variables in `detectClosureVariables()` MUST match `SubroutineParser`'s `paramList` ordering, because `capturedVars[i]` is copied to register `3+i` and the bytecode was compiled expecting specific variables at specific registers. - -## Common Failure Patterns - -### Infinite loops / TIMEOUT -- Often caused by `return` inside a block refactored by `LargeBlockRefactorer` into `sub { ... }->(@_)`. The `return` exits the anonymous sub instead of the enclosing function. -- Can also be caused by regex catastrophic backtracking. -- Use `timeout 120` to prevent hangs; `JPERL_SHOW_FALLBACK=1` to see if interpreter fallback is involved. - -### Missing mandatory EXIF tags on write -- When creating EXIF, mandatory tags (YCbCrPositioning, ExifVersion, ComponentsConfiguration, ColorSpace) should be auto-created by `WriteExif.pl` using `%mandatory` hash. -- If these are missing, check that `%mandatory` is accessible (closure variable issue in interpreter fallback). - -### Closure variable inaccessibility in interpreter -- File-scope `my %hash` / `my @array` not accessible inside large subs compiled by interpreter. -- Symptoms: tags silently missing from output, no error messages. Hash lookups return undef instead of the expected values. -- **Root cause pattern**: The bytecode compiler has MULTIPLE variable resolution paths (`visit(IdentifierNode)`, `handleHashElementAccess`, `handleArrayElementAccess`, hash/array slices, assignment LHS). If captured variables are only in `capturedVarIndices` but NOT in the compiler's symbol table, most access paths won't find them and fall through to global variable load (which returns an empty hash/array). -- **Fix**: `detectClosureVariables()` must call `symbolTable.addVariableWithIndex()` for each captured variable so all resolution paths find them. -- **Debugging**: Add `System.err.println` in `BytecodeInterpreter.execute()` after the `System.arraycopy` for capturedVars to verify the correct values are being passed at runtime. Also check the `handleHashElementAccess` code path to see if it reaches `LOAD_GLOBAL_HASH` (bad) vs `getVariableRegister` (good). - -### XMP lang-alt writing failures -- Non-default language entries (`en`, `de`, `fr`) fail to be created in lang-alt lists. -- Related to `WriteXMP.pl` path tracking using `pos()` after `m//g` regex. - -### pos() behavior after m//g -- `pos()` returning wrong value after global regex match can cause index tracking bugs in ExifTool's write logic. - -### Foreach loop variable aliasing -- Postfix foreach (`EXPR foreach @list`) must alias `$_` to actual array elements for modification. -- Block-form and statement-modifier foreach have different code paths in `StatementParser.java` vs `StatementResolver.java`. - -### Encoding / binary data issues -- ExifTool heavily uses `binmode`, `sysread`, `syswrite`, `pack`, `unpack`, `Encode::decode`/`encode`. -- BYTE_STRING vs STRING type propagation in concat operations can corrupt binary data. - -### Read-only variable violations -- Operations that try to modify read-only scalars (e.g., `$_` aliased to a constant). - -## Current Test Status (as of 2026-03-03) - -### ExifTool Test Results: 590/600 planned (98%) - -| Test | Pass/Planned | Status | -|------|-------------|--------| -| ExifTool.t | 35/35 | PASS | -| Writer.t | 59/61 | 2 fail (test 10: Pentax date fmt, test 46: XMP Audio data) | -| XMP.t | 44/54 | 10 fail | -| Geotag.t | 3/12 | 9 fail | -| PDF.t | 18/26 | 8 fail | -| QuickTime.t | 17/22 | 5 fail | -| CanonVRD.t | 19/24 | 5 fail | -| Nikon.t | 6/9 | 3 fail | -| CanonRaw.t | 5/9 | 3 fail + crash | -| Pentax.t | 1/4 | 3 fail | -| Panasonic.t | 2/5 | 3 fail | -| (72 other tests) | all pass | PASS | - -### Fix Priority (by impact) - -#### P1: Writer.t remaining failures (2 tests: Writer 10, 46) -- **Test 10**: Pentax MakerNotes date `2008:03:02` becomes `2008:0:0`, time `12:01:23` becomes `12:0:0`. Binary date decoding issue — likely `pack`/`unpack` or BCD decode in Pentax.pm. Also has a float rounding diff (`13.2` vs `13.3`). -- **Test 46**: Missing `[XMP, XMP-GAudio, Audio] Data - Audio Data: (Binary data 1 bytes)` in output. An XMP Audio binary data tag is not being written/preserved. - -#### RESOLVED: Writer.t closure variable fix (previously P1, 15 tests fixed) -The `%mandatory` and `%crossDelete` hashes in `WriteExif.pl` are file-scope `my` variables accessed inside the large `WriteExif` sub (compiled by interpreter fallback). Fixed by registering captured variables in the compiler's symbol table via `addVariableWithIndex()` in `detectClosureVariables()`. This fixed Writer tests 6,7,11,13,19,25-28,35,38,42,48,53,55. - -#### P2: Geotag date/time computation (9 tests: Geotag 2,4,6-12) -All geotag tests except module loading and 2 others fail. All use `Time::Local` for date arithmetic and GPS coordinate interpolation. Likely one root cause in date string parsing or timezone offset calculation. Compare `Geotag_2.out` vs `Geotag_2.failed` to see if GPS coordinates are wrong or dates are wrong. - -#### P3: XMP lang-alt writing (5 tests: XMP 13,17,26,51,52) -Writing non-default language entries to XMP lang-alt lists fails silently. Only `x-default` works. The write path in `WriteXMP.pl` uses `pos()` after `m//g` for path tracking. Test with: -```bash -perl -e '"a/b/c" =~ m|/|g; print pos(), "\n"' # should print 2 -java -jar target/perlonjava-3.0.0.jar -e '"a/b/c" =~ m|/|g; print pos(), "\n"' -``` - -#### P4: XMP lang-alt Bag index tracking (3 tests: XMP 36,38,50) -Values assigned to wrong bag items; empty strings dropped from lists. Also likely `pos()` related. Test 36 specifically loses an empty string as first list element. - -#### P5: PDF write/revert cycle (8 tests: PDF 7-12,25,26) -Tests 7-12 are sequential edit/revert operations on a PDF — one failure cascades. Tests 25-26 are AES encryption (require `Digest::SHA`). Investigate test 7 first as it's the cascade root. - -#### P6: QuickTime write failures (5 tests: QuickTime 11-13,18,20) -HEIC write failures and VideoKeys/AudioKeys extraction. Lower priority — likely format-specific issues. - -#### P7: Other write failures (CanonVRD 5, Nikon 3, Pentax 3, Panasonic 3, etc.) -Various format-specific write issues. Many may share root causes with P1 (mandatory EXIF tags). - -## Key Source Files Quick Reference - -| Area | File | -|------|------| -| Bytecode compiler | `backend/bytecode/BytecodeCompiler.java` | -| Bytecode interpreter | `backend/bytecode/BytecodeInterpreter.java` | -| Assignment compilation (interp) | `backend/bytecode/CompileAssignment.java` | -| Variable collector (closures) | `backend/bytecode/VariableCollectorVisitor.java` | -| Opcodes | `backend/bytecode/Opcodes.java` | -| Block emission (JVM) | `backend/jvm/EmitBlock.java` | -| Subroutine emission (JVM) | `backend/jvm/EmitSubroutine.java` | -| Foreach emission (JVM) | `backend/jvm/EmitForeach.java` | -| Eval handling (JVM) | `backend/jvm/EmitEval.java` | -| Method creator / fallback | `backend/jvm/EmitterMethodCreator.java` | -| Large block refactoring | `backend/jvm/LargeBlockRefactorer.java` | -| Control flow safety | `frontend/analysis/ControlFlowDetectorVisitor.java` | -| Statement parser (block foreach) | `frontend/parser/StatementParser.java` | -| Statement resolver (postfix foreach) | `frontend/parser/StatementResolver.java` | -| Subroutine parser | `frontend/parser/SubroutineParser.java` | -| Runtime scalar | `runtime/runtimetypes/RuntimeScalar.java` | -| Runtime array | `runtime/runtimetypes/RuntimeArray.java` | -| Runtime hash | `runtime/runtimetypes/RuntimeHash.java` | -| Dynamic variables | `runtime/runtimetypes/DynamicVariableManager.java` | -| IO operations | `runtime/runtimetypes/RuntimeIO.java` | -| IO operator (open/dup) | `runtime/operators/IOOperator.java` | -| Control flow (goto/labels) | `backend/jvm/EmitControlFlow.java` | -| Dereference / slicing | `backend/jvm/Dereference.java` | -| Variable emission (refs) | `backend/jvm/EmitVariable.java` | -| String parser (qw, heredoc) | `frontend/parser/StringParser.java` | -| String operators | `runtime/operators/StringOperators.java` | -| Pack/Unpack | `runtime/operators/PackOperator.java` | -| Regex preprocessor | `runtime/regex/RegexPreprocessor.java` | -| Regex runtime | `runtime/regex/RuntimeRegex.java` | -| Module loading | `runtime/operators/ModuleOperators.java` | - -All paths relative to `src/main/java/org/perlonjava/`. - -## Lessons Learned (Debugging Pitfalls) - -### Register recycling inflation -The HEAD code's AST-based `detectClosureVariables` populated `capturedVarIndices` with ~321 entries, which inflated `getHighestVariableRegister()` and prevented aggressive register recycling. A no-op version (removing all capturedVarIndices) dropped Writer.t from 44/61 to 26/61 — not because of closure access, but because register recycling became too aggressive. When modifying `detectClosureVariables`, always ensure `capturedVarIndices` has enough entries to keep `getHighestVariableRegister()` high enough to prevent register corruption. - -### Multiple variable resolution paths -The bytecode compiler resolves variables in MANY separate code paths: -- `visit(IdentifierNode)` — checks `capturedVarIndices` then symbol table -- `handleHashElementAccess` — checks closure vars, symbol table, then global -- `handleArrayElementAccess` — same pattern -- `handleHashSlice`, `handleArraySlice`, `handleHashKeyValueSlice` — same -- Assignment targets in `CompileAssignment.java` — same pattern -- Various places in `CompileOperator.java` - -If a fix only patches ONE of these paths (e.g., `capturedVarIndices` check in `visit(IdentifierNode)`), hash/array access will still fall through to globals. The correct fix is to register captured variables in the **symbol table** so ALL paths find them. - -### Ordering matters for capturedVars -`SubroutineParser` builds `paramList` by iterating `getAllVisibleVariables()` (TreeMap sorted by register index) with specific filters. `detectClosureVariables()` must use the **exact same iteration order and filters**. Any mismatch causes captured variable values to be assigned to wrong registers at runtime. - -### goto LABEL across JVM scope boundaries -`EmitControlFlow.handleGotoLabel()` resolves labels at compile time within the current JVM scope. When the target label is outside the current scope (e.g., goto inside a `map` block to a label outside, or goto inside an `eval` block), the compile-time lookup fails. The fix is to emit a `RuntimeControlFlowList` marker with `ControlFlowType.GOTO` at runtime (the same mechanism used by dynamic `goto EXPR`), allowing the goto signal to propagate up the call stack. This was a blocker for both op/array.t and op/eval.t. - -### List slice with range indices -In `Dereference.handleArrowArrayDeref()`, the check for single-index vs slice path must account for range expressions (`..` operator). A range like `0..5` is a single AST node but produces multiple indices. The correct condition is: use single-index path only if there's one element AND it's not a range. Otherwise, use the slice path. The old code had a complex `isArrayLiteral` check that was too restrictive. - -### qw() backslash processing -`StringParser.parseWordsString()` must apply single-quote backslash rules to each word: `\\` → `\` and `\delimiter` → `delimiter`. Without this, backslashes are doubled in the output. The processing uses the closing delimiter from the qw construct. - -### `\(LIST)` must flatten arrays before creating refs -`\(@array)` should create individual scalar refs to each array element (like `map { \$_ } @array`), not a single ref to the array. `EmitVariable` needs a `flattenElements()` method that detects `@` sigil nodes in the list and flattens them before creating element references. - -### Squashing a diverged branch with `git diff` + `git apply` -When a feature branch has diverged far from master (thousands of commits in common history), both `git rebase` and `git merge --squash` can produce massive conflicts across dozens of files. The clean workaround: -```bash -# 1. Generate a patch of ONLY the branch's changes vs master -git diff master..feature-branch > /tmp/branch-diff.patch -# 2. Create a fresh branch from current master -git checkout master && git checkout -b feature-branch-clean -# 3. Apply the patch (no merge history = no conflicts) -git apply /tmp/branch-diff.patch -# 4. Commit as a single squashed commit -git add -A && git commit -m "Squashed: ..." -# 5. Force push to update the PR -git push --force origin feature-branch-clean -``` -This works because `git diff master..branch` produces the exact file-level delta, bypassing all the intermediate merge history that causes conflicts. - -### Always commit fixes before rebasing -Uncommitted working tree changes are lost when `git rebase --abort` is run. If you have a fix in progress (e.g., a BitwiseOperators change), commit it first — even as a WIP commit — before attempting any rebase. The rebase abort restores the branch to its pre-rebase state, which does NOT include uncommitted changes. - -### `getInt()` vs `(int) getLong()` for 32-bit integer wrapping -`RuntimeScalar.getInt()` clamps DOUBLE values to `Integer.MAX_VALUE` (e.g., `(int) 2147483648.0 == 2147483647`). But `(int) getLong()` wraps correctly via long→int truncation (e.g., `(int) 2147483648L == -2147483648`). For `use integer` operations where Config.pm reports `ivsize=4`, always use `(int) getLong()` to get proper 32-bit wrapping behavior matching Perl's semantics. - -### scalar gmtime/localtime ctime(3) format -Perl's scalar `gmtime`/`localtime` returns ctime(3) format: `"Fri Mar 7 20:13:52 881"` — NOT RFC 1123 (`"Fri, 7 Mar 0881 20:13:52 GMT"`). Use `String.format()` with explicit field widths, not `DateTimeFormatter`. Also: wday must use `getValue() % 7` (Perl: 0=Sun..6=Sat) not `getValue()` (Java: 1=Mon..7=Sun). Large years (>9999) must not crash the formatter. - -### Regression testing: always compare branch vs master -Before declaring a fix complete, run the same test on both master and the branch to distinguish real regressions from pre-existing failures. Use `perl5_t/t/` (not `perl5/t/`) for running Perl5 core tests — the `perl5_t` copy has test harness files (`test.pl`, `charset_tools.pl`) that PerlOnJava can load. - -## Adding Debug Instrumentation - -In ExifTool Perl code (temporary, never commit): -```perl -print STDERR "DEBUG: variable=$variable\n"; -``` - -In PerlOnJava Java code (temporary, never commit): -```java -System.err.println("DEBUG: value=" + value); -``` - -To trace which subs hit interpreter fallback: -```bash -JPERL_SHOW_FALLBACK=1 java -jar target/perlonjava-3.0.0.jar -Ilib t/Writer.t 2>&1 | grep FALLBACK -``` diff --git a/.cognition/skills/debug-perlonjava/SKILL.md b/.cognition/skills/debug-perlonjava/SKILL.md deleted file mode 100644 index 11c988a39..000000000 --- a/.cognition/skills/debug-perlonjava/SKILL.md +++ /dev/null @@ -1,424 +0,0 @@ ---- -name: debug-perlonjava -description: Debug and fix test failures and regressions in PerlOnJava -argument-hint: "[test-name, error message, or Perl construct]" -triggers: - - user - - model ---- - -# Debugging PerlOnJava - -You are debugging failures in PerlOnJava, a Perl-to-JVM compiler with a bytecode interpreter fallback. This skill covers debugging workflows for test failures, regressions, and parity issues between backends. - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -## Git Workflow - -**IMPORTANT: Never push directly to master. Always use feature branches and PRs.** - -```bash -git checkout -b fix/descriptive-name -# ... make changes ... -git push origin fix/descriptive-name -gh pr create --title "Fix: description" --body "Details" -``` - -## Project Layout - -- **PerlOnJava source**: `src/main/java/org/perlonjava/` (compiler, bytecode interpreter, runtime) -- **Unit tests**: `src/test/resources/unit/*.t` (run via `make`) -- **Perl5 core tests**: `perl5_t/t/` (Perl 5 compatibility suite) -- **Fat JAR**: `target/perlonjava-3.0.0.jar` -- **Launcher script**: `./jperl` - -## Building - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during debugging) | - -```bash -make # Standard build - compiles and runs tests -make dev # Quick build - compiles only, NO tests -``` - -## Running Tests - -### Single Perl5 core test -```bash -cd perl5_t/t -../../jperl op/bop.t -``` - -### With environment variables (for specific tests) -```bash -# For re/pat.t and similar regex tests -JPERL_UNIMPLEMENTED=1 JPERL_OPTS=-Xss256m PERL_SKIP_BIG_MEM_TESTS=1 ./jperl perl5_t/t/re/pat.t - -# For op/sprintf2.t -JPERL_UNIMPLEMENTED=1 ./jperl perl5_t/t/op/sprintf2.t -``` - -### Test runner (parallel, with summary) -```bash -perl dev/tools/perl_test_runner.pl perl5_t/t/op -perl dev/tools/perl_test_runner.pl --jobs 8 --timeout 60 perl5_t/t -``` - -### Test runner environment variables -The test runner (`dev/tools/perl_test_runner.pl`) automatically sets environment variables for specific tests: - -```perl -# JPERL_UNIMPLEMENTED="warn" for these tests: -re/pat_rt_report.t | re/pat.t | re/regex_sets.t | re/regexp_unicode_prop.t -op/pack.t | op/index.t | op/split.t | re/reg_pmod.t | op/sprintf.t | base/lex.t - -# JPERL_OPTS="-Xss256m" for these tests: -re/pat.t | op/repeat.t | op/list.t - -# PERL_SKIP_BIG_MEM_TESTS=1 for ALL tests -``` - -To reproduce what the test runner does for a specific test: -```bash -# For re/pat.t (needs all three): -cd perl5_t/t && JPERL_UNIMPLEMENTED=warn JPERL_OPTS=-Xss256m PERL_SKIP_BIG_MEM_TESTS=1 ../../jperl re/pat.t - -# For re/subst.t (only PERL_SKIP_BIG_MEM_TESTS): -cd perl5_t/t && PERL_SKIP_BIG_MEM_TESTS=1 ../../jperl re/subst.t - -# For op/bop.t (only PERL_SKIP_BIG_MEM_TESTS): -cd perl5_t/t && PERL_SKIP_BIG_MEM_TESTS=1 ../../jperl op/bop.t -``` - -### Interpreter mode -```bash -./jperl --interpreter script.pl -./jperl --interpreter -e 'print "hello\n"' -JPERL_INTERPRETER=1 ./jperl script.pl # Global (affects require/do/eval) -``` - -## Comparing Outputs - -### PerlOnJava vs System Perl -```bash -# System Perl -perl -e 'my @a = (1,2,3); print "@a\n"' - -# PerlOnJava -./jperl -e 'my @a = (1,2,3); print "@a\n"' -``` - -### JVM backend vs Interpreter backend -```bash -./jperl -e 'code' # JVM backend -JPERL_INTERPRETER=1 ./jperl -e 'code' # Interpreter backend -``` - -## Environment Variables - -### Compiler/Interpreter Control -| Variable | Effect | -|----------|--------| -| `JPERL_INTERPRETER=1` | Force interpreter mode globally (require/do/eval) | -| `JPERL_DISABLE_INTERPRETER_FALLBACK=1` | Disable bytecode interpreter fallback for large subs | -| `JPERL_SHOW_FALLBACK=1` | Print message when a sub falls back to interpreter | -| `JPERL_EVAL_NO_INTERPRETER=1` | Disable interpreter for `eval STRING` | -| `JPERL_OPTS="-Xss256m"` | Pass JVM options (e.g., stack size) | - -### Debugging/Tracing -| Variable | Effect | -|----------|--------| -| `JPERL_DISASSEMBLE=1` | Disassemble generated bytecode | -| `JPERL_ASM_DEBUG=1` | Print JVM bytecode when ASM frame computation crashes | -| `JPERL_EVAL_VERBOSE=1` | Verbose error reporting for eval compilation | -| `JPERL_EVAL_TRACE=1` | Trace eval STRING execution path | -| `JPERL_IO_DEBUG=1` | Trace file handle open/dup/write operations | -| `JPERL_REQUIRE_DEBUG=1` | Trace `require`/`use` module loading | - -### Perl-level -| Variable | Effect | -|----------|--------| -| `JPERL_UNIMPLEMENTED=1` | Allow unimplemented features (skip instead of die) | -| `PERL_SKIP_BIG_MEM_TESTS=1` | Skip memory-intensive tests | - -## Debugging Workflow - -### 1. Identify the regression -```bash -# Compare branch vs master -git checkout master && make dev -./jperl -e 'failing code' - -git checkout branch && make dev -./jperl -e 'failing code' -``` - -### 2. Create minimal reproducer -Reduce the failing test to the smallest code that demonstrates the bug: -```bash -./jperl -e 'my $x = 58; eval q{($x) .= "z"}; print "x=$x\n"' -``` - -### 3. Compare with system Perl -```bash -perl -e 'same code' -``` - -### 4. Use --parse to check AST -When parsing issues are suspected, compare the parse tree: -```bash -./jperl --parse -e 'code' # Show PerlOnJava AST -perl -MO=Deparse -e 'code' # Compare with Perl's interpretation -``` -This helps identify operator precedence issues and incorrect parsing. - -### 5. Use disassembly to understand -```bash -./jperl --disassemble -e 'minimal code' # JVM bytecode -./jperl --disassemble --interpreter -e 'minimal code' # Interpreter bytecode -``` - -### 6. Profile with JFR (for performance issues) -```bash -# Record profile -$JAVA_HOME/bin/java -XX:StartFlightRecording=duration=10s,filename=profile.jfr \ - -jar target/perlonjava-3.0.0.jar script.pl - -# Analyze hotspots -$JAVA_HOME/bin/jfr print --events jdk.ExecutionSample profile.jfr 2>&1 | \ - grep -E "^\s+[a-z].*line:" | sed 's/line:.*//' | sort | uniq -c | sort -rn | head -20 -``` - -### 7. Add debug prints (if needed) -In Java source, add: -```java -System.err.println("DEBUG: var=" + var); -``` -Then rebuild with `make dev`. - -### 8. Fix and verify -```bash -# After fixing -make dev -./jperl -e 'test code' # Verify fix -make # Build + run unit tests (no regressions) -``` - -## Git Workflow - -**IMPORTANT**: Always work in a feature branch and create a PR for review. - -### 1. Create a branch before making changes -```bash -git checkout -b fix-descriptive-name -``` - -### 2. Make commits with clear messages -```bash -git add -A && git commit -m "Fix by - -
- -Generated with [Devin](https://cli.devin.ai/docs) - -Co-Authored-By: Devin " -``` - -### 3. Push branch and create PR -```bash -git push -u origin fix-descriptive-name - -# Create PR using gh CLI -gh pr create --title "Fix: description" --body "## Summary -- Fixed X by Y - -## Test Plan -- [ ] Unit tests pass -- [ ] Reproducer now works correctly - -Generated with [Devin](https://cli.devin.ai/docs)" -``` - -### 4. After PR is merged, clean up -```bash -git checkout master -git pull -git branch -d fix-descriptive-name -``` - -## Architecture: Two Backends - -``` -Source → Lexer → Parser → AST ─┬─→ JVM Compiler → JVM bytecode (default) - └─→ BytecodeCompiler → InterpretedCode → BytecodeInterpreter -``` - -Both backends share the parser (same AST) and runtime (same operators, same RuntimeScalar/Array/Hash). - -## Key Source Files - -| Area | File | Notes | -|------|------|-------| -| **Bytecode Compiler** | `backend/bytecode/BytecodeCompiler.java` | AST → interpreter bytecode | -| **Bytecode Interpreter** | `backend/bytecode/BytecodeInterpreter.java` | Main dispatch loop | -| **Assignment (interp)** | `backend/bytecode/CompileAssignment.java` | Assignment compilation | -| **Binary ops (interp)** | `backend/bytecode/CompileBinaryOperator.java` | Binary operator compilation | -| **Unary ops (interp)** | `backend/bytecode/CompileOperator.java` | Unary operator compilation | -| **Opcodes** | `backend/bytecode/Opcodes.java` | Opcode constants | -| **eval STRING** | `backend/bytecode/EvalStringHandler.java` | eval STRING compilation | -| **JVM Compiler** | `backend/jvm/EmitterMethodCreator.java` | AST → JVM bytecode | -| **JVM Subroutine** | `backend/jvm/EmitSubroutine.java` | Sub compilation (JVM) | -| **JVM Binary ops** | `backend/jvm/EmitBinaryOperator.java` | Binary ops (JVM) | -| **Compilation router** | `app/scriptengine/PerlLanguageProvider.java` | Picks backend | -| **Runtime scalar** | `runtime/runtimetypes/RuntimeScalar.java` | Scalar values | -| **Runtime array** | `runtime/runtimetypes/RuntimeArray.java` | Array values | -| **Runtime hash** | `runtime/runtimetypes/RuntimeHash.java` | Hash values | -| **Math operators** | `runtime/operators/MathOperators.java` | +, -, *, /, etc. | -| **String operators** | `runtime/operators/StringOperators.java` | ., x, etc. | -| **Bitwise operators** | `runtime/operators/BitwiseOperators.java` | &, |, ^, etc. | -| **Regex runtime** | `runtime/regex/RuntimeRegex.java` | Regex matching | -| **Regex preprocessor** | `runtime/regex/RegexPreprocessor.java` | Perl→Java regex | - -All paths relative to `src/main/java/org/perlonjava/`. - -## CRITICAL: Investigate JVM Backend First - -**When fixing interpreter bugs, ALWAYS investigate how the JVM backend handles the same operation before implementing a fix.** - -The interpreter and JVM backends share the same runtime classes (`RuntimeScalar`, `RuntimeArray`, `RuntimeHash`, `RuntimeList`, `PerlRange`, etc.). The JVM backend is the reference implementation - if the interpreter handles something differently, it's likely wrong. - -### How to investigate JVM behavior - -1. **Disassemble the JVM bytecode** to see what runtime methods it calls: - ```bash - ./jperl --disassemble -e 'code that works' - ``` - -2. **Look for the runtime method calls** in the disassembly (INVOKEVIRTUAL, INVOKESTATIC): - ``` - INVOKEVIRTUAL org/perlonjava/runtime/runtimetypes/RuntimeList.addToArray - INVOKEVIRTUAL org/perlonjava/runtime/runtimetypes/RuntimeBase.setFromList - ``` - -3. **Read those runtime methods** to understand the correct behavior: - - How does `setFromList()` handle different input types? - - What methods does it call internally (`addToArray`, `getList`, etc.)? - -4. **Use the same runtime methods in the interpreter** instead of reimplementing the logic with special cases. - -### Example: Hash slice assignment with PerlRange - -**Wrong approach** (special-casing types in interpreter): -```java -if (valuesBase instanceof RuntimeList) { ... } -else if (valuesBase instanceof RuntimeArray) { ... } -else if (valuesBase instanceof PerlRange) { ... } // BAD: special case -else { ... } -``` - -**Correct approach** (use same runtime methods as JVM): -```java -// JVM calls addToArray() which handles all types uniformly -RuntimeArray valuesArray = new RuntimeArray(); -valuesBase.addToArray(valuesArray); // Works for RuntimeList, RuntimeArray, PerlRange, etc. -``` - -The JVM's `setFromList()` → `addToArray()` chain already handles `PerlRange` correctly via `PerlRange.addToArray()` → `toList().addToArray()`. The interpreter should use the same mechanism. - -## Common Bug Patterns - -### 1. Context not propagated correctly -**Symptom**: Operation returns wrong type (list vs scalar). -**Pattern**: Code uses `node.accept(this)` instead of `compileNode(node, -1, RuntimeContextType.SCALAR)`. -**Fix**: Use `compileNode()` helper with explicit context. - -### 2. Missing opcode implementation -**Symptom**: "Unknown opcode" or silent wrong result. -**Fix**: Add opcode to `Opcodes.java`, handler to `BytecodeInterpreter.java`, emitter to `BytecodeCompiler.java`, disassembly to `InterpretedCode.java`. - -### 3. Closure variable not accessible -**Symptom**: Variable returns undef inside eval/sub. -**Pattern**: Variable not registered in symbol table. -**Fix**: Ensure `detectClosureVariables()` registers captured variables via `addVariableWithIndex()`. - -### 4. Double compilation of RHS -**Symptom**: Side effects happen twice (e.g., `shift` removes two elements). -**Pattern**: RHS compiled once at top of function, then again in specific handler. -**Fix**: Remove redundant compilation, use `valueReg` from first compilation. - -### 5. Lvalue not preserved -**Symptom**: Assignment doesn't modify original variable. -**Pattern**: Expression returns copy instead of lvalue reference. -**Fix**: Ensure lvalue context is preserved through compilation chain. - -### 6. LIST_TO_COUNT destroys value -**Symptom**: Numeric value instead of expected string/reference. -**Pattern**: Incorrect scalar context conversion. -**Fix**: Remove spurious `LIST_TO_COUNT` or use proper scalar coercion. - -### 7. Block returns stale value when last statement has no result -**Symptom**: Block/eval returns unexpected value (e.g., 1 instead of undef). -**Pattern**: Last statement is `for` loop or similar that sets `lastResultReg = -1`. -**Fix**: In `visit(BlockNode)`, initialize `outerResultReg` to undef when `lastResultReg < 0`. - -### 8. Loop list evaluated in wrong context -**Symptom**: `for` loop only iterates last element when inside `eval` in scalar context. -**Pattern**: Loop list compiled with `node.list.accept(this)` instead of explicit LIST context. -**Fix**: Use `compileNode(node.list, -1, RuntimeContextType.LIST)` for loop lists. - -### 9. eval STRING context leaks into compiled code -**Symptom**: Operations inside eval behave differently based on how eval result is used. -**Pattern**: `currentCallContext` from eval propagates incorrectly to inner constructs. -**Fix**: Isolate context - loops/blocks should use their own context, not inherit from eval. - -## Test File Categories - -| Directory | Tests | Notes | -|-----------|-------|-------| -| `perl5_t/t/op/` | Core operators | bop.t, sprintf.t, etc. | -| `perl5_t/t/re/` | Regex | pat.t needs special env vars | -| `perl5_t/t/io/` | I/O operations | filetest.t, etc. | -| `perl5_t/t/uni/` | Unicode | | -| `perl5_t/t/mro/` | Method resolution | | - -## Quick Reference Commands - -```bash -# Build + test -make - -# Build only (no tests) -make dev - -# Run specific Perl5 test -perl dev/tools/perl_test_runner.pl perl5_t/t/op/bop.t - -# Debug parsing -./jperl --parse -e 'code' -perl -MO=Deparse -e 'code' - -# Debug bytecode -./jperl --disassemble -e 'code' -./jperl --disassemble --interpreter -e 'code' - -# Compare output -diff <(./jperl -e 'code') <(perl -e 'code') - -# Git workflow (always use branches!) -git checkout -b fix-name -# ... make changes ... -git add -A && git commit -m "Fix message" -git push -u origin fix-name -gh pr create --title "Fix: title" --body "Description" -``` diff --git a/.cognition/skills/debug-windows-ci/SKILL.md b/.cognition/skills/debug-windows-ci/SKILL.md deleted file mode 100644 index db59dba2c..000000000 --- a/.cognition/skills/debug-windows-ci/SKILL.md +++ /dev/null @@ -1,187 +0,0 @@ -# Debug PerlOnJava Windows CI Failures - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -## Overview - -This skill helps debug test failures that occur specifically in the Windows CI/CD environment but pass locally on macOS/Linux. - -## When to Use - -- Tests pass locally on macOS/Linux but fail on Windows CI -- Windows-specific path handling issues -- Shell command differences between platforms -- File I/O issues on Windows - -## CI/CD Structure - -### GitHub Actions Workflow - -The CI runs on `windows-latest` using: -- Java 21 (Temurin) -- Gradle for build -- Maven for tests (`make ci` runs `mvn clean test`) - -### Viewing CI Logs - -```bash -# List recent CI runs -gh run list --branch --limit 5 - -# View failed test logs -gh run view --log-failed - -# Filter for specific errors -gh run view --log-failed 2>&1 | grep -E "FAILURE|error|not ok" - -# Get test count summary -gh run view --log-failed 2>&1 | grep "Tests run:" -``` - -## Common Windows CI Issues - -### 1. Cwd/getcwd Issues - -**Symptom**: "Cannot chdir back to : 2" or "Undefined subroutine &Cwd::cwd called" - -**Root Cause**: The Perl `Cwd.pm` uses shell backticks (`` `cd` ``) on Windows which doesn't work in PerlOnJava. - -**Solution**: PerlOnJava provides `Internals::getcwd` which uses Java's `System.getProperty("user.dir")`. The Cwd.pm has been modified to use this when available. - -**Key Files**: -- `src/main/perl/lib/Cwd.pm` - Perl module with platform-specific fallbacks -- `src/main/java/org/perlonjava/runtime/perlmodule/Internals.java` - Java implementation of getcwd - -### 2. Temp File Creation Issues - -**Symptom**: "Cannot open/create : open failed" - -**Root Cause**: -- Windows uses different path separators (`\` vs `/`) -- Temp directory permissions may differ -- File locking behavior differs on Windows - -**Debugging**: -```bash -# Check temp path in error message -gh run view --log-failed 2>&1 | grep "open failed" -``` - -### 3. $^O Detection - -PerlOnJava sets `$^O` based on the Java `os.name` property: -- Windows: `MSWin32` -- macOS: `darwin` -- Linux: `linux` - -**Key File**: `src/main/java/org/perlonjava/runtime/runtimetypes/SystemUtils.java` - -### 4. Shell Command Differences - -Windows CI may fail when Perl code uses: -- Backticks with Unix commands -- `system()` calls assuming Unix shell -- Path separators in shell commands - -## Debugging Workflow - -### Step 1: Identify the Failing Test - -```bash -# Get list of failing tests -gh run view --log-failed 2>&1 | grep "testUnitTests.*FAILURE" -``` - -### Step 2: Map Test Number to File - -```bash -# List tests in order (tests are numbered alphabetically) -ls -1 src/test/resources/unit/*.t | sort | nl | grep "" -``` - -### Step 3: Analyze the Error - -```bash -# Get full context around error -gh run view --log-failed 2>&1 | grep -A10 "unit\\.t" -``` - -### Step 4: Check if Pre-existing - -```bash -# Compare with master branch CI -gh run list --branch master --limit 3 -gh run view --log-failed -``` - -## Platform-Specific Code Patterns - -### Checking for Windows in Perl - -```perl -if ($^O eq 'MSWin32') { - # Windows-specific code -} -``` - -### Checking for Windows in Java - -```java -if (SystemUtils.osIsWindows()) { - // Windows-specific code -} -``` - -### Safe Cross-Platform getcwd - -```perl -# In Cwd.pm, use Internals::getcwd if available -if (eval { Internals::getcwd(); 1 }) { - *getcwd = \&Internals::getcwd; -} -``` - -## Test File Locations - -- Unit tests: `src/test/resources/unit/*.t` -- Perl5 test suite: `perl5_t/t/` -- Java tests: `src/test/java/org/perlonjava/` - -## Related Files - -- `.github/workflows/gradle.yml` - CI workflow definition -- `Makefile` - Build targets including `ci` -- `src/main/java/org/perlonjava/runtime/perlmodule/Cwd.java` - Java Cwd stub -- `src/main/perl/lib/Cwd.pm` - Perl Cwd implementation - -## Troubleshooting Checklist - -1. [ ] Is the failure Windows-specific? (Check if macOS/Linux CI passes) -2. [ ] Is it a new regression or pre-existing? (Compare with master) -3. [ ] Does it involve file paths or shell commands? -4. [ ] Does it use Cwd or directory operations? -5. [ ] Is `$^O` being checked correctly? -6. [ ] Are there any `defined &Subroutine` checks that might behave differently? - -## Adding Debug Output - -To debug CI issues, you can temporarily add print statements to Perl modules: - -```perl -# Add to Cwd.pm to debug -warn "DEBUG: \$^O = $^O"; -warn "DEBUG: Internals::getcwd available: " . (eval { Internals::getcwd(); 1 } ? "yes" : "no"); -``` - -Then check CI logs: -```bash -gh run view --log-failed 2>&1 | grep "DEBUG:" -``` - -Remember to remove debug output before final commit. diff --git a/.cognition/skills/debugger/SKILL.md b/.cognition/skills/debugger/SKILL.md deleted file mode 100644 index 55e611163..000000000 --- a/.cognition/skills/debugger/SKILL.md +++ /dev/null @@ -1,207 +0,0 @@ -# Perl Debugger Implementation Skill - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -## Overview - -Continue implementing the Perl debugger (`-d` flag) for PerlOnJava. The debugger uses DEBUG opcodes injected at statement boundaries in the bytecode interpreter. - -## Git Workflow - -**IMPORTANT: Never push directly to master. Always use feature branches and PRs.** - -**IMPORTANT: Always commit or stash changes BEFORE switching branches.** If `git stash pop` has conflicts, uncommitted changes may be lost. - -```bash -git checkout -b feature/debugger-improvement -# ... make changes ... -git push origin feature/debugger-improvement -gh pr create --title "Debugger: description" --body "Details" -``` - -## Key Documentation - -### Design Document -- **Location**: `dev/design/perl_debugger.md` -- Contains implementation phases, architecture diagrams, and code examples - -### Perl Debugger Documentation (reference) -- `perldoc perldebug` - User documentation for Perl debugger -- `perldoc perldebguts` - Internal implementation details (key reference!) -- `perldoc perldebtut` - Tutorial -- `perl5/lib/perl5db.pl` - The standard Perl debugger (~10,000 lines) - -## Current Implementation Status - -**Branch**: `implement-perl-debugger` - -### Completed (Phase 1 + partial Phase 2) -- DEBUG opcode (376) in `Opcodes.java` -- `-d` flag in `ArgumentParser.java` sets `debugMode=true`, forces interpreter -- `BytecodeCompiler` emits DEBUG at statement boundaries when `debugMode=true` -- `BytecodeInterpreter` handles DEBUG opcode, calls `DebugHooks.debug()` -- `DebugState.java` - global debug flags, breakpoints, source storage -- `DebugHooks.java` - command loop with n/s/c/q/l/b/B/L/h commands -- Source line extraction from tokens (`ErrorMessageUtil.extractSourceLines()`) -- `l` command shows source with `==>` current line marker -- Compile-time statements (`use`/`no`) correctly skipped via `compileTimeOnly` annotation -- Infrastructure nodes in BEGIN blocks skipped via `skipDebug` annotation - -### Working Commands -| Command | Description | -|---------|-------------| -| `n` | Next (step over) | -| `s` | Step into (shows subroutine name, e.g., `main::foo(file:line)`) | -| `r` | Return (step out of current subroutine) | -| `c [line]` | Continue (optionally to line) | -| `q` | Quit | -| `l [range]` | List source (`l 10-20` or `l 15`) | -| `.` | Show current line | -| `b [line]` | Set breakpoint | -| `B [line]` | Delete breakpoint (`B *` = all) | -| `L` | List breakpoints | -| `T` | Stack trace | -| `p expr` | Print expression (supports lexical variables) | -| `x expr` | Dump expression with Data::Dumper (supports lexical variables) | -| `h` | Help | - -## Comparison with System Perl Debugger - -Tested side-by-side with `perl -d`: - -| Feature | jperl | System perl | Status | -|---------|-------|-------------|--------| -| Start line | First runtime stmt | First runtime stmt | Match | -| `n` (next) | Works | Works | Match | -| `s` (step) | Works | Works | Match | -| `c` (continue) | Works | Works | Match | -| `b` (breakpoint) | Works, confirms | Works, silent | OK | -| `L` (list bp) | Simple list | Shows code + condition | Different | -| `l` (list) | Shows context around line | Shows current line only | Different | -| `q` (quit) | Works | Works | Match | -| Package prefix | Missing | Shows `main::` | TODO | -| Prompt counter | `DB<0>` (0-indexed) | `DB<1>` (1-indexed) | TODO | -| Loading message | None | Shows perl5db.pl version | OK (intentional) | - -### Known Differences to Address -1. ~~**Package prefix**: Add `main::` (or current package) to location display~~ **DONE** -2. ~~**Prompt counter**: Change to 1-indexed (`DB<1>`) to match Perl~~ **DONE** -3. **`l` command**: Perl shows current line, subsequent `l` shows next 10 lines - -## Source Files - -| File | Purpose | -|------|---------| -| `src/main/java/org/perlonjava/runtime/debugger/DebugState.java` | Global flags, breakpoints, source storage | -| `src/main/java/org/perlonjava/runtime/debugger/DebugHooks.java` | Debug hook called by DEBUG opcode, command loop | -| `src/main/java/org/perlonjava/backend/bytecode/Opcodes.java` | DEBUG = 376 | -| `src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java` | Emits DEBUG opcodes, checks `skipDebug` | -| `src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java` | Handles DEBUG opcode | -| `src/main/java/org/perlonjava/app/cli/ArgumentParser.java` | `-d` flag handling | -| `src/main/java/org/perlonjava/frontend/parser/StatementParser.java` | Marks `use`/`no` as `compileTimeOnly` | -| `src/main/java/org/perlonjava/frontend/parser/SpecialBlockParser.java` | Marks BEGIN infrastructure as `skipDebug` | -| `src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java` | `extractSourceLines()` for source display | - -## Next Steps (from design doc) - -### Phase 2: Source Line Support (mostly done) -- [x] Store source lines during parsing -- [x] Skip compile-time statements (use/no) -- [x] Display subroutine names when stepping (e.g., `main::foo(file:line)`) -- [ ] Track breakable lines (statements vs comments) -- [ ] Implement `@{"_<$filename"}` magical array -- [ ] Implement `%{"_<$filename"}` for breakpoint storage - -### Phase 3: Debug Variables (partially done) -- [x] `$DB::single`, `$DB::trace`, `$DB::signal` synced from Java -- [x] `$DB::filename`, `$DB::line` set by DEBUG opcode -- [x] `@DB::args` support in `caller()` -- [x] `%DB::sub` for subroutine location tracking -- [ ] Make debug variables fully tied (Perl can modify them) - -### Phase 4: Perl Expression Evaluation (DONE) -- [x] `p expr` - print expression value -- [x] `x expr` - dump expression (Data::Dumper style) -- [x] Lexical variable access in debugger expressions -- [x] Registry deduplication to minimize memory usage - -### Phase 5: perl5db.pl Compatibility -- [ ] Inject `BEGIN { require 'perl5db.pl' }` when `-d` used -- [ ] `DB::sub()` routing for subroutine tracing -- [ ] Test with actual perl5db.pl - -## Tips for Development - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during debugging) | - -### Testing the debugger -```bash -make dev # Quick build after changes (no tests) - -# Test basic stepping -echo 'n -n -q' | ./jperl -d /tmp/test.pl - -# Test source listing -echo 'l -l 1-10 -q' | ./jperl -d -e 'print 1; print 2; print 3;' - -# Test breakpoints -echo 'b 3 -c -q' | ./jperl -d /tmp/test.pl - -# Compare with system perl -perl -d /tmp/test.pl -``` - -### Interactive testing -The debugger can be tested interactively - send commands and observe responses. - -### Key design principles -1. **All debugger logic in DebugHooks** - interpreter loop stays clean -2. **Zero overhead when not debugging** - no DEBUG opcodes emitted -3. **Breakpoints via Set** - O(1) lookup of "file:line" -4. **Source from tokens** - `ErrorMessageUtil.extractSourceLines()` rebuilds source -5. **Skip internal nodes** - `compileTimeOnly` and `skipDebug` annotations - -### Adding new commands -1. Add case in `DebugHooks.executeCommand()` -2. Create `handleXxx()` method -3. Return `true` to resume execution, `false` to stay in command loop -4. Update `handleHelp()` with new command - -### Adding debug variables -To expose `$DB::single` etc. to Perl code: -1. Create tied variable class that reads/writes `DebugState` fields -2. Register in GlobalVariable initialization -3. See `GlobalVariable.java` for examples of special variables - -### Step-over implementation -Already working via `DebugState.stepOverDepth`: -- `n` sets `stepOverDepth = callDepth` -- DEBUG skips when `callDepth > stepOverDepth` -- Need to call `DebugHooks.enterSubroutine()`/`exitSubroutine()` on sub entry/exit - -### Annotations for skipping DEBUG opcodes -- `compileTimeOnly` - skips entire statement compilation (for `use`/`no` results) -- `skipDebug` - skips only DEBUG opcode emission (for infrastructure nodes) - -### Common issues -- **Source not showing**: Check `DebugState.sourceLines` is populated -- **Breakpoint not hitting**: Verify line is breakable (has DEBUG opcode) -- **Step-over not working**: Ensure `callDepth` tracking is correct -- **Duplicate lines**: Check for missing `skipDebug` on internal nodes diff --git a/.cognition/skills/fix-pat-sprintf/SKILL.md b/.cognition/skills/fix-pat-sprintf/SKILL.md deleted file mode 100644 index 8ecb25316..000000000 --- a/.cognition/skills/fix-pat-sprintf/SKILL.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -name: fix-pat-sprintf -description: Fix re/pat.t and op/sprintf2.t test regressions on fix-exiftool-cli branch -argument-hint: "[test-name or specific failure]" -triggers: - - user - - model ---- - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -# Fix pat.t and sprintf2.t Regressions - -You are fixing test regressions in `re/pat.t` (-17 tests) and `op/sprintf2.t` (-3 tests) on the `fix-exiftool-cli` branch of PerlOnJava. - -## Hard Constraints - -1. **No AST refactoring fallback.** The `LargeBlockRefactorer` / AST splitter must NOT be restored. This is non-negotiable. -2. **Fix the interpreter.** The bytecode interpreter must achieve feature parity with the JVM compiler. Both backends must produce identical results for all Perl constructs. -3. **Match the baseline exactly.** Target is the master baseline scores — no more, no less: - - `re/pat.t`: 1056/1296 - - `op/sprintf2.t`: 1652/1655 -4. **Do NOT modify shared runtime** (`RuntimeRegex.java`, `RegexFlags.java`, `RegexPreprocessor.java`, etc.). The runtime is shared between both backends. Fixes must be in the interpreter code. - -## Why the Interpreter Is Involved - -Large subroutines that exceed the JVM 64KB method limit fall back to the bytecode interpreter via `EmitterMethodCreator.createRuntimeCode()`. - -- **pat.t**: The `run_tests` subroutine (lines 38-2652, ~2614 lines) falls back to interpreter. All 1296 tests run through it. Confirmed with `JPERL_SHOW_FALLBACK=1`. -- **sprintf2.t**: Same mechanism — large test body falls back to interpreter. - -## Baseline vs Branch - -| Test | Master baseline (397ba45d) | Branch HEAD | Delta | -|------|---------------------------|-------------|-------| -| re/pat.t | 1056/1296 | 1039/1296 | -17 | -| op/sprintf2.t | 1652/1655 | 1649/1655 | -3 | - -## Methodology - -For each failing test: - -1. **Extract** the specific Perl code from the test file -2. **Compare** JVM vs interpreter output: - ```bash - ./jperl -E 'extracted code' # JVM backend (correct behavior) - ./jperl --interpreter -E 'extracted code' # Interpreter (may differ) - ``` -3. **When they differ**: identify the root cause in the interpreter code (BytecodeCompiler, BytecodeInterpreter, etc.) and fix it -4. **When they don't differ standalone**: the failure depends on context from earlier tests in the same large function. Investigate what prior state affects the result — look at regex state, variable scoping, match variables, pos(), etc. -5. **Verify** the fix doesn't break other tests - -## Running the Tests - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during debugging) | - -```bash -make # Standard build - compiles and runs tests -make dev # Quick build - compiles only, NO tests -``` - -Run individual tests via test runner (sets correct ENV vars): -```bash -perl dev/tools/perl_test_runner.pl perl5_t/t/re/pat.t -perl dev/tools/perl_test_runner.pl perl5_t/t/op/sprintf2.t - -# Run manually with correct ENV -cd perl5_t/t -PERL_SKIP_BIG_MEM_TESTS=1 JPERL_UNIMPLEMENTED=warn JPERL_OPTS="-Xss256m" ../../jperl re/pat.t -PERL_SKIP_BIG_MEM_TESTS=1 JPERL_UNIMPLEMENTED=warn ../../jperl op/sprintf2.t - -# Compare JVM vs interpreter for a specific construct -./jperl -E 'code' -./jperl --interpreter -E 'code' - -# Check if a test file uses interpreter fallback -cd perl5_t/t && JPERL_SHOW_FALLBACK=1 ../../jperl re/pat.t 2>&1 | grep 'interpreter backend' - -# Get interpreter bytecodes for a construct -./jperl --interpreter --disassemble -E 'code' 2>&1 -``` - -## pat.t: Exact Regressions (18 PASS->FAIL, 1 FAIL->PASS, net -17) - -### Tests that went from PASS to FAIL - -| # | Test Description | pat.t Line | Category | -|---|-----------------|------------|----------| -| 1 | Stack may be bad | 508 | regex match | -| 2 | $^N, @- and @+ are read-only | 845-851 | eval STRING special vars | -| 3-4 | \G testing (x2) | 858, 866 | \G anchor | -| 5 | \b is not special | 1089 | word boundary | -| 6-8 | \s, [[:space:]] and [[:blank:]] (x3) | 1223-1225 | POSIX classes | -| 9 | got a latin string - rt75680 | 1252 | latin/unicode | -| 10-11 | RT #3516 A, B | 1329, 1335 | \G loop | -| 12 | Qr3 bare | ~1490 | qr// overload | -| 13 | Qr3 bare - with use re eval | ~1498 | qr// eval | -| 14 | Eval-group not allowed at runtime | 524 | regex eval | -| 15-18 | Branch reset pattern 1-4 | 2392-2409 | branch reset | - -### Test that went from FAIL to PASS - -| Test Description | Category | -|-----------------|----------| -| 1 '', '1', '12' (Eval-group) | regex eval | - -## Interpreter Architecture - -``` -Source -> Lexer -> Parser -> AST --+--> JVM Compiler (EmitterMethodCreator) -> JVM bytecode - \--> BytecodeCompiler -> InterpretedCode -> BytecodeInterpreter -``` - -Both backends share the same runtime (RuntimeRegex, RuntimeScalar, etc.). The difference is ONLY in how the AST is lowered to executable form. The interpreter must handle every construct identically to the JVM compiler. - -### Key interpreter files - -| File | Role | -|------|------| -| `backend/bytecode/BytecodeCompiler.java` | AST -> interpreter bytecodes | -| `backend/bytecode/BytecodeInterpreter.java` | Main dispatch loop | -| `backend/bytecode/InterpretedCode.java` | Code object + disassembler | -| `backend/bytecode/Opcodes.java` | Opcode constants | -| `backend/bytecode/CompileAssignment.java` | Assignment compilation | -| `backend/bytecode/CompileBinaryOperator.java` | Binary ops compilation | -| `backend/bytecode/CompileOperator.java` | Unary/misc ops compilation | -| `backend/bytecode/SlowOpcodeHandler.java` | Rarely-used op handlers | -| `backend/bytecode/OpcodeHandlerExtended.java` | CREATE_CLOSURE, STORE_GLOB, etc. | -| `backend/bytecode/MiscOpcodeHandler.java` | Misc operations | -| `backend/bytecode/EvalStringHandler.java` | eval STRING compilation for interpreter | - -All paths relative to `src/main/java/org/perlonjava/`. - -### Key source files (do NOT modify) - -| Area | File | Notes | -|------|------|-------| -| Regex runtime | `runtime/regex/RuntimeRegex.java` | DO NOT MODIFY | -| Regex flags | `runtime/regex/RegexFlags.java` | DO NOT MODIFY | -| Regex preprocessor | `runtime/regex/RegexPreprocessor.java` | DO NOT MODIFY | - -All paths relative to `src/main/java/org/perlonjava/`. - -## Verification Steps - -After any fix: - -```bash -# 1. Build must pass -make build - -# 2. Unit tests must pass -make test-unit - -# 3. Check pat.t — must match baseline (1056/1296) -perl dev/tools/perl_test_runner.pl perl5_t/t/re/pat.t - -# 4. Check sprintf2.t — must match baseline (1652/1655) -perl dev/tools/perl_test_runner.pl perl5_t/t/op/sprintf2.t - -# 5. No regressions in other key tests -perl dev/tools/perl_test_runner.pl perl5_t/t/op/pack.t -perl dev/tools/perl_test_runner.pl perl5_t/t/re/pat_rt_report.t -``` - -## Debugging Tips - -### Compare raw output between baseline and branch -```bash -# Save branch output -cd perl5_t/t && PERL_SKIP_BIG_MEM_TESTS=1 JPERL_UNIMPLEMENTED=warn JPERL_OPTS="-Xss256m" ../../jperl re/pat.t > /tmp/pat_branch.txt 2>&1 - -# Compare by test name against saved baseline -LC_ALL=C diff \ - <(LC_ALL=C grep -E '^(ok|not ok)' /tmp/pat_base_raw.txt | LC_ALL=C sed 's/^ok [0-9]* - /PASS: /;s/^not ok [0-9]* - /FAIL: /' | LC_ALL=C sort) \ - <(LC_ALL=C grep -E '^(ok|not ok)' /tmp/pat_branch.txt | LC_ALL=C sed 's/^ok [0-9]* - /PASS: /;s/^not ok [0-9]* - /FAIL: /' | LC_ALL=C sort) \ - | grep '^[<>]' -``` - -### Test specific construct through both backends -```bash -./jperl -E 'my $s="abcde"; pos $s=2; say $s =~ /^\G/ ? "match" : "no"' -./jperl --interpreter -E 'my $s="abcde"; pos $s=2; say $s =~ /^\G/ ? "match" : "no"' -``` diff --git a/.cognition/skills/interpreter-parity/SKILL.md b/.cognition/skills/interpreter-parity/SKILL.md deleted file mode 100644 index 95e688b19..000000000 --- a/.cognition/skills/interpreter-parity/SKILL.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -name: interpreter-parity -description: Debug and fix interpreter vs JVM backend parity issues in PerlOnJava -argument-hint: "[test-name, error message, or Perl construct]" -triggers: - - user - - model ---- - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -# Interpreter/JVM Backend Parity Debugging - -You are fixing cases where PerlOnJava's bytecode interpreter produces different results than the JVM compiler backend. The interpreter should be a drop-in replacement — same parsing, same runtime APIs, different execution engine. - -## Git Workflow - -**IMPORTANT: Never push directly to master. Always use feature branches and PRs.** - -**IMPORTANT: Always commit changes BEFORE switching branches.** Use `git diff > backup.patch` to save uncommitted work, or commit to a WIP branch. Never use `git stash` — changes can be silently lost. - -```bash -git checkout -b fix/interpreter-issue-name -# ... make changes ... -git push origin fix/interpreter-issue-name -gh pr create --title "Fix interpreter: description" --body "Details" -``` - -## Project Layout - -- **PerlOnJava source**: `src/main/java/org/perlonjava/` (compiler, bytecode interpreter, runtime) -- **Unit tests**: `src/test/resources/unit/*.t` (155 tests, run via `make`) -- **Fat JAR**: `target/perlonjava-3.0.0.jar` -- **Launcher script**: `./jperl` - -## Building - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during debugging) | -| `make test-interpreter` | Run unit tests with interpreter backend | - -```bash -make # Standard build - compiles and runs tests -make dev # Quick build - compiles only, NO tests -make test-interpreter # Test interpreter backend specifically -``` - -## Running in Interpreter Mode - -### CLI flag (top-level only without global flag) -```bash -./jperl --interpreter script.pl -./jperl --interpreter -e 'print "hello\n"' -./jperl --interpreter --disassemble -e 'code' # Show interpreter bytecode -``` - -### Environment variable (global, affects require/do/eval) -```bash -JPERL_INTERPRETER=1 ./jperl script.pl -``` - -### Comparing backends -```bash -# JVM backend -./jperl -e 'code' -# Interpreter backend -JPERL_INTERPRETER=1 ./jperl -e 'code' -``` - -**CRITICAL: eval STRING uses interpreter by default!** -Even when running with JVM backend, `eval STRING` compiles code with the interpreter. -This means interpreter bugs can cause test failures even without `--interpreter`. - -To trace eval STRING execution: -```bash -JPERL_EVAL_TRACE=1 ./jperl script.pl 2>&1 | grep -i interpreter -``` - -Fallback for large subs (`JPERL_SHOW_FALLBACK=1`) does NOT show eval STRING usage. -One-liners won't trigger fallback - test with actual test files! - -## Architecture: Two Backends, Shared Everything Else - -``` -Source → Lexer → Parser → AST ─┬─→ JVM Compiler (EmitterMethodCreator) → JVM bytecode - └─→ BytecodeCompiler → InterpretedCode → BytecodeInterpreter -``` - -Both backends: -- Share the same parser (same AST) -- Call identical runtime methods (MathOperators, StringOperators, RuntimeScalar, etc.) -- Use GlobalVariable for package variables -- Use RuntimeCode.apply() for subroutine dispatch - -The difference is ONLY in how the AST is lowered to executable form. - -## Key Source Files - -| Area | File | Notes | -|------|------|-------| -| Interpreter compiler | `backend/bytecode/BytecodeCompiler.java` | AST → interpreter bytecode | -| Interpreter executor | `backend/bytecode/BytecodeInterpreter.java` | Main dispatch loop | -| Interpreter code object | `backend/bytecode/InterpretedCode.java` | Extends RuntimeCode, holds bytecode + disassembler | -| Opcodes | `backend/bytecode/Opcodes.java` | Opcode constants (keep contiguous!) | -| Slow ops | `backend/bytecode/SlowOpcodeHandler.java` | Rarely-used operation handlers | -| Extended ops | `backend/bytecode/OpcodeHandlerExtended.java` | CREATE_CLOSURE, STORE_GLOB, etc. | -| JVM compiler | `backend/jvm/EmitterMethodCreator.java` | AST → JVM bytecode | -| JVM subroutine emit | `backend/jvm/EmitSubroutine.java` | Named/anon sub compilation (JVM) | -| Compilation router | `app/scriptengine/PerlLanguageProvider.java` | `compileToExecutable()` picks backend | -| Global interp flag | `runtime/runtimetypes/RuntimeCode.java` | `USE_INTERPRETER` static boolean | -| CLI flag handling | `app/cli/ArgumentParser.java` | `--interpreter` sets global flag | -| Module loading | `runtime/operators/ModuleOperators.java` | `require`/`do` propagates interpreter flag | -| Subroutine parser | `frontend/parser/SubroutineParser.java` | Named sub compilation, prototype checks | -| Special blocks | `frontend/parser/SpecialBlockParser.java` | BEGIN/END/CHECK/INIT block handling | - -All paths relative to `src/main/java/org/perlonjava/`. - -## How --interpreter Propagates - -1. `ArgumentParser.java`: Sets `parsedArgs.useInterpreter = true` AND `RuntimeCode.setUseInterpreter(true)` (global flag) -2. `ModuleOperators.java`: When loading files via `require`/`do`, copies `RuntimeCode.USE_INTERPRETER` to new `CompilerOptions` -3. `SpecialBlockParser.java`: BEGIN blocks clone `parser.ctx.compilerOptions` (inherits `useInterpreter`) -4. `PerlLanguageProvider.compileToExecutable()`: Checks `ctx.compilerOptions.useInterpreter` to pick backend - -## Common Parity Issues - -### 1. Missing metadata on InterpretedCode - -**Pattern**: The JVM backend sets metadata (prototype, attributes) on RuntimeCode objects via EmitSubroutine, but BytecodeCompiler doesn't. - -**Example**: Anonymous sub `sub() { 1 }` — JVM backend uses `node.prototype` at EmitSubroutine.java:198. BytecodeCompiler.visitAnonymousSubroutine must also set `subCode.prototype = node.prototype`. - -**Detection**: Parser disambiguation fails — e.g., `FOO ?` parsed as regex instead of ternary because `subExists` is false (requires `prototype != null`). - -**Files to check**: -- `BytecodeCompiler.visitAnonymousSubroutine()` — must copy `node.prototype` and `node.attributes` to InterpretedCode -- `InterpretedCode.withCapturedVars()` — must preserve prototype/attributes/subName/packageName when creating closure copies -- `OpcodeHandlerExtended.executeCreateClosure()` — must use `withCapturedVars()` not raw constructor - -### 2. Type mismatches (RuntimeList vs RuntimeScalar) - -**Pattern**: Method calls (`->can()`, `->method()`) return RuntimeList. The JVM backend calls `.scalar()` on the result. The interpreter's STORE_GLOB expects RuntimeScalar. - -**Detection**: `ClassCastException: RuntimeList cannot be cast to RuntimeScalar` at `BytecodeInterpreter.java` STORE_GLOB handler. - -**Fix**: The BytecodeCompiler must emit a `LIST_TO_COUNT` or similar scalar-context conversion before STORE_GLOB when the RHS is a method call. - -### 3. Missing opcode implementations - -**Pattern**: The JVM backend handles a Perl construct via a Java method call in generated bytecode. The interpreter has no corresponding opcode or emitter case. - -**Detection**: "Unknown opcode" errors, or silent wrong results. - -**Fix**: Add opcode to Opcodes.java, handler to BytecodeInterpreter.java, emitter case to BytecodeCompiler.java, disassembly case to InterpretedCode.java. Keep opcodes contiguous for tableswitch optimization. - -### 4. Context propagation differences - -**Pattern**: The JVM backend propagates scalar/list/void context through the EmitterContext. The BytecodeCompiler may not propagate context correctly for all node types. - -**Detection**: Operations return wrong type (list where scalar expected, or vice versa). Array in scalar context returns element instead of count. - -### 5. BEGIN block compilation path - -**Pattern**: BEGIN blocks are compiled and executed during parsing via `SpecialBlockParser` → `executePerlAST` → `compileToExecutable`. The BEGIN code runs BEFORE the rest of the file is parsed. Side effects (like registering subs via `*FOO = sub() { 1 }`) must be visible to the parser for subsequent code. - -**Key flow**: -1. Parser encounters `BEGIN { ... }` -2. SpecialBlockParser clones compilerOptions (inherits useInterpreter) -3. `executePerlAST` compiles the BEGIN block code (may use interpreter) -4. BEGIN block executes — side effects are immediate -5. Parser continues parsing rest of file — sees BEGIN's side effects - -**Issues**: If BEGIN creates a constant sub but the InterpretedCode has null prototype, the parser won't recognize it as a known sub, causing disambiguation failures. - -## Debugging Workflow - -### CRITICAL: Save Master Baselines ONCE, Don't Rebuild Repeatedly - -**Save master baseline to files FIRST** (do this once per debugging session): -```bash -# Save your current work first (NEVER use git stash!) -git diff > /tmp/my-changes.patch # Save uncommitted changes -git add -A && git commit -m "WIP: save work before baseline check" # Or commit to WIP - -# Switch to master and build -git checkout master -make dev - -# Save master test output for JVM backend -cd perl5_t/t && ../../jperl re/subst.t 2>&1 > /tmp/master_subst.log -grep "^not ok" /tmp/master_subst.log > /tmp/master_subst_fails.txt - -# ALSO save interpreter baseline! -cd perl5_t/t && ../../jperl --interpreter re/subst.t 2>&1 > /tmp/master_subst_interp.log - -# Switch back to feature branch -git checkout feature-branch -# Restore uncommitted changes if you used patch: -# git apply /tmp/my-changes.patch -``` - -**After making changes**, compare against saved baselines: -```bash -make dev - -# Test JVM backend -cd perl5_t/t && ../../jperl re/subst.t 2>&1 > /tmp/feature_subst.log -diff /tmp/master_subst_fails.txt <(grep "^not ok" /tmp/feature_subst.log) - -# MUST ALSO test with interpreter! -cd perl5_t/t && ../../jperl --interpreter re/subst.t 2>&1 > /tmp/feature_subst_interp.log -``` - -### CRITICAL: Always Test with BOTH Backends - -A fix that works for JVM backend may break interpreter, or vice versa. - -**For quick tests (one-liners):** -```bash -./jperl -e 'test code' # JVM backend -./jperl --interpreter -e 'test code' # Interpreter backend -``` - -**For test files (use env var so require/do/eval also use interpreter):** -```bash -./jperl test.t # JVM backend -JPERL_INTERPRETER=1 ./jperl test.t # Interpreter backend (full) -``` - -### 1. Reproduce with minimal code -```bash -# Find the failing construct -JPERL_INTERPRETER=1 ./jperl -e 'failing code' -# Compare with JVM backend -./jperl -e 'failing code' -``` - -**CRITICAL: Save baselines to files!** When comparing test suites across branches: -```bash -# On master - save results so you don't have to rebuild later -git checkout master && make dev -cd perl5_t/t && JPERL_INTERPRETER=1 ../../jperl test.t 2>&1 | tee /tmp/test_master.log -JPERL_INTERPRETER=1 ../../jperl test.t 2>&1 | grep "^ok\|^not ok" > /tmp/test_master_results.txt -grep "^ok" /tmp/test_master_results.txt | wc -l # Save this number! - -# Return to feature branch - now you can compare without rebuilding master -git checkout feature-branch && make dev -``` - -### 2. Use --disassemble to see interpreter bytecode -```bash -JPERL_INTERPRETER=1 ./jperl --disassemble -e 'code' 2>&1 -``` - -### 3. Check the bytecode around the crash -Error messages include: `[opcodes at pc-3..pc: X Y Z >>>W <<< ...]` -- Decode opcodes using `Opcodes.java` constants -- The `>>>W<<<` is the failing opcode - -### 4. Add targeted debug prints -```java -// In BytecodeInterpreter.java, around the failing opcode: -System.err.println("DEBUG opcode=" + opcode + " rd=" + rd + " type=" + registers[rd].getClass().getName()); -``` - -### 5. Trace through both backends -Compare what the JVM backend emits (via `--disassemble` without `--interpreter`) vs what the BytecodeCompiler emits (with `--interpreter --disassemble`). - -## Environment Variables - -| Variable | Effect | -|----------|--------| -| `JPERL_INTERPRETER=1` | Force interpreter mode globally (require/do/eval) | -| `JPERL_EVAL_USE_INTERPRETER=1` | Force interpreter only for eval STRING | -| `JPERL_EVAL_VERBOSE=1` | Verbose error reporting for eval compilation | -| `JPERL_DISASSEMBLE=1` | Disassemble generated bytecode | -| `JPERL_SHOW_FALLBACK=1` | Show when subs fall back to interpreter | - -## Test Infrastructure - -### make test-interpreter -Runs all 155 unit tests with `JPERL_INTERPRETER=1`. Uses `perl dev/tools/perl_test_runner.pl`. - -Output categories: -- `! 0/0 ok` — Test errored out completely (no TAP output). Usually means module loading failed. -- `X/Y ok` with checkmark — All tests passed. -- `X/Y ok` with X — Some tests failed. - -### Feature impact analysis -The test runner reports which "features" (modules, prototypes, regex, objects) block the most tests. This helps prioritize fixes. - -### Current blockers (as of 2026-03-03) -152/155 tests fail because `use Test::More` fails to load. The chain is: -``` -Test::More → Test::Builder → Test::Builder::Formatter → Test2::Formatter::TAP -``` -The failure is a ClassCastException in `Test/Builder/Formatter.pm` BEGIN block where `*OUT_STD = Test2::Formatter::TAP->can('OUT_STD')` — method call result (RuntimeList) is stored to glob (expects RuntimeScalar). - -## Design Decision: JVM Emitter Must Not Mutate the AST - -When the JVM backend fails with `MethodTooLargeException` (or `VerifyError`, etc.), `createRuntimeCode()` in `EmitterMethodCreator.java` falls back to the interpreter via `compileToInterpreter(ast, ...)`. The same fallback exists in `PerlLanguageProvider.compileToExecutable()`. - -**Problem**: The JVM emitter (EmitterVisitor and helpers) mutates the AST during code generation. If JVM compilation fails partway through, the interpreter receives a corrupted AST, producing wrong results. This is the root cause of mixed-mode failures (e.g., pack.t gets 45 extra failures when the main script falls back to interpreter after partial JVM emission). - -**Rule**: The JVM emitter must NEVER permanently mutate AST nodes. All mutations must either: -1. Be avoided entirely (work on local copies), OR -2. Use save/restore in try/finally (already done in `EmitLogicalOperator.java`) - -### Known AST mutation sites - -| File | Line(s) | What it mutates | Status | -|------|---------|-----------------|--------| -| `EmitOperator.java` | ~373 | `operand.elements.addFirst(operand.handle)` in `handleSystemBuiltin` — adds handle to elements list, never removed | **DANGEROUS** | -| `Dereference.java` | ~347,442,511,579,911 | `nodeRight.elements.set(0, new StringNode(...))` — converts IdentifierNode to StringNode for hash autoquoting. `nodeRight` comes from `asListNode()` which creates a new ListNode but shares the same `elements` list | **DANGEROUS** — mutates shared elements list | -| `EmitLogicalOperator.java` | ~188,300,340 | Temporarily rewrites `declaration.operator`/`.operand` | **SAFE** — uses save/restore in try/finally | -| `EmitControlFlow.java` | ~280 | `argsNode.elements.add(atUnderscore)` | **SAFE** — `argsNode` is a freshly created ListNode | -| `EmitOperator.java` | ~398,410 | `handleSpliceBuiltin` removes/restores first element | **SAFE** — uses try/finally restore | -| Annotations (`setAnnotation`) | various | Sets `blockIsSubroutine`, `skipRegexSaveRestore`, `isDeclaredReference` | **Likely safe** — annotations are additive hints, but verify interpreter handles them | - -### How to fix dangerous sites - -**`handleSystemBuiltin` (EmitOperator.java:373)**: Wrap in try/finally to remove the added element after accept(): -```java -if (operand.handle != null) { - hasHandle = true; - operand.elements.addFirst(operand.handle); -} -try { - operand.accept(emitterVisitor.with(RuntimeContextType.LIST)); -} finally { - if (hasHandle) { - operand.elements.removeFirst(); - } -} -``` - -**Dereference.java autoquoting**: `asListNode()` creates a new ListNode but passes the SAME `elements` list reference. The `elements.set(0, ...)` call mutates the original HashLiteralNode's elements. Fix by either: -- Making `asListNode()` copy the elements list: `new ListNode(new ArrayList<>(elements), tokenIndex)` -- Or saving/restoring the original element in try/finally - -## Lessons Learned - -### InterpretedCode constructor drops metadata -The `InterpretedCode` constructor calls `super(null, new ArrayList<>())` — always null prototype. Any metadata (prototype, attributes, subName, packageName) must be set AFTER construction. - -### withCapturedVars creates a new object -`InterpretedCode.withCapturedVars()` creates a fresh InterpretedCode. It must copy all metadata fields from the original. The CREATE_CLOSURE opcode at runtime uses this method. - -### Closure detection is aggressive -`collectVisiblePerlVariables()` in BytecodeCompiler captures ALL visible `my` variables, even if the anonymous sub doesn't reference them. This means `sub() { 1 }` inside a scope with `my $x` will go through CREATE_CLOSURE instead of LOAD_CONST. The closure copy must preserve metadata. - -### Parser disambiguation depends on RuntimeCode fields -`SubroutineParser.java:172-184` checks `existsGlobalCodeRef(fullName)` and then requires one of: `methodHandle != null`, `compilerSupplier != null`, `isBuiltin`, `prototype != null`, or `attributes != null`. In interpreter mode, InterpretedCode often has none of these set (methodHandle is null, prototype is null). The parser then treats the bareword as unknown, causing `FOO ?` to be parsed as regex instead of ternary. - -### STORE_GLOB expects RuntimeScalar -`BytecodeInterpreter.java` line 1508: `((RuntimeGlob) registers[globReg]).set((RuntimeScalar) registers[valueReg])`. If the value register contains a RuntimeList (from a method call), this throws ClassCastException. The BytecodeCompiler must ensure scalar context for glob assignment RHS. - -### Opcode contiguity is critical -JVM uses tableswitch (O(1)) for dense opcode ranges. Gaps cause lookupswitch (O(log n)) — 10-15% performance hit. Always use sequential opcode numbers. Run `dev/tools/check_opcodes.pl` after changes. - -### Disassembly cases are mandatory -Every new opcode MUST have a disassembly case in InterpretedCode.java. Missing cases cause PC misalignment — the disassembler doesn't advance past the opcode's operands, corrupting all subsequent output. diff --git a/.cognition/skills/migrate-jna/SKILL.md b/.cognition/skills/migrate-jna/SKILL.md deleted file mode 100644 index ceeec2f84..000000000 --- a/.cognition/skills/migrate-jna/SKILL.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -name: migrate-jna -description: Migrate from JNA to a modern native access library (eliminate sun.misc.Unsafe warnings) -argument-hint: "[library choice or file to migrate]" -triggers: - - user ---- - -# Migrate JNA to Modern Native Access Library - -## Problem - -JNA 5.18.1 uses `sun.misc.Unsafe::staticFieldBase` internally, which produces deprecation warnings on Java 21+ and will break in future JDK releases. The project needs to migrate to a library that uses supported APIs. - -## Candidate Replacement Libraries - -The choice of replacement library is TBD. Evaluate these options: - -### Option A: jnr-posix -- **Maven**: `com.github.jnr:jnr-posix` -- **Pros**: Purpose-built for POSIX ops, used by JRuby (production-proven), clean high-level API (`FileStat`, `kill()`, `waitpid()`, `umask()`, `utime()`), built on jnr-ffi (no `sun.misc.Unsafe`) -- **Cons**: Third-party dependency, may not cover Windows-specific calls - -### Option B: Java Foreign Function & Memory API (FFM) -- **Module**: `java.lang.foreign` (JDK built-in) -- **Pros**: No third-party dependency, official JDK solution, no deprecated APIs -- **Cons**: Stable only since Java 22 (preview in 21), verbose low-level API, requires manual struct layout definitions -- **Note**: If the project bumps minimum to Java 22, this becomes viable without preview flags - -### Option C: jnr-ffi (without jnr-posix) -- **Maven**: `com.github.jnr:jnr-ffi` -- **Pros**: Modern JNA alternative, no `sun.misc.Unsafe`, flexible -- **Cons**: Lower-level than jnr-posix, requires manual bindings (similar effort to FFM) - -## Current JNA Usage - -10 files use JNA. All paths relative to `src/main/java/org/perlonjava/`. - -### Native interface definitions - -| File | JNA Usage | -|------|-----------| -| `runtime/nativ/PosixLibrary.java` | POSIX C library bindings: `stat`, `lstat`, `chmod`, `chown`, `getpid`, `getppid`, `setpgid`, `getpgid`, `setsid`, `tcsetpgrp`, `tcgetpgrp`, `getpgrp`, `setpgrp` | -| `runtime/nativ/WindowsLibrary.java` | Windows kernel32 bindings: `GetCurrentProcessId`, `_getpid` | -| `runtime/nativ/NativeUtils.java` | JNA Platform utilities: `getpid()`, `getuid()`, `geteuid()`, `getgid()`, `getegid()`, plus `CLibrary` for `getpriority`/`setpriority`/`alarm`/`getlogin` | -| `runtime/nativ/ExtendedNativeUtils.java` | Additional POSIX: `getpwuid`, `getpwnam`, `getgrnam`, `getgrgid` (passwd/group lookups) | - -### Consumers (files that call native operations) - -| File | Operations Used | -|------|----------------| -| `runtime/operators/Stat.java` | `PosixLibrary.stat()`, `PosixLibrary.lstat()` — all 13 stat fields (dev, ino, mode, nlink, uid, gid, rdev, size, atime, mtime, ctime, blksize, blocks) | -| `runtime/operators/Operator.java` | `PosixLibrary.chmod()`, `PosixLibrary.chown()`, `NativeUtils` for pid/uid/gid | -| `runtime/operators/KillOperator.java` | `PosixLibrary.kill()` for sending signals, `NativeUtils.getpid()` | -| `runtime/operators/WaitpidOperator.java` | JNA `CLibrary.waitpid()` with `WNOHANG`/`WUNTRACED` flags, macros `WIFEXITED`/`WEXITSTATUS`/`WIFSIGNALED`/`WTERMSIG`/`WIFSTOPPED`/`WSTOPSIG` | -| `runtime/operators/UmaskOperator.java` | JNA `CLibrary.umask()` | -| `runtime/operators/UtimeOperator.java` | JNA `CLibrary.utimes()` with `timeval` struct | - -## Migration Strategy - -### Phase 1: Replace native interface definitions -1. Create new interface files using the chosen library -2. Keep the same method signatures where possible -3. Ensure struct mappings (stat, timeval, passwd, group) are complete - -### Phase 2: Update consumers one by one -Migrate in this order (least to most complex): -1. `UmaskOperator.java` — single `umask()` call -2. `KillOperator.java` — `kill()` + `getpid()` -3. `UtimeOperator.java` — `utimes()` with struct -4. `Operator.java` — `chmod()`, `chown()`, pid/uid/gid -5. `WaitpidOperator.java` — `waitpid()` with flag macros -6. `Stat.java` — `stat()`/`lstat()` with 13-field struct -7. `NativeUtils.java` / `ExtendedNativeUtils.java` — passwd/group lookups - -### Phase 3: Remove JNA dependency -1. Remove JNA imports from all files -2. Remove JNA from `build.gradle` and `pom.xml` -3. Remove `--enable-native-access=ALL-UNNAMED` from `jperl` launcher (if no longer needed) -4. Verify the `sun.misc.Unsafe` warning is gone - -## Testing - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration) | -| `make test-all` | Run extended test suite | - -After each file migration: -```bash -make # Build + unit tests (must pass) -make test-all # Check for regressions in extended tests -``` - -Key tests that exercise native operations: -- `perl5_t/t/op/stat.t` — stat/lstat fields -- `perl5_t/t/io/fs.t` — chmod, chown, utime -- `perl5_t/t/op/fork.t` — kill, waitpid -- `src/test/resources/unit/glob.t` — readdir (uses stat internally) - -## Build Configuration - -### Current JNA in gradle -``` -# gradle/libs.versions.toml -jna = "5.18.1" -jna = { module = "net.java.dev.jna:jna", version.ref = "jna" } -jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" } -``` - -### Current JNA in pom.xml -```xml - - net.java.dev.jna - jna - - - net.java.dev.jna - jna-platform - -``` - -## Platform Considerations - -- **macOS/Linux**: Full POSIX support required (stat, lstat, kill, waitpid, chmod, chown, umask, utime, passwd/group lookups) -- **Windows**: Limited support via `kernel32` (`GetCurrentProcessId`), `msvcrt` (`_getpid`, stat) -- The replacement must handle both platforms, or gracefully degrade on Windows (as JNA currently does) diff --git a/.cognition/skills/port-cpan-module/SKILL.md b/.cognition/skills/port-cpan-module/SKILL.md deleted file mode 100644 index 0640fec92..000000000 --- a/.cognition/skills/port-cpan-module/SKILL.md +++ /dev/null @@ -1,415 +0,0 @@ -# Port CPAN Module to PerlOnJava - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -This skill guides you through porting a CPAN module with XS/C components to PerlOnJava using Java implementations. - -## When to Use This Skill - -- User asks to add a CPAN module to PerlOnJava -- User asks to port a Perl module with XS code -- User wants to implement Perl module functionality in Java - -## Key Principles - -1. **Reuse as much original code as possible** - Most CPAN modules are 70-90% pure Perl. Only the XS/C portions need Java replacements. Copy the original `.pm` code and adapt minimally. - -2. **Always inspect the XS source** - The `.xs` file reveals exactly what needs Java implementation. Study it to understand the C algorithms, edge cases, and expected behavior. - -3. **Credit original authors** - Always preserve the original AUTHORS and COPYRIGHT sections in the POD. Add a note that this is a PerlOnJava port. - -## Overview - -PerlOnJava supports three types of modules: -1. **Pure Perl modules** - Work directly, no Java needed -2. **Java-implemented modules (XSLoader)** - Replace XS/C with Java -3. **Built-in modules (GlobalContext)** - Internal only - -**Most CPAN ports use type #2 (XSLoader).** - -## Step-by-Step Process - -### Phase 1: Analysis - -1. **Fetch the original module source:** - ``` - https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.pm - https://fastapi.metacpan.org/v1/source/AUTHOR/Module-Version/Module.xs - ``` - -2. **Study the XS file thoroughly:** - - Look for `MODULE = ` and `PACKAGE = ` declarations - - Identify each XS function (appears after `void` or return type) - - Read the C code to understand algorithms and edge cases - - Note any platform-specific code (WIN32, etc.) - - Check for copyright notices to preserve - -3. **Identify what needs Java implementation:** - - Functions defined in `.xs` files - - Functions that call C libraries (strftime, crypt, etc.) - - Functions loaded via `XSLoader::load()` - -4. **Identify what can be reused as pure Perl (typically 70-90%):** - - Most accessor methods - - Helper/utility functions - - Overloaded operators - - Import/export logic - - Format translation maps - - Constants and configuration - -5. **Check for dependencies:** - - Other modules the target depends on - - Whether those dependencies exist in PerlOnJava - -6. **Check available Java libraries:** - - Review `pom.xml` and `build.gradle` for already-imported dependencies - - Common libraries already available: Gson, jnr-posix, jnr-ffi, SnakeYAML, etc. - - Consider if a Java library can replace the XS functionality directly - -7. **Check existing PerlOnJava infrastructure:** - - `org.perlonjava.runtime.nativ.PosixLibrary` - JNR-POSIX wrapper for native calls - - `org.perlonjava.runtime.nativ.NativeUtils` - Cross-platform utilities with Windows fallbacks - - `org.perlonjava.runtime.operators.*` - Existing operator implementations - -### Phase 2: Create Java Implementation - -**File location:** `src/main/java/org/perlonjava/runtime/perlmodule/` - -**Naming convention:** `Module::Name` → `ModuleName.java` -- `Time::Piece` → `TimePiece.java` -- `Digest::MD5` → `DigestMD5.java` -- `DBI` → `DBI.java` - -**Basic structure:** -```java -package org.perlonjava.runtime.perlmodule; - -import org.perlonjava.runtime.runtimetypes.*; - -public class ModuleName extends PerlModuleBase { - - public ModuleName() { - super("Module::Name", false); // false = not a pragma - } - - public static void initialize() { - ModuleName module = new ModuleName(); - try { - // Register methods - Perl name, Java method name (null = same), prototype - module.registerMethod("xs_function", null); - module.registerMethod("perl_name", "javaMethodName", null); - } catch (NoSuchMethodException e) { - System.err.println("Warning: Missing method: " + e.getMessage()); - } - } - - // Method signature: (RuntimeArray args, int ctx) -> RuntimeList - public static RuntimeList xs_function(RuntimeArray args, int ctx) { - // args.get(0) = first argument ($self for methods) - // ctx = RuntimeContextType.SCALAR, LIST, or VOID - - String param = args.get(0).toString(); - int number = args.get(1).getInt(); - - // Return value - return new RuntimeScalar(result).getList(); - } -} -``` - -### Phase 3: Create Perl Wrapper - -**File location:** `src/main/perl/lib/Module/Name.pm` - -**Template:** -```perl -package Module::Name; - -use strict; -use warnings; - -our $VERSION = '1.00'; - -# Load Java implementation -use XSLoader; -XSLoader::load('Module::Name', $VERSION); - -# Pure Perl code from original module goes here -# (accessors, helpers, overloads, etc.) - -1; - -__END__ - -=head1 NAME - -Module::Name - Description - -=head1 DESCRIPTION - -This is a port of the CPAN Module::Name module for PerlOnJava. - -=head1 AUTHOR - -Original Author Name, original@email.com - -Additional Author, other@email.com (if applicable) - -=head1 COPYRIGHT AND LICENSE - -Copyright YEAR, Original Copyright Holder. - -This module is free software; you may distribute it under the same terms -as Perl itself. - -=cut -``` - -### Phase 4: Testing - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during development) | - -1. **Create test file:** `src/test/resources/module_name.t` - -2. **Compare with system Perl:** - ```bash - # Create test script - cat > /tmp/test.pl << 'EOF' - use Module::Name; - # test code - EOF - - # Run with both - perl /tmp/test.pl - ./jperl /tmp/test.pl - ``` - -3. **Build and verify:** - ```bash - make dev # Quick build (no tests) - ./jperl -e 'use Module::Name; ...' - make # Full build with tests before committing - ``` - -## Common Patterns - -### Reading XS Files - -XS files have a specific structure: - -```c -MODULE = Time::Piece PACKAGE = Time::Piece - -void -_strftime(fmt, epoch, islocal = 1) - char * fmt - time_t epoch - int islocal -CODE: - /* C implementation here */ - ST(0) = sv_2mortal(newSVpv(result, len)); -``` - -Key elements to identify: -- **Function name**: `_strftime` (usually prefixed with `_` for internal XS) -- **Parameters**: `fmt`, `epoch`, `islocal` with their C types -- **Default values**: `islocal = 1` -- **Return mechanism**: `ST(0)`, `RETVAL`, or stack manipulation - -### Converting XS to Java - -| XS Pattern | Java Equivalent | -|------------|-----------------| -| `SvIV(arg)` | `args.get(i).getInt()` | -| `SvNV(arg)` | `args.get(i).getDouble()` | -| `SvPV(arg, len)` | `args.get(i).toString()` | -| `newSViv(n)` | `new RuntimeScalar(n)` | -| `newSVnv(n)` | `new RuntimeScalar(n)` | -| `newSVpv(s, len)` | `new RuntimeScalar(s)` | -| `av_fetch(av, i, 0)` | `array.get(i)` | -| `hv_fetch(hv, k, len, 0)` | `hash.get(k)` | -| `RETVAL` / `ST(0)` | `return new RuntimeScalar(x).getList()` | - -### Using Existing Java Libraries - -**Check `build.gradle` for available dependencies:** -```bash -grep "implementation" build.gradle -``` - -**Common libraries already in PerlOnJava:** - -| Java Library | Use Case | Example Module | -|--------------|----------|----------------| -| Gson | JSON parsing/encoding | `Json.java` | -| jnr-posix | Native POSIX calls | `POSIX.java` | -| jnr-ffi | Foreign function interface | Native bindings | -| SnakeYAML | YAML parsing | `YAMLPP.java` | -| TOML4J | TOML parsing | `Toml.java` | -| Java stdlib | Crypto, encoding, time | Various | - -**Example: JSON.java uses Gson directly:** -```java -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public static RuntimeList encode_json(RuntimeArray args, int ctx) { - Gson gson = new GsonBuilder().create(); - String json = gson.toJson(convertToJava(args.get(0))); - return new RuntimeScalar(json).getList(); -} -``` - -**Standard Java imports:** -```java -// Time operations -import java.time.*; -import java.time.format.DateTimeFormatter; - -// Crypto -import java.security.MessageDigest; - -// Encoding -import java.util.Base64; -import java.nio.charset.StandardCharsets; - -// Native POSIX calls (with Windows fallbacks) -import org.perlonjava.runtime.nativ.PosixLibrary; -import org.perlonjava.runtime.nativ.NativeUtils; -``` - -**Using PosixLibrary for native calls:** -```java -// Direct POSIX call (Unix only) -int uid = PosixLibrary.INSTANCE.getuid(); - -// Cross-platform with Windows fallback (preferred) -RuntimeScalar uid = NativeUtils.getuid(ctx); -``` - -### Returning Different Types - -```java -// Scalar -return new RuntimeScalar(value).getList(); - -// List -RuntimeList result = new RuntimeList(); -result.add(new RuntimeScalar(item1)); -result.add(new RuntimeScalar(item2)); -return result; - -// Array reference -RuntimeArray arr = new RuntimeArray(); -arr.push(new RuntimeScalar(item)); -return arr.createReference().getList(); - -// Hash reference -RuntimeHash hash = new RuntimeHash(); -hash.put("key", new RuntimeScalar(value)); -return hash.createReference().getList(); -``` - -### Handling Context - -```java -public static RuntimeList myMethod(RuntimeArray args, int ctx) { - if (ctx == RuntimeContextType.SCALAR) { - // Return single value - return new RuntimeScalar(count).getList(); - } else { - // Return list - RuntimeList result = new RuntimeList(); - for (String item : items) { - result.add(new RuntimeScalar(item)); - } - return result; - } -} -``` - -## Checklist - -### Pre-porting -- [ ] Fetch original `.pm` and `.xs` source -- [ ] Study XS code to understand C algorithms and edge cases -- [ ] Identify XS functions that need Java implementation -- [ ] Check dependencies exist in PerlOnJava -- [ ] Check `build.gradle`/`pom.xml` for usable Java libraries -- [ ] Check `nativ/` package for POSIX functionality -- [ ] Review existing similar modules for patterns - -### Implementation -- [ ] Create `ModuleName.java` with XS replacements -- [ ] Create `Module/Name.pm` with pure Perl code -- [ ] Add proper author/copyright attribution -- [ ] Register all methods in `initialize()` - -### Testing -- [ ] Build compiles without errors: `make dev` (NEVER use raw mvn/gradlew) -- [ ] Basic functionality works: `./jperl -e 'use Module::Name; ...'` -- [ ] Compare output with system Perl -- [ ] Test edge cases identified in XS code - -### Documentation -- [ ] Add POD with AUTHOR and COPYRIGHT sections -- [ ] Credit original authors - -## Example: Time::Piece Port - -**Files created:** -- `src/main/java/org/perlonjava/runtime/perlmodule/TimePiece.java` -- `src/main/java/org/perlonjava/runtime/perlmodule/POSIX.java` (for strftime) -- `src/main/perl/lib/Time/Piece.pm` -- `src/main/perl/lib/Time/Seconds.pm` - -**XS functions replaced:** -| XS Function | Java Implementation | -|-------------|---------------------| -| `_strftime(fmt, epoch, islocal)` | `DateTimeFormatter` with format mapping | -| `_strptime(str, fmt, gmt, locale)` | `DateTimeFormatter.parse()` | -| `_tzset()` | No-op (Java handles TZ) | -| `_crt_localtime(epoch)` | `ZonedDateTime` conversion | -| `_crt_gmtime(epoch)` | `ZonedDateTime` at UTC | -| `_get_localization()` | `DateFormatSymbols` | -| `_mini_mktime(...)` | `LocalDateTime` normalization | - -**Pure Perl reused (~80%):** -- All accessor methods (sec, min, hour, year, etc.) -- Formatting helpers (ymd, hms, datetime) -- Julian day calculations -- Overloaded operators -- Import/export logic - -## Troubleshooting - -### "Can't load Java XS module" -- Check class name matches: `Module::Name` → `ModuleName.java` -- Verify `initialize()` method exists and is static -- Check package is `org.perlonjava.runtime.perlmodule` - -### Method not found -- Ensure method is registered in `initialize()` -- Check method signature: `public static RuntimeList name(RuntimeArray args, int ctx)` - -### Different output than system Perl -- Compare with fixed test values (not current time) -- Check locale handling -- Verify edge cases from XS comments - -## References - -- Module porting guide: `docs/guides/module-porting.md` -- Existing modules: `src/main/java/org/perlonjava/runtime/perlmodule/` -- Runtime types: `src/main/java/org/perlonjava/runtime/runtimetypes/` diff --git a/.cognition/skills/profile-perlonjava/SKILL.md b/.cognition/skills/profile-perlonjava/SKILL.md deleted file mode 100644 index 4f532afff..000000000 --- a/.cognition/skills/profile-perlonjava/SKILL.md +++ /dev/null @@ -1,149 +0,0 @@ -# Profile PerlOnJava - -## ⚠️⚠️⚠️ CRITICAL: NEVER USE `git stash` ⚠️⚠️⚠️ - -**DANGER: Changes are SILENTLY LOST when using git stash/stash pop!** - -- NEVER use `git stash` to temporarily revert changes -- INSTEAD: Commit to a WIP branch or use `git diff > backup.patch` -- This warning exists because completed work was lost during debugging - -Profile and optimize PerlOnJava runtime performance using Java Flight Recorder. - -## Git Workflow - -**IMPORTANT: Never push directly to master. Always use feature branches and PRs.** - -**IMPORTANT: Always commit or stash changes BEFORE switching branches.** If `git stash pop` has conflicts, uncommitted changes may be lost. - -```bash -git checkout -b perf/optimization-name -# ... make changes ... -git push origin perf/optimization-name -gh pr create --title "Perf: description" --body "Details" -``` - -## When to Use - -- Investigating performance bottlenecks in Perl scripts running on PerlOnJava -- Finding optimization opportunities in the runtime -- Measuring impact of optimizations - -## Workflow - -### 1. Run with JFR Profiling - -```bash -cd /Users/fglock/projects/PerlOnJava2 - -# Profile a long-running script (adjust duration as needed) -java -XX:+FlightRecorder \ - -XX:StartFlightRecording=duration=60s,filename=profile.jfr \ - -jar target/perlonjava-3.0.0.jar [args...] -``` - -### 2. Analyze with JFR Tools - -```bash -# Path to jfr tool -JFR="$(/usr/libexec/java_home)/bin/jfr" - -# Summary of recorded events -$JFR summary profile.jfr - -# Extract execution samples (CPU hotspots) -$JFR print --events jdk.ExecutionSample profile.jfr - -# Aggregate hotspots by method (most useful) -$JFR print --events jdk.ExecutionSample profile.jfr 2>&1 | \ - grep -E "^\s+[a-z].*line:" | \ - sed 's/line:.*//' | \ - sort | uniq -c | sort -rn | head -40 -``` - -### 3. Key Hotspot Categories - -| Category | Methods to Watch | Optimization Approach | -|----------|------------------|----------------------| -| **Number parsing** | `Long.parseLong`, `Double.parseDouble`, `NumberParser.parseNumber` | Cache numeric values, avoid string→number conversions | -| **Type checking** | `ScalarUtils.looksLikeNumber`, `RuntimeScalar.getDefinedBoolean` | Fast-path for common types (INTEGER, DOUBLE) | -| **Bitwise ops** | `BitwiseOperators.*` | Ensure values stay as INTEGER type | -| **Regex** | `Pattern.match`, `Matcher.matches` | Reduce unnecessary regex checks | -| **Loop control** | `RuntimeControlFlowRegistry.checkLoopAndGetAction` | ThreadLocal overhead | -| **Array ops** | `ArrayList.grow`, `Arrays.copyOf` | Pre-size arrays, reduce allocations | - -### 4. Common Runtime Files - -| File | Purpose | -|------|---------| -| `src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java` | Scalar value representation, getLong/getDouble/getInt | -| `src/main/java/org/perlonjava/runtime/runtimetypes/ScalarUtils.java` | Utility functions like looksLikeNumber | -| `src/main/java/org/perlonjava/runtime/operators/BitwiseOperators.java` | Bitwise operations (&, |, ^, ~, <<, >>) | -| `src/main/java/org/perlonjava/runtime/operators/Operator.java` | General operators | -| `src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java` | Array operations | - -### 5. Optimization Patterns - -#### Fast-path for common types -```java -public static boolean looksLikeNumber(RuntimeScalar runtimeScalar) { - // Inlined fast-path for most common numeric types - int t = runtimeScalar.type; - if (t == INTEGER || t == DOUBLE) { - return true; - } - return looksLikeNumberSlow(runtimeScalar, t); -} -``` - -#### Avoid repeated parsing -```java -// Bad: parses string every time -long val = runtimeScalar.getLong(); // calls Long.parseLong if STRING - -// Better: check type first, use cached value -if (runtimeScalar.type == INTEGER) { - long val = (int) runtimeScalar.value; // direct access -} -``` - -### 6. Benchmark Commands - -```bash -# Quick benchmark with life_bitpacked.pl -java -jar target/perlonjava-3.0.0.jar examples/life_bitpacked.pl \ - -w 200 -h 200 -g 10000 -r none - -# Multiple runs for consistency -for i in 1 2 3; do - java -jar target/perlonjava-3.0.0.jar examples/life_bitpacked.pl \ - -w 200 -h 200 -g 10000 -r none 2>&1 | grep "per second" -done -``` - -### 7. Build and Test - -**ALWAYS use `make` commands. NEVER use raw mvn/gradlew commands.** - -| Command | What it does | -|---------|--------------| -| `make` | Build + run all unit tests (use before committing) | -| `make dev` | Build only, skip tests (for quick iteration during profiling) | - -```bash -make # Standard build - compiles and runs tests -make dev # Quick build - compiles only, NO tests -``` - -## Example Session - -``` -1. Identify slow script or operation -2. Profile with JFR (60s recording) -3. Aggregate hotspots by method -4. Identify top bottlenecks (parsing, type checks, etc.) -5. Implement fast-path optimization -6. Rebuild and benchmark -7. Profile again to verify improvement -8. Run tests to ensure correctness -``` diff --git a/.gitignore b/.gitignore index 7db9747b8..5c346f560 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ Image-ExifTool-* /.err /.out /.windsurf/ +/.cognition/ # Ignore xxx/ directory (temporary module staging area) xxx/ diff --git a/dev/cpan-reports/cpan-compatibility-fail.dat b/dev/cpan-reports/cpan-compatibility-fail.dat index ee90b4bf6..2e24478a5 100644 --- a/dev/cpan-reports/cpan-compatibility-fail.dat +++ b/dev/cpan-reports/cpan-compatibility-fail.dat @@ -1,134 +1,662 @@ A1z::Html FAIL Unknown test outcome 2026-04-12 +AAC::Pvoice FAIL Missing: Wx.pm 2026-04-12 +AE FAIL Configure failed 2026-04-12 +AFS::Monitor FAIL Unknown test outcome 2026-04-12 +AI::Categorizer FAIL 28 0 67/28 subtests failed 2026-04-12 +AI::MXNetCAPI FAIL 1 0 1/1 subtests failed 2026-04-12 +AI::NNVMCAPI FAIL 1 0 1/1 subtests failed 2026-04-12 +AI::Prolog FAIL Unknown test outcome 2026-04-12 +ALPM FAIL Configure failed 2026-04-12 +ANSI::Unicode FAIL Missing: Moose.pm 2026-04-12 +ARGV::JSON FAIL 1 1 2026-04-12 +ARGV::OrDATA FAIL 20 17 3/20 subtests failed 2026-04-12 +AWS::IP FAIL 1 0 1/1 subtests failed 2026-04-12 +AXL::Client::Simple FAIL Configure failed 2026-04-12 +Abstract::Meta::Class FAIL 104 104 Missing: Devel/Symdump.pm 2026-04-12 +Ace FAIL Unknown test outcome 2026-04-12 +Acrux FAIL 139 134 5/139 subtests failed 2026-04-12 +Affix FAIL Configure failed 2026-04-12 +Algorithm::Combinatorics FAIL 2 1 1/2 subtests failed 2026-04-12 +Algorithm::SVM FAIL 2026-04-12 +Alias FAIL 2026-04-12 +Alien::Base::ModuleBuild FAIL Unknown test outcome 2026-04-12 Alien::Base::Wrapper FAIL Unknown test outcome 2026-04-12 +Alien::Build::Plugin::Download::GitHub FAIL 4 2 2/4 subtests failed 2026-04-12 +Alien::GMP FAIL Configure failed 2026-04-12 +Alien::Libxml2 FAIL Configure failed 2026-04-12 +Alien::libextism FAIL Configure failed 2026-04-12 +Alien::wxWidgets FAIL 1 0 8/1 subtests failed 2026-04-12 +AlignDB::DeltaG FAIL 1 0 1/1 subtests failed 2026-04-12 +Alter FAIL 91 73 18/91 subtests failed 2026-04-12 AnnoCPAN::Perldoc::SyncDB FAIL No parseable output 2026-04-12 Announcements FAIL Configure failed 2026-04-12 +Any::Moose FAIL 18 15 3/18 subtests failed 2026-04-12 +AnyData FAIL 1 0 59/1 subtests failed 2026-04-12 +AnyEvent FAIL Configure failed 2026-04-12 +AnyEvent::AggressiveIdle FAIL 4 0 12/4 subtests failed 2026-04-12 +AnyEvent::Atom::Stream FAIL Configure failed 2026-04-12 +AnyEvent::Capture FAIL Missing: AnyEvent.pm 2026-04-12 +AnyEvent::Connection FAIL Configure failed 2026-04-12 +AnyEvent::Discord FAIL PerlOnJava: register limit exceeded 2026-04-12 +AnyEvent::FTP FAIL 116 105 11/116 subtests failed 2026-04-12 +AnyEvent::Fork::Remote FAIL Missing: common/sense.pm 2026-04-12 +AnyEvent::ForkObject FAIL 5 0 51/5 subtests failed 2026-04-12 +AnyEvent::FriendFeed::Realtime FAIL Configure failed 2026-04-12 +AnyEvent::Gearman FAIL Configure failed 2026-04-12 +AnyEvent::Groonga FAIL Configure failed 2026-04-12 +AnyEvent::HTTP FAIL Missing: common/sense.pm 2026-04-12 +AnyEvent::ImageShack FAIL 1 0 1/1 subtests failed 2026-04-12 +AnyEvent::MockTCPServer FAIL Missing: AnyEvent/Socket.pm 2026-04-12 +AnyEvent::OWNet FAIL 18 15 3/18 subtests failed 2026-04-12 +AnyEvent::Pg::Pool FAIL Missing: Pg/PQ.pm 2026-04-12 +AnyEvent::Pg::Pool::Multiserver FAIL 1 0 1/1 subtests failed 2026-04-12 +AnyEvent::Plurk FAIL Configure failed 2026-04-12 +AnyEvent::Processor FAIL 5 0 5/5 subtests failed 2026-04-12 +AnyEvent::RPC FAIL Configure failed 2026-04-12 +AnyEvent::SSH2 FAIL 1 0 1/1 subtests failed 2026-04-12 +AnyEvent::Serialize FAIL Missing: AnyEvent.pm 2026-04-12 +AnyEvent::Tools FAIL 10 0 103/10 subtests failed 2026-04-12 +AnyEvent::WebService::Notifo FAIL 1 0 1/1 subtests failed 2026-04-12 +AnyEvent::mDNS FAIL Configure failed 2026-04-12 +AnyMQ::Pg FAIL Configure failed 2026-04-12 +Ao FAIL 2026-04-12 +Apache2::AuthAny FAIL 19 4 15/19 subtests failed 2026-04-12 +Apache2::AuthenSmb FAIL Configure failed 2026-04-12 +Apache2::FixupContentLanguage FAIL Configure failed 2026-04-12 +Apache2::ScoreboardIsFull FAIL 1 0 1/1 subtests failed 2026-04-12 +Apache2::WebApp FAIL Configure failed 2026-04-12 +Apache2::WebApp::Plugin::DBI FAIL Configure failed 2026-04-12 +Apache2::WebApp::Plugin::DateTime FAIL Configure failed 2026-04-12 +Apache2::WebApp::Plugin::File FAIL Configure failed 2026-04-12 +Apache::Htpasswd FAIL Unknown test outcome 2026-04-12 +Apache::Scoreboard FAIL Configure failed 2026-04-12 App::Cmd::Setup FAIL 57 31 26/57 subtests failed 2026-04-12 +AppBase::Grep FAIL PerlOnJava: register limit exceeded 2026-04-12 +AppBase::Sort FAIL PerlOnJava: register limit exceeded 2026-04-12 +Archive::CAR FAIL Missing: Codec/CBOR.pm 2026-04-12 +Archive::Peek FAIL 5 4 1/5 subtests failed 2026-04-12 +Array::Compare FAIL 37 36 1/37 subtests failed 2026-04-12 +ArrayData::Lingua::Word::EN::Medical::Glutanimate FAIL PerlOnJava: register limit exceeded 2026-04-12 +Arriba FAIL 9 5 4/9 subtests failed 2026-04-12 Asm::Preproc FAIL 1 0 1/1 subtests failed 2026-04-12 +AsposeCellsCloud::Object::ProtectWorkbookRequst FAIL Unknown test outcome 2026-04-12 +AsposeSlidesCloud::ApiClient FAIL Unknown test outcome 2026-04-12 +At FAIL 2026-04-12 +Atompub FAIL Configure failed 2026-04-12 +B::Deobfuscate FAIL 2026-04-12 +B::Keywords FAIL 15 15 2026-04-12 +B::Lint FAIL Unknown test outcome 2026-04-12 +B::Lint::Plugin::Test FAIL No parseable output 2026-04-12 +B::Module::Info FAIL 109 70 39/109 subtests failed 2026-04-12 +BBS::UserInfo::SOB FAIL 1 0 1/1 subtests failed 2026-04-12 +BIE::Data::HDF5 FAIL Configure failed 2026-04-12 +Bad_Handle FAIL TIMEOUT (>120s) 2026-04-12 +BaseLib FAIL 2026-04-12 +BeePack FAIL 1 0 1/1 subtests failed 2026-04-12 +BenchmarkAnything::Config FAIL 2 2 2026-04-12 BerkeleyDB FAIL 3 3 2026-04-12 +Binding FAIL Configure failed 2026-04-12 +BingoX::Argon FAIL Unknown test outcome 2026-04-12 +Bison FAIL Configure failed 2026-04-12 Bit::Vector FAIL 1 0 14/1 subtests failed 2026-04-12 +Blessed::Merge FAIL 1 0 1/1 subtests failed 2026-04-12 +Bluesky FAIL Missing: At.pm 2026-04-12 +BorderStyle FAIL PerlOnJava: register limit exceeded 2026-04-12 +ByteCache FAIL Unknown test outcome 2026-04-12 +Bytes::Random::Secure FAIL 207 195 12/207 subtests failed 2026-04-12 +CACertOrg::CA FAIL 6 3 3/6 subtests failed 2026-04-12 +CAD::Calc FAIL Unknown test outcome 2026-04-12 +CAM::EmailTemplate::SMTP FAIL Missing: CAM/Template.pm 2026-04-12 +CDB::TinyCDB FAIL Configure failed 2026-04-12 +CGI::Application::Plugin::AutoRunmode FAIL 74 71 3/74 subtests failed 2026-04-12 +CGI::Application::Plugin::TT FAIL 58 55 3/58 subtests failed 2026-04-12 +CGI::Auth FAIL 4 2 2/4 subtests failed 2026-04-12 +CGI::Builder FAIL 5 0 22/5 subtests failed 2026-04-12 +CGI::Capture FAIL 14 13 1/14 subtests failed 2026-04-12 +CGI::Easy FAIL 2 0 4/2 subtests failed 2026-04-12 CGI::Emulate::PSGI FAIL 41 28 13/41 subtests failed 2026-04-12 +CGI::EncryptForm FAIL 2026-04-12 +CGI::Enurl FAIL Unknown test outcome 2026-04-12 +CGI::FormBuilder FAIL 467 313 154/467 subtests failed 2026-04-12 CGI::PSGI FAIL Configure failed 2026-04-12 +CGI::Path FAIL 2026-04-12 +CGI::Pure FAIL No parseable output 2026-04-12 +CGI::Session FAIL No parseable output 2026-04-12 +CGI::Session::Driver::dbic FAIL No parseable output 2026-04-12 +CGI::Session::Driver::flexmysql FAIL No parseable output 2026-04-12 +CGI::Simple::Cookie FAIL 181 0 702/181 subtests failed 2026-04-12 +CGI::Snapp::Demo::Three FAIL No parseable output 2026-04-12 +CGI::Struct::XS FAIL No parseable output 2026-04-12 +CGI::Test FAIL 8 0 168/8 subtests failed 2026-04-12 +CGI::Untaint::CountyStateProvince::US FAIL No parseable output 2026-04-12 +CGI::Utils FAIL No parseable output 2026-04-12 +CGI::Wiki::Formatter::Multiple FAIL No parseable output 2026-04-12 +CGI::remote_addr FAIL No parseable output 2026-04-12 +CGIS FAIL No parseable output 2026-04-12 +CHI FAIL Unknown test outcome 2026-04-12 +CLI::Coin::Toss FAIL No parseable output 2026-04-12 +CLI::Osprey FAIL No parseable output 2026-04-12 +CORBA::C FAIL Unknown test outcome 2026-04-12 +CORBA::IDL FAIL 2026-04-12 +CORBA::IDLtree FAIL No parseable output 2026-04-12 +CPAN::Changes FAIL 2026-04-12 +CPAN::Changes::Group::Dependencies::Stats FAIL 2026-04-12 +CPAN::Cpanorg::Auxiliary FAIL No parseable output 2026-04-12 +CPAN::Diff FAIL No parseable output 2026-04-12 +CPAN::Digger FAIL No parseable output 2026-04-12 +CPAN::Meta::Prereqs::Diff FAIL Configure failed 2026-04-12 +CPAN::Mini::Inject::REST::Client FAIL No parseable output 2026-04-12 +CPAN::Mini::Live FAIL Unknown test outcome 2026-04-12 +CPAN::Test::Dummy::Perl5::Build::Fails FAIL 2 1 1/2 subtests failed 2026-04-12 +CPAN::Test::Dummy::SCO::Lacks FAIL Unknown test outcome 2026-04-12 +CPAN::Test::Reporter FAIL 1 0 1/1 subtests failed 2026-04-12 +CPAN::Testers::Data::Release FAIL 3 0 11/3 subtests failed 2026-04-12 +CPAN::Testers::Fact::PlatformInfo FAIL 1 0 1/1 subtests failed 2026-04-12 +CPU::Emulator::Z80 FAIL 56 0 1707/56 subtests failed 2026-04-12 +CPU::Z80::Assembler FAIL 106 0 18352/106 subtests failed 2026-04-12 +CPU::Z80::Assembler::Token FAIL TIMEOUT (>120s) 2026-04-12 +CSS::Prepare FAIL 3 0 1070/3 subtests failed 2026-04-12 +CTK FAIL Unknown test outcome 2026-04-12 +CWB FAIL Configure failed 2026-04-12 +Cache::Cache FAIL 1 0 166/1 subtests failed 2026-04-12 +Cache::File FAIL Configure failed 2026-04-12 +Cache::LRU FAIL Configure failed 2026-04-12 +Cache::Memcached::Fast FAIL 2026-04-12 +Cache::Memory FAIL Configure failed 2026-04-12 +Carp::Assert FAIL 44 42 2/44 subtests failed 2026-04-12 Carp::Clan FAIL 116 58 58/116 subtests failed 2026-04-12 +CatalystX::Imports::Context FAIL Configure failed 2026-04-12 +CatalystX::OAuth2::Provider FAIL Configure failed 2026-04-12 +CatalystX::Plugin::Blurb FAIL Configure failed 2026-04-12 +CfgTie::CfgArgs FAIL 3 0 27/3 subtests failed 2026-04-12 +Chart FAIL Missing: GD.pm 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::C3::Adopt::NEXT FAIL 4 0 22/4 subtests failed 2026-04-12 +Class::Container FAIL Missing: Params/Validate.pm 2026-04-12 +Class::InsideOut FAIL 316 173 143/316 subtests failed 2026-04-12 Class::Load FAIL 86 70 16/86 subtests failed 2026-04-12 +Class::MOP FAIL Configure failed 2026-04-12 +Class::Std FAIL 255 224 31/255 subtests failed 2026-04-12 +Class::Unload FAIL 10 10 2026-04-12 +Class::Util FAIL 341 323 18/341 subtests failed 2026-04-12 Class::XSAccessor FAIL 10 0 184/10 subtests failed 2026-04-12 +ClearCase::ClearPrompt FAIL Unknown test outcome 2026-04-12 +ClearCase::Region_Cfg_Parser FAIL Configure failed 2026-04-12 +CodeGenRequestResponseType FAIL Configure failed 2026-04-12 +Codec::CBOR FAIL 8 5 3/8 subtests failed 2026-04-12 +ColorTheme FAIL PerlOnJava: register limit exceeded 2026-04-12 +Colouring::In FAIL 65 55 10/65 subtests failed 2026-04-12 +Combine::Keys FAIL 1 1 2026-04-12 +Commandable FAIL Configure failed 2026-04-12 +Commandable::Invocation FAIL Configure failed 2026-04-12 +Comparer FAIL PerlOnJava: register limit exceeded 2026-04-12 +Config::Backend::INI FAIL 2 0 10/2 subtests failed 2026-04-12 +Config::General FAIL 17 0 62/17 subtests failed 2026-04-12 +Config::IniFiles FAIL 45 0 175/45 subtests failed 2026-04-12 +Config_u FAIL No parseable output 2026-04-12 Const::Fast FAIL 1 0 1/1 subtests failed 2026-04-12 +Continuity FAIL 1 0 1/1 subtests failed 2026-04-12 +Coro FAIL 17 0 43/17 subtests failed 2026-04-12 +Corona FAIL Configure failed 2026-04-12 +CouchDB::View FAIL Configure failed 2026-04-12 +CouchWiki FAIL 5 4 1/5 subtests failed 2026-04-12 +Cpanel::JSON::XS FAIL 2026-04-12 +Crayon FAIL 1 0 1/1 subtests failed 2026-04-12 +Crypt::Blowfish FAIL 2026-04-12 +Crypt::CBC FAIL Unknown test outcome 2026-04-12 +Crypt::Cipher::AES FAIL Unknown test outcome 2026-04-12 +Crypt::Curve25519 FAIL 11 0 11/11 subtests failed 2026-04-12 +Crypt::HCE_SHA FAIL 2026-04-12 +Crypt::IDEA FAIL 2026-04-12 +Crypt::JWT FAIL 3 0 6/3 subtests failed 2026-04-12 +Crypt::Mode::CBC::Easy FAIL Unknown test outcome 2026-04-12 +Crypt::PBKDF2 FAIL 7 0 4028/7 subtests failed 2026-04-12 Crypt::Sodium FAIL 1 0 1/1 subtests failed 2026-04-12 +Crypt::URandom FAIL 48 34 14/48 subtests failed 2026-04-12 +Curses FAIL 1 0 1/1 subtests failed 2026-04-12 +Curses::UI FAIL Configure failed 2026-04-12 +DAPNET::API FAIL Unknown test outcome 2026-04-12 +DB::AsKVS FAIL 1 0 1/1 subtests failed 2026-04-12 +DB::Color FAIL Configure failed 2026-04-12 +DB::Ent FAIL 2026-04-12 +DBD::AnyData::db FAIL Unknown test outcome 2026-04-12 +DBD::EmpressNet FAIL Configure failed 2026-04-12 +DBD::JDBC FAIL Configure failed 2026-04-12 +DBD::Mock FAIL 206 161 45/206 subtests failed 2026-04-12 +DBD::Oracle::db FAIL Configure failed 2026-04-12 +DBD::PgSPI FAIL Configure failed 2026-04-12 +DBD::RDFStore FAIL Missing: RDFStore.pm 2026-04-12 +DBD::Redbase FAIL Configure failed 2026-04-12 +DBD::Safe FAIL TIMEOUT (>120s) 2026-04-12 +DBD::monetdb FAIL No parseable output 2026-04-12 +DBD::mysql FAIL No parseable output 2026-04-12 +DBGp::Client FAIL No parseable output 2026-04-12 +DBI::Log FAIL No parseable output 2026-04-12 +DBICErrorTest::SyntaxError FAIL No parseable output 2026-04-12 +DBICx::TestDatabase FAIL Configure failed 2026-04-12 +DBIx::AbstractStatement FAIL No parseable output 2026-04-12 +DBIx::Admin::DSNManager FAIL No parseable output 2026-04-12 +DBIx::CSV FAIL No parseable output 2026-04-12 +DBIx::Chart FAIL Configure failed 2026-04-12 +DBIx::Class::Candy FAIL 4 2 2/4 subtests failed 2026-04-12 +DBIx::Class::Helper::IgnoreWantarray FAIL Unknown test outcome 2026-04-12 +DBIx::Class::Helper::SimpleStats FAIL 1 1 2026-04-12 +DBIx::Class::InflateColumn::Currency FAIL Configure failed 2026-04-12 +DBIx::Class::InflateColumn::DateTime::WithTimeZone FAIL 4 1 3/4 subtests failed 2026-04-12 +DBIx::Class::InflateColumn::TimeMoment FAIL 1 1 2026-04-12 +DBIx::Class::IntrospectableM2M FAIL Configure failed 2026-04-12 +DBIx::Class::LookupColumn FAIL 1 0 1/1 subtests failed 2026-04-12 +DBIx::Class::QueryLog::Conditional FAIL Missing: aliased.pm 2026-04-12 +DBIx::Class::ResultClass::TrackColumns FAIL Missing: Moose.pm 2026-04-12 +DBIx::Class::Row::Slave FAIL Configure 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::TimeStamp::WithTimeZone FAIL 1 0 1/1 subtests failed 2026-04-12 DBIx::Class::UUIDColumns FAIL Configure failed 2026-04-12 +DBIx::Class::UnicornLogger FAIL 2026-04-12 +DBIx::Class::Validation FAIL Configure failed 2026-04-12 +DBIx::Connection FAIL 86 86 Missing: Devel/Symdump.pm 2026-04-12 +DBIx::Connector FAIL 118 0 640/118 subtests failed 2026-04-12 +DBIx::Deployer FAIL Missing: Moops.pm 2026-04-12 +DBIx::Dump FAIL Unknown test outcome 2026-04-12 +DBIx::FixtureLoader FAIL 1 0 1/1 subtests failed 2026-04-12 +DBIx::HTMLinterface FAIL Unknown test outcome 2026-04-12 +DBIx::Introspector FAIL 16 16 2026-04-12 +DBIx::NamedBinding FAIL 5 0 8/5 subtests failed 2026-04-12 +DBIx::ORM::Declarative FAIL 4 4 2026-04-12 +DBIx::Repgen FAIL 2026-04-12 +DBIx::SQLite::Deploy FAIL Configure failed 2026-04-12 +DBIx::TNDBO FAIL Unknown test outcome 2026-04-12 +DBIx::TransactionManager FAIL Configure failed 2026-04-12 DBIx::Tree::NestedSet FAIL 2026-04-12 +DBIx::TryAgain FAIL 2 2 2026-04-12 +DBIx::TxnPool FAIL Configure failed 2026-04-12 +DBIx::Version FAIL Unknown test outcome 2026-04-12 +DBIx::Wrapper FAIL 1 0 50/1 subtests failed 2026-04-12 +DBIx::Wrapper::Config FAIL Missing: DBIx/Wrapper/Config.pm 2026-04-12 +DBIx::dbMan FAIL 1 0 4/1 subtests failed 2026-04-12 +DBM::Deep FAIL 1 0 1/1 subtests failed 2026-04-12 +DBNull_File FAIL TIMEOUT (>120s) 2026-04-12 +DBR FAIL 9 8 1/9 subtests failed 2026-04-12 +DBUnit FAIL 110 110 Missing: Devel/Symdump.pm 2026-04-12 +DB_File FAIL 49 0 522/49 subtests failed 2026-04-12 +DB_File::Lock FAIL StackOverflowError 2026-04-12 +DDB_File FAIL 2026-04-12 +DJabberd FAIL 10 0 165/10 subtests failed 2026-04-12 +DJabberd::Authen::DBI FAIL 1 0 1/1 subtests failed 2026-04-12 +DMTF::WSMan FAIL 1 0 1/1 subtests failed 2026-04-12 +DR::DateTime FAIL 182 181 1/182 subtests failed 2026-04-12 +DR::Msgpuck::Bool FAIL TIMEOUT (>120s) 2026-04-12 +Dancer FAIL Unknown test outcome 2026-04-12 +Dancer2::Logger::Syslog FAIL 1 0 1/1 subtests failed 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 +Dancer2::Template::TextTemplate FAIL 2 1 1/2 subtests failed 2026-04-12 +Danga::Socket FAIL 43 27 16/43 subtests failed 2026-04-12 +DarkPAN::Compare FAIL 1 0 1/1 subtests failed 2026-04-12 +DarkSky::API FAIL Missing: common/sense.pm 2026-04-12 Data::Alias FAIL 1 0 635/1 subtests failed 2026-04-12 Data::Dump FAIL 2026-04-12 +Data::GUID FAIL 15 0 63/15 subtests failed 2026-04-12 +Data::Perl FAIL 194 193 1/194 subtests failed 2026-04-12 Data::Serializer::JSON FAIL 480 231 249/480 subtests failed 2026-04-12 +Data::ShowTable FAIL 12 12 2026-04-12 +Data::Stag FAIL 95 87 8/95 subtests failed 2026-04-12 Data::Stream::Bulk FAIL 13 0 13/13 subtests failed 2026-04-12 +Data::StreamDeserializer FAIL 11 0 65/11 subtests failed 2026-04-12 +Data::StreamSerializer FAIL 7 0 68/7 subtests failed 2026-04-12 Data::UUID FAIL 1 0 32/1 subtests failed 2026-04-12 +Data::Validator FAIL 1 0 1/1 subtests failed 2026-04-12 Data::Visitor FAIL 1 0 1/1 subtests failed 2026-04-12 +DataSexta FAIL 2026-04-12 Date::Calc FAIL 2997 2951 46/2997 subtests failed 2026-04-12 +DateTime::Calendar::Mayan FAIL 5 0 120/5 subtests failed 2026-04-12 +DateTime::Event::Klingon FAIL 3 0 4/3 subtests failed 2026-04-12 +DateTime::Event::MultiCron FAIL Missing: DateTime/Event/Cron.pm 2026-04-12 +DateTime::Fiction::JRRTolkien::Shire FAIL 181 179 2/181 subtests failed 2026-04-12 +DateTime::Format::Alami FAIL PerlOnJava: register limit exceeded 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 +DateTime::Format::MySQL FAIL 1 0 97/1 subtests failed 2026-04-12 +DateTime::Format::PDF FAIL 3 1 2/3 subtests failed 2026-04-12 +DateTime::Format::SQLite FAIL 2 0 51/2 subtests failed 2026-04-12 +DateTimeX::AATW FAIL 42 34 8/42 subtests failed 2026-04-12 +DateTimeX::Auto FAIL 6 2 4/6 subtests failed 2026-04-12 +DateTimeX::Duration::Lite FAIL PerlOnJava: register limit exceeded 2026-04-12 +DbFramework::Attribute FAIL Missing: t/Config.pm 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::CheckLib FAIL 25 13 12/25 subtests failed 2026-04-12 +Devel::GlobalDestruction FAIL 12 3 9/12 subtests failed 2026-04-12 +Devel::Hide FAIL 77 55 22/77 subtests failed 2026-04-12 +Devel::MAT::Dumper FAIL Configure failed 2026-04-12 Devel::PPPort FAIL Configure failed 2026-04-12 Devel::Symdump FAIL Configure failed 2026-04-12 +Device::ParallelPort::drv::parport FAIL 2026-04-12 +Diamond FAIL Configure failed 2026-04-12 +Digest::BubbleBabble FAIL Configure failed 2026-04-12 +Digest::JHash FAIL 1 0 6/1 subtests failed 2026-04-12 Digest::SHA1 FAIL 2026-04-12 +Digest::SHA3 FAIL 2 0 31/2 subtests failed 2026-04-12 +Digest::SHA::PurePerl FAIL Unknown test outcome 2026-04-12 +DirectiveSet FAIL 223 22 201/223 subtests failed 2026-04-12 Directory::Scratch FAIL Unknown test outcome 2026-04-12 +Dist::Build FAIL 14 13 1/14 subtests failed 2026-04-12 +Dist::Build::XS::Conf FAIL Configure failed 2026-04-12 +DocRaptor FAIL Unknown test outcome 2026-04-12 +Docopt FAIL 1 0 1/1 subtests failed 2026-04-12 +Dotenv FAIL 30 30 2026-04-12 +Draft FAIL 14 13 1/14 subtests failed 2026-04-12 +DuckCurses::dagobert FAIL Configure failed 2026-04-12 +DynGig::Range::Cluster FAIL 8 7 1/8 subtests failed 2026-04-12 +ELF::sign FAIL 2 1 1/2 subtests failed 2026-04-12 +ERG::line_formatter FAIL No parseable output 2026-04-12 +EV FAIL 2 0 6850/2 subtests failed 2026-04-12 +EV::ADNS FAIL Missing: common/sense.pm 2026-04-12 +EV::ClickHouse FAIL 1 0 1/1 subtests failed 2026-04-12 +Eeuctw FAIL Unknown test outcome 2026-04-12 +Eircode FAIL Missing: Const/Fast.pm 2026-04-12 +Elatin8 FAIL 2026-04-12 +Email::Date::Format FAIL 8 4 4/8 subtests failed 2026-04-12 +Entrez FAIL Missing: Stone/Cursor.pm 2026-04-12 +Error FAIL 9 0 44/9 subtests failed 2026-04-12 +Error::Pure FAIL 115 113 2/115 subtests failed 2026-04-12 +Event FAIL 2026-04-12 +ExecCmds FAIL 2026-04-12 +Expect FAIL Missing: IO/Pty.pm 2026-04-12 +Export::Attrs FAIL 1 0 2/1 subtests failed 2026-04-12 +ExtUtils::Builder FAIL 82 80 2/82 subtests failed 2026-04-12 +ExtUtils::Builder::Compiler FAIL 12 12 2026-04-12 +ExtUtils::Constant FAIL Unknown test outcome 2026-04-12 +ExtUtils::CppGuess FAIL 20 13 7/20 subtests failed 2026-04-12 +ExtUtils::Depends FAIL 2 0 17/2 subtests failed 2026-04-12 +ExtUtils::H2PM FAIL Configure failed 2026-04-12 +ExtUtils::PkgConfig FAIL 21 0 42/21 subtests failed 2026-04-12 +ExtUtils::XSpp FAIL 3 3 Missing: t/lib/XSP/Test.pm 2026-04-12 +FB3 FAIL 2 0 2/2 subtests failed 2026-04-12 +FCGI::ProcManager FAIL 2026-04-12 +FCGI::ProcManager::Dynamic FAIL Missing: IPC/SysV.pm 2026-04-12 FFI::CheckLib FAIL Unknown test outcome 2026-04-12 +FFmpeg::Command FAIL 4 4 2026-04-12 +FFmpeg::Thumbnail FAIL 1 0 1/1 subtests failed 2026-04-12 +FSA::Rules FAIL 340 267 73/340 subtests failed 2026-04-12 +FServer FAIL No parseable output 2026-04-12 +FarmBalance FAIL Configure failed 2026-04-12 +Feature::Compat::Try FAIL 38 31 7/38 subtests failed 2026-04-12 +File::Cache FAIL 2026-04-12 File::Copy::Recursive::Reduced FAIL 2026-04-12 File::Path::Expand FAIL 1 0 8/1 subtests failed 2026-04-12 +File::PathConvert FAIL 266 264 2/266 subtests failed 2026-04-12 +File::Spec FAIL 2026-04-12 +File::Sync FAIL 2026-04-12 +File::chmod FAIL 39 30 9/39 subtests failed 2026-04-12 Filter::signatures FAIL 10 0 59/10 subtests failed 2026-04-12 +FindBin::libs FAIL Configure failed 2026-04-12 +Fl_Align_Group FAIL Configure failed 2026-04-12 Font::Metrics::Courier FAIL 2 2 Missing: Font/AFM.pm 2026-04-12 +FormValidator::Simple FAIL Configure failed 2026-04-12 +Function::Parameters FAIL 14 0 1426/14 subtests failed 2026-04-12 +Future FAIL 786 757 29/786 subtests failed 2026-04-12 Geo::IP FAIL Configure failed 2026-04-12 +GraphViz FAIL Missing: IPC/Run.pm 2026-04-12 +Graphics::Toolkit::Color FAIL 2572 1651 921/2572 subtests failed 2026-04-12 +Guard FAIL 2026-04-12 +HTML::Element FAIL 399 0 593/399 subtests failed 2026-04-12 +HTML::FillInForm FAIL Unknown test outcome 2026-04-12 +HTML::FillInForm::Lite FAIL 147 0 152/147 subtests 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::TableTiler FAIL 1 0 5/1 subtests failed 2026-04-12 HTML::Template FAIL 608 605 3/608 subtests failed 2026-04-12 +HTML::Template::Default FAIL Missing: LEOCHARRE/Debug.pm 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::Body FAIL 57 0 185/57 subtests failed 2026-04-12 HTTP::Headers::ActionPack FAIL 448 445 3/448 subtests failed 2026-04-12 +HTTP::Parser::XS FAIL Configure failed 2026-04-12 HTTP::Server::Simple FAIL 14 0 76/14 subtests failed 2026-04-12 +HTTP::Tiny::SPDY FAIL 3 0 16/3 subtests failed 2026-04-12 +Hash::AsObject FAIL 93 93 Missing: diagnostics.pm 2026-04-12 +Hash::FieldHash FAIL Configure failed 2026-04-12 +Hash::Ordered FAIL 106 106 StackOverflowError 2026-04-12 +Heap FAIL 862 0 1612/862 subtests failed 2026-04-12 +Hook::LexWrap FAIL 58 58 2026-04-12 +Horus FAIL 25 0 157/25 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 +IO::Pty FAIL Configure failed 2026-04-12 +IO::String FAIL 43 41 2/43 subtests failed 2026-04-12 +IO::Util FAIL 43 0 58/43 subtests failed 2026-04-12 +IPC::Run FAIL 1 0 640/1 subtests failed 2026-04-12 +IPC::SysV FAIL Configure failed 2026-04-12 +IRI FAIL 8 4 4/8 subtests failed 2026-04-12 +Image::Magick FAIL 2026-04-12 +Import::Export FAIL 29 27 2/29 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 +JSONP FAIL 1 0 1/1 subtests failed 2026-04-12 Jcode FAIL Missing: diagnostics.pm 2026-04-12 +Kwalify FAIL 139 133 6/139 subtests failed 2026-04-12 LabKey::Query FAIL 12 6 6/12 subtests failed 2026-04-12 +Lingua::EN::Inflect::Phrase FAIL 137 137 2026-04-12 +Lingua::EN::Tagger FAIL 41 0 75/41 subtests failed 2026-04-12 +Lingua::Stem::Ru FAIL 4 0 4/4 subtests failed 2026-04-12 +Lingua::Stem::Snowball::Da FAIL Unknown test outcome 2026-04-12 LinuxRealTime FAIL 1 0 1/1 subtests failed 2026-04-12 List::MoreUtils FAIL Unknown test outcome 2026-04-12 +List::SomeUtils FAIL 45 41 4/45 subtests failed 2026-04-12 +List::UtilsBy FAIL Unknown test outcome 2026-04-12 +Locale::gettext FAIL Configure failed 2026-04-12 +Log::Any FAIL 456 417 39/456 subtests failed 2026-04-12 +Log::Log4perl FAIL Unknown test outcome 2026-04-12 +Log::Sprintf FAIL 2026-04-12 +Log::Structured FAIL 13 11 2/13 subtests failed 2026-04-12 +MD5 FAIL 3 0 11/3 subtests failed 2026-04-12 +MIME::Charset FAIL 93 77 16/93 subtests failed 2026-04-12 +MIME::Lite FAIL 24 18 6/24 subtests failed 2026-04-12 +MIME::QuotedPrint FAIL 348 315 33/348 subtests failed 2026-04-12 +MIME::Types FAIL 97 97 2026-04-12 Mac::SystemDirectory FAIL Configure failed 2026-04-12 +Math::Base::Convert FAIL Missing: Math/Base/Convert.pm 2026-04-12 +Math::BigFloat FAIL 4967 0 38173/4967 subtests failed 2026-04-12 +Math::BigInt FAIL 4967 0 38173/4967 subtests failed 2026-04-12 +Math::Complex FAIL 392 0 841/392 subtests failed 2026-04-12 +Math::Int64 FAIL Configure failed 2026-04-12 +Math::Random::ISAAC FAIL 609 9 600/609 subtests failed 2026-04-12 +Math::Vec FAIL 20 13 7/20 subtests failed 2026-04-12 +Memoize FAIL Unknown test outcome 2026-04-12 +Memoize::ExpireLRU FAIL 2026-04-12 +Metabase::Fact::Hash FAIL 47 0 53/47 subtests failed 2026-04-12 Mixin::Linewise::Readers FAIL 1 1 2026-04-12 +Mock::Config FAIL 1 0 2/1 subtests failed 2026-04-12 +Modern::Perl FAIL 164 91 73/164 subtests failed 2026-04-12 Module::Build::XSUtil FAIL 3 1 2/3 subtests failed 2026-04-12 +Module::CPANfile FAIL 37 37 2026-04-12 +Module::Extract::Namespaces FAIL 14 10 4/14 subtests failed 2026-04-12 +Module::Mask FAIL 4 0 28/4 subtests failed 2026-04-12 +Module::Signature FAIL 2 2 Missing: IPC/Run.pm 2026-04-12 +Module::Util FAIL 47 46 1/47 subtests failed 2026-04-12 MooX::BuildArgs FAIL 2026-04-12 MooX::Enumeration FAIL Configure failed 2026-04-12 +MooX::HandlesVia FAIL 787 779 8/787 subtests failed 2026-04-12 +MooX::Lsub FAIL Configure failed 2026-04-12 +Moops FAIL Configure failed 2026-04-12 Moose FAIL Configure failed 2026-04-12 +Moose::Autobox FAIL 17 0 171/17 subtests failed 2026-04-12 Moose::Meta::TypeConstraint::Role FAIL Configure failed 2026-04-12 Moose::Util::TypeConstraints FAIL Configure failed 2026-04-12 +MooseX::Aliases FAIL 8 0 153/8 subtests failed 2026-04-12 +MooseX::ArrayRef FAIL 1 0 10/1 subtests failed 2026-04-12 +MooseX::Attribute::Chained FAIL 7 1 6/7 subtests failed 2026-04-12 +MooseX::Attribute::ENV 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::Emulate::Class::Accessor::Fast FAIL 4 0 76/4 subtests failed 2026-04-12 +MooseX::Getopt FAIL 10 4 6/10 subtests failed 2026-04-12 MooseX::NonMoose FAIL 1 1 Missing: Moose.pm 2026-04-12 +MooseX::OneArgNew FAIL 1 1 Missing: Moose.pm 2026-04-12 MooseX::Params::Validate FAIL 5 1 4/5 subtests failed 2026-04-12 +MooseX::Role::Parameterized FAIL 4 4 Missing: Moose.pm 2026-04-12 +MooseX::SlurpyConstructor FAIL 5 4 1/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 FAIL Unknown test outcome 2026-04-12 +MooseX::Types::Moose FAIL Unknown test outcome 2026-04-12 MooseX::Types::Path::Class FAIL 3 3 Missing: Moose.pm 2026-04-12 MouseX::Types FAIL Configure failed 2026-04-12 +NEXT FAIL 13 0 47/13 subtests failed 2026-04-12 Net::DNS FAIL Unknown test outcome 2026-04-12 +Net::NIS FAIL 1 0 62/1 subtests failed 2026-04-12 +Net::SSH::Perl FAIL 21 0 31/21 subtests failed 2026-04-12 +Net::Server::PreFork FAIL 37 0 158/37 subtests failed 2026-04-12 +OPC FAIL 4 3 1/4 subtests failed 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 +PPI 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 +Parse::CPAN::Packages FAIL 3 0 3/3 subtests failed 2026-04-12 +Parse::Yapp FAIL 10 0 16/10 subtests failed 2026-04-12 +PerlIO::eol FAIL 2 0 24/2 subtests failed 2026-04-12 PerlIO::utf8_strict FAIL 5816 2389 3427/5816 subtests failed 2026-04-12 +Pg::PQ FAIL Configure failed 2026-04-12 PkgConfig FAIL Unknown test outcome 2026-04-12 +Plack::Session FAIL 423 423 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 +Proc::FastSpawn FAIL 2026-04-12 +Proc::Guard FAIL 1 0 1/1 subtests failed 2026-04-12 +RDF::NS FAIL 98 97 1/98 subtests failed 2026-04-12 +RDF::Query FAIL Configure failed 2026-04-12 REST::Client FAIL 2 2 2026-04-12 +Regexp::Common FAIL 3 3 2026-04-12 +Router::Boom FAIL 1 0 1/1 subtests failed 2026-04-12 Router::Simple FAIL 1 0 1/1 subtests failed 2026-04-12 +SGI::FAM FAIL Missing: Test/Helper.pm 2026-04-12 +SQL::Maker FAIL 18 17 1/18 subtests failed 2026-04-12 +SQL::NamedPlaceholder FAIL Unknown test outcome 2026-04-12 +SQL::QueryMaker FAIL Configure failed 2026-04-12 +SQL::Statement FAIL 2026-04-12 +SUPER FAIL 51 46 5/51 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 +Safe FAIL Unknown test outcome 2026-04-12 +Scalar::Util FAIL 816 0 1560/816 subtests failed 2026-04-12 +Session::Token FAIL Unknown test outcome 2026-04-12 +Set::Object FAIL Unknown test outcome 2026-04-12 +Set::Scalar FAIL Unknown test outcome 2026-04-12 +Shell::Perl FAIL 25 12 13/25 subtests failed 2026-04-12 +Simple::SAX::Serializer FAIL 34 33 1/34 subtests failed 2026-04-12 Smart::Args FAIL 1 0 1/1 subtests failed 2026-04-12 Smart::Comments FAIL Unknown test outcome 2026-04-12 +Sort::Key FAIL 1 0 36/1 subtests failed 2026-04-12 +Sort::Maker FAIL Unknown test outcome 2026-04-12 +Sort::MergeSort FAIL 197 196 1/197 subtests failed 2026-04-12 SpamMonkey FAIL Missing: File/Path/Expand.pm 2026-04-12 Spiffy FAIL 168 148 20/168 subtests failed 2026-04-12 +Statistics::Contingency FAIL Missing: Params/Validate.pm 2026-04-12 +String::CRC32 FAIL 2026-04-12 String::CamelCase FAIL 31 27 4/31 subtests failed 2026-04-12 +String::ToIdentifier::EN FAIL Unknown test outcome 2026-04-12 +Struct::Match FAIL 5 5 2026-04-12 Sub::Exporter::ForMethods FAIL 10 6 4/10 subtests failed 2026-04-12 +Sub::Identify FAIL 139 86 53/139 subtests failed 2026-04-12 +Switch FAIL Syntax error 2026-04-12 +Syntax::Feature::Junction FAIL 9 0 380/9 subtests failed 2026-04-12 +Syntax::Highlight::Perl::Improved FAIL Unknown test outcome 2026-04-12 +Sys::Hostname::Long FAIL 1 0 1/1 subtests failed 2026-04-12 +Sys::Syslog FAIL 112 0 289/112 subtests failed 2026-04-12 SystemTray::Applet FAIL No parseable output 2026-04-12 +Task::Weaken FAIL 22 21 1/22 subtests failed 2026-04-12 +Template::Magic FAIL 5 0 51/5 subtests failed 2026-04-12 +Term::Cap FAIL 2026-04-12 +Term::ReadLine FAIL 15 12 3/15 subtests failed 2026-04-12 +Term::ReadLine::Gnu FAIL Configure failed 2026-04-12 +Term::Size FAIL 18 12 6/18 subtests failed 2026-04-12 +Term::Table FAIL Unknown test outcome 2026-04-12 Test::Base FAIL Unknown test outcome 2026-04-12 +Test::Carp FAIL Unknown test outcome 2026-04-12 +Test::Class FAIL 173 159 14/173 subtests failed 2026-04-12 +Test::CleanNamespaces FAIL 134 119 15/134 subtests failed 2026-04-12 +Test::DBIx::Class FAIL 23 0 35/23 subtests failed 2026-04-12 Test::Differences FAIL 49 45 4/49 subtests failed 2026-04-12 +Test::FailWarnings FAIL 8 6 2/8 subtests failed 2026-04-12 +Test::Helper FAIL Unknown test outcome 2026-04-12 +Test::LongString FAIL 38 32 6/38 subtests failed 2026-04-12 Test::Memory::Cycle FAIL 38 19 19/38 subtests failed 2026-04-12 +Test::MockModule FAIL 2 1 1/2 subtests failed 2026-04-12 +Test::MockObject FAIL 103 0 136/103 subtests failed 2026-04-12 +Test::More FAIL 31 31 2026-04-12 +Test::Pod::Coverage FAIL 9 0 20/9 subtests failed 2026-04-12 +Test::Refcount FAIL 21 15 6/21 subtests failed 2026-04-12 +Test::Roo FAIL 9 1 8/9 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 +Test::Trap FAIL 5 0 5/5 subtests failed 2026-04-12 +Test::YAML FAIL 1 0 1/1 subtests failed 2026-04-12 +Text::FillIn FAIL Unknown test outcome 2026-04-12 +Text::FormatTable FAIL Unknown test outcome 2026-04-12 Text::Template FAIL 163 100 63/163 subtests failed 2026-04-12 +Text::VisualWidth::PP FAIL 5 0 16/5 subtests failed 2026-04-12 +Text::VisualWidth::UTF8 FAIL 3 0 15/3 subtests failed 2026-04-12 Tie::File FAIL 4725 4389 336/4725 subtests failed 2026-04-12 +Tie::IxHash FAIL 29 27 2/29 subtests failed 2026-04-12 +Time::Format FAIL 214 0 319/214 subtests failed 2026-04-12 +Time::HiRes FAIL Configure failed 2026-04-12 +Time::Moment FAIL 36 1 35/36 subtests failed 2026-04-12 +Time::Object FAIL 2026-04-12 +Time::ParseDate FAIL 8 2 6/8 subtests failed 2026-04-12 +Tk FAIL Configure failed 2026-04-12 +Tk::WorldCanvas FAIL Missing: Tk.pm 2026-04-12 Types::Serialiser FAIL Missing: common/sense.pm 2026-04-12 +UNIVERSAL::can FAIL 59 56 3/59 subtests failed 2026-04-12 +UNIVERSAL::isa FAIL 76 53 23/76 subtests failed 2026-04-12 UNIX::Cal FAIL Configure failed 2026-04-12 URI::Find FAIL 619 617 2/619 subtests failed 2026-04-12 +URI::Query FAIL 93 91 2/93 subtests failed 2026-04-12 URI::Template::Restrict FAIL Configure failed 2026-04-12 +Unicode::EastAsianWidth FAIL Configure failed 2026-04-12 +Unicode::LineBreak FAIL 9 0 202/9 subtests 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 +Want FAIL 1 0 147/1 subtests failed 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::Parser::Wrapper FAIL 2 0 10/2 subtests failed 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::Simple FAIL 503 502 1/503 subtests failed 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::Any FAIL 38 29 9/38 subtests failed 2026-04-12 +YAML::PP FAIL 2581 2441 140/2581 subtests failed 2026-04-12 YAML::Syck FAIL Configure failed 2026-04-12 +YAML::Tiny FAIL 58 52 6/58 subtests failed 2026-04-12 YAML::XS FAIL 2 0 48/2 subtests failed 2026-04-12 +aliased FAIL 40 39 1/40 subtests failed 2026-04-12 +autobox FAIL 2 0 670/2 subtests failed 2026-04-12 +autobox::Core FAIL 2026-04-12 +autovivification FAIL 41 0 71/41 subtests failed 2026-04-12 +bigint FAIL Unknown test outcome 2026-04-12 +boolean FAIL 89 87 2/89 subtests failed 2026-04-12 +mod_perl2 FAIL Configure failed 2026-04-12 +smallnum FAIL 72 51 21/72 subtests failed 2026-04-12 strictures FAIL 5 4 1/5 subtests failed 2026-04-12 +threads FAIL 1 0 1/1 subtests failed 2026-04-12 +threads::shared FAIL 80 80 2026-04-12 diff --git a/dev/cpan-reports/cpan-compatibility-pass.dat b/dev/cpan-reports/cpan-compatibility-pass.dat index 99dd1e3e5..2aab2ffac 100644 --- a/dev/cpan-reports/cpan-compatibility-pass.dat +++ b/dev/cpan-reports/cpan-compatibility-pass.dat @@ -1,37 +1,171 @@ +API::CPanel PASS 61 61 2026-04-12 c1942aad0 Algorithm::Diff PASS 1004 1004 2026-04-12 cc5efa220 Alien::Build::Plugin::Download::GitLab PASS 2 2 2026-04-12 cc5efa220 +AnyData2 PASS 21 21 2026-04-12 c1942aad0 +Apache2::AuthzNIS PASS 2 2 2026-04-12 c1942aad0 Array::Utils PASS 17 17 2026-04-12 cc5efa220 -CGI::Application PASS 189 189 2026-04-12 cc5efa220 +Asm::Z80::Table PASS 19641 19641 2026-04-12 c1942aad0 +AsposeBarCodeCloud::ApiClient PASS 3 3 2026-04-12 c1942aad0 +AsposeImagingCloud::ApiClient PASS 3 3 2026-04-12 c1942aad0 +AsposeStorageCloud::StorageApi PASS 3 3 2026-04-12 c1942aad0 +AudioFile::Info PASS 14 14 2026-04-12 c1942aad0 +BBPerl PASS 93 93 2026-04-12 c1942aad0 +BitArray PASS 2 2 2026-04-12 c1942aad0 +Builder PASS 31 31 2026-04-12 c1942aad0 +CDR::Parser::SI3000 PASS 17 17 2026-04-12 c1942aad0 +CGI::Application PASS 189 189 2026-04-12 c1942aad0 CGI::Application::Plugin::AbstractCallback PASS 2 2 2026-04-12 cc5efa220 +CGI::Application::Plugin::DetectAjax PASS 4 4 2026-04-12 c1942aad0 +CGI::Application::Plugin::Forward PASS 33 33 2026-04-12 c1942aad0 +CGI::Application::Plugin::MessageStack PASS 9 9 2026-04-12 c1942aad0 +CGI::Auth::Auto PASS 1 1 2026-04-12 c1942aad0 +CGI::FormBuilder::Source::Perl PASS 1 1 2026-04-12 c1942aad0 +CGI::Scriptpaths PASS 18 18 2026-04-12 c1942aad0 +CGI::Struct PASS 126 126 2026-04-12 c1942aad0 +CORBA::Python PASS 14 14 2026-04-12 c1942aad0 +CPAN::DistnameInfo PASS 829 829 2026-04-12 c1942aad0 +CPAN::Mini PASS 48 48 2026-04-12 c1942aad0 +CPAN::Tarball::Patch PASS 1 1 2026-04-12 c1942aad0 +CPAN::Test::Dummy::Perl5::Build::DepeFails PASS 2 2 2026-04-12 c1942aad0 +CPAN::Test::Dummy::Perl5::ExtUtilsMakeMaker PASS 1 1 2026-04-12 c1942aad0 +CPAN::Test::Dummy::Perl5::StaticInstall PASS 1 1 2026-04-12 c1942aad0 +CPAN::Test::Dummy::Perl5::VersionQV PASS 1 1 2026-04-12 c1942aad0 +CPAN::Testers::Common::DBUtils PASS 4 4 2026-04-12 c1942aad0 +CPU::Emulator::Memory::Banked PASS 91 91 2026-04-12 c1942aad0 +CSS::Tiny PASS 36 36 2026-04-12 c1942aad0 +CSVAWK PASS 2 2 2026-04-12 c1942aad0 +Calendar::Simple PASS 83 83 2026-04-12 c1942aad0 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 +Class::StrongSingleton PASS 26 26 2026-04-12 c1942aad0 +Class::Trigger PASS 51 51 2026-04-12 c1942aad0 +Config::Frontend PASS 136 136 2026-04-12 c1942aad0 +Crypt::Random::Seed PASS 26 26 2026-04-12 c1942aad0 +Crypt::Random::TESHA2 PASS 20 20 2026-04-12 c1942aad0 Crypt::SaltedHash PASS 10 10 2026-04-12 cc5efa220 -Cwd::Guard PASS 6 6 2026-04-12 cc5efa220 +Crypt::SysRandom PASS 4 4 2026-04-12 c1942aad0 +Cwd::Ext PASS 31 31 2026-04-12 c1942aad0 +Cwd::Guard PASS 6 6 2026-04-12 c1942aad0 +D64::Disk::Layout::Base PASS 23 23 2026-04-12 c1942aad0 +DBD::Multiplex PASS 1 1 2026-04-12 c1942aad0 +DBIx::Class::InflateColumn::Geo PASS 3 3 2026-04-12 c1942aad0 +DBIx::Connect::FromConfig PASS 5 5 2026-04-12 c1942aad0 DBIx::MyPassword PASS 13 13 2026-04-12 d833a1ecb +DBIx::Wrapper::VerySimple PASS 20 20 2026-04-12 c1942aad0 +DCE::Perl::RPC PASS 4 4 2026-04-12 c1942aad0 +DNS::Record::Check PASS 5 5 2026-04-12 c1942aad0 +DNS::WorldWideDns PASS 8 8 2026-04-12 c1942aad0 Data::MethodProxy PASS 10 10 2026-04-12 cc5efa220 +Data::Walk PASS 266 266 2026-04-12 c1942aad0 +Date::Tolkien::Shire::Data PASS 5558 5558 2026-04-12 c1942aad0 DateTime::Format::Strptime PASS 143 143 2026-04-12 cc5efa220 +DateTime::Functions PASS 2 2 2026-04-12 c1942aad0 +DateTime::Moonpig PASS 22 22 2026-04-12 c1942aad0 +DateTime::TimeZone::Alias PASS 2453 2453 2026-04-12 c1942aad0 +DateTime::TimeZone::Catalog::Extend PASS 355 355 2026-04-12 c1942aad0 +DateTimeX::Format::Ago PASS 602 602 2026-04-12 c1942aad0 +Device::ParallelPort PASS 35 35 2026-04-12 c1942aad0 +DublinCore::Element PASS 137 137 2026-04-12 c1942aad0 +DynGig::Multiplex PASS 4 4 2026-04-12 c1942aad0 +DynGig::Range PASS 10 10 2026-04-12 c1942aad0 +DynGig::Range::Time PASS 6 6 2026-04-12 c1942aad0 +DynGig::Util PASS 15 15 2026-04-12 c1942aad0 +ESPPlus::Storage PASS 45 45 2026-04-12 c1942aad0 +Eidolon PASS 126 126 2026-04-12 c1942aad0 +Error::Pure::Output::Text PASS 41 41 2026-04-12 c1942aad0 Exception PASS 4 4 2026-04-12 cc5efa220 +Exporter::Lite PASS 30 30 2026-04-12 c1942aad0 +Exporter::Tidy PASS 44 44 2026-04-12 c1942aad0 +ExtUtils::MakeMaker::CPANfile PASS 5 5 2026-04-12 c1942aad0 +FAST PASS 312 312 2026-04-12 c1942aad0 +FSM::Basic PASS 53 53 2026-04-12 c1942aad0 +FTN::Msg PASS 1 1 2026-04-12 c1942aad0 +File::Fetch PASS 304 304 2026-04-12 c1942aad0 File::HomeDir PASS 90 90 2026-04-12 cc5efa220 +File::ShareDir::Dist PASS 19 19 2026-04-12 c1942aad0 +File::ShareDir::Tiny PASS 35 35 2026-04-12 c1942aad0 File::Slurper PASS 8 8 2026-04-12 cc5efa220 +Getopt::Mixed PASS 26 26 2026-04-12 c1942aad0 HTML::Form PASS 223 223 2026-04-12 cc5efa220 +HTTP::BrowserDetect PASS 2601 2601 2026-04-12 c1942aad0 +HTTP::Server::Simple::PSGI PASS 1 1 2026-04-12 c1942aad0 +Hash::MoreUtils PASS 66 66 2026-04-12 c1942aad0 +IO::Prompt::Tiny PASS 5 5 2026-04-12 c1942aad0 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 +LEOCHARRE::CLI PASS 22 22 2026-04-12 c1942aad0 +LEOCHARRE::DEBUG PASS 10 10 2026-04-12 c1942aad0 +Lingua::EN::FindNumber PASS 5 5 2026-04-12 c1942aad0 Lingua::EN::Inflect PASS 2147 2147 2026-04-12 cc5efa220 +Lingua::EN::Inflect::Number PASS 21 21 2026-04-12 c1942aad0 +Lingua::EN::Number::IsOrdinal PASS 20 20 2026-04-12 c1942aad0 +Lingua::EN::Words2Nums PASS 66 66 2026-04-12 c1942aad0 +Lingua::GL::Stemmer PASS 12 12 2026-04-12 c1942aad0 +Lingua::Stem PASS 7 7 2026-04-12 c1942aad0 +Lingua::Stem::Fr PASS 16 16 2026-04-12 c1942aad0 +Lingua::Stem::It PASS 98 98 2026-04-12 c1942aad0 +Lingua::Stem::Snowball::No PASS 20633 20633 2026-04-12 c1942aad0 +Lingua::Stem::Snowball::Se PASS 30628 30628 2026-04-12 c1942aad0 +Linux::usermod PASS 10 10 2026-04-12 c1942aad0 +Math::Round::Var PASS 10 10 2026-04-12 c1942aad0 +Method::WeakCallback PASS 13 13 2026-04-12 c1942aad0 +Module::Build::WithXSpp PASS 1 1 2026-04-12 c1942aad0 Module::Pluggable::Fast PASS 6 6 2026-04-12 cc5efa220 +MooX::Types::MooseLike::Base PASS 169 169 2026-04-12 c1942aad0 +MooX::Types::MooseLike::Numeric PASS 43 43 2026-04-12 c1942aad0 +Mozilla::CA PASS 2 2 2026-04-12 c1942aad0 +Net::CIDR::Lite PASS 54 54 2026-04-12 c1942aad0 +Net::CIDR::Set PASS 219 219 2026-04-12 c1942aad0 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 +Protocol::Notifo PASS 40 40 2026-04-12 c1942aad0 +Regexp::Trie PASS 1 1 2026-04-12 c1942aad0 +Shell::Config::Generate PASS 80 80 2026-04-12 c1942aad0 +Shell::Guess PASS 181 181 2026-04-12 c1942aad0 +Sort::Versions PASS 96 96 2026-04-12 c1942aad0 +String::Formatter PASS 22 22 2026-04-12 c1942aad0 String::RewritePrefix PASS 39 39 2026-04-12 cc5efa220 +Sys::Syscall PASS 1 1 2026-04-12 c1942aad0 TAP::Harness::Metrics PASS 4 4 2026-04-12 cc5efa220 +Term::Screen PASS 29 29 2026-04-12 c1942aad0 +Test::API PASS 32 32 2026-04-12 c1942aad0 +Test::CPAN::Meta PASS 196 196 2026-04-12 c1942aad0 +Test::Identity PASS 10 10 2026-04-12 c1942aad0 +Test::InDistDir PASS 6 6 2026-04-12 c1942aad0 +Test::Inter PASS 90 90 2026-04-12 c1942aad0 Test::More::UTF8 PASS 14 14 2026-04-12 cc5efa220 Test::Most PASS 88 88 2026-04-12 cc5efa220 +Test::Number::Delta PASS 72 72 2026-04-12 c1942aad0 +Test::Object PASS 5 5 2026-04-12 c1942aad0 Test::Output PASS 1217 1217 2026-04-12 cc5efa220 Test::Strict PASS 80 80 2026-04-12 cc5efa220 +Test::SubCalls PASS 25 25 2026-04-12 c1942aad0 +Text::Brew PASS 18 18 2026-04-12 c1942aad0 Text::Diff PASS 33 33 2026-04-12 cc5efa220 +Text::German PASS 34 34 2026-04-12 c1942aad0 +Text::Patch PASS 1 1 2026-04-12 c1942aad0 +Text::Quote PASS 53 53 2026-04-12 c1942aad0 +Text::SimpleTable PASS 10 10 2026-04-12 c1942aad0 +Text::SimpleTable::AutoWidth PASS 6 6 2026-04-12 c1942aad0 +Text::Soundex PASS 18 18 2026-04-12 c1942aad0 +Text::Unidecode PASS 1483 1483 2026-04-12 c1942aad0 +Thread::Queue PASS 59 59 2026-04-12 c1942aad0 +Thread::Semaphore PASS 17 17 2026-04-12 c1942aad0 +Tie::Hash::Vivify PASS 31 31 2026-04-12 c1942aad0 Tie::ToObject PASS 10 10 2026-04-12 cc5efa220 +Time::Duration PASS 250 250 2026-04-12 c1942aad0 +Time::Duration::Parse PASS 2051 2051 2026-04-12 c1942aad0 +Time::Progress PASS 2 2 2026-04-12 c1942aad0 +Types::Path::Tiny PASS 20 20 2026-04-12 c1942aad0 UNIVERSAL::require PASS 25 25 2026-04-12 cc5efa220 +Unicode::Stringprep PASS 194 194 2026-04-12 c1942aad0 +Unix::Process PASS 2 2 2026-04-12 c1942aad0 XML::NamespaceSupport PASS 49 49 2026-04-12 cc5efa220 +XML::SAX::Expat PASS 2 2 2026-04-12 c1942aad0 +rlib PASS 7 7 2026-04-12 c1942aad0 +syntax PASS 4 4 2026-04-12 c1942aad0 diff --git a/dev/cpan-reports/cpan-compatibility.md b/dev/cpan-reports/cpan-compatibility.md index 5e1369e94..0754dd4c0 100644 --- a/dev/cpan-reports/cpan-compatibility.md +++ b/dev/cpan-reports/cpan-compatibility.md @@ -1,6 +1,6 @@ # CPAN Module Compatibility Report for PerlOnJava -> Auto-generated by `dev/tools/cpan_random_tester.pl` on 2026-04-12 15:28:12 +> Auto-generated by `dev/tools/cpan_random_tester.pl` on 2026-04-12 18:49:03 > > Modules are randomly selected from the full CPAN index and tested > with `./jcpan -t`. Dependencies are tested too; every module that @@ -10,213 +10,890 @@ | Metric | Count | |--------|-------| -| **Modules Tested** | 171 | -| **Pass** | 37 (21.6%) | -| **Fail** | 134 | +| **Modules Tested** | 833 | +| **Pass** | 171 (20.5%) | +| **Fail** | 662 | | **Skipped (XS-only)** | 0 | ## Modules That Pass All Tests | Module | Subtests | Date | Git Commit | |--------|----------|------|------------| +| API::CPanel | 61 | 2026-04-12 | c1942aad0 | | Algorithm::Diff | 1004 | 2026-04-12 | cc5efa220 | | Alien::Build::Plugin::Download::GitLab | 2 | 2026-04-12 | cc5efa220 | +| AnyData2 | 21 | 2026-04-12 | c1942aad0 | +| Apache2::AuthzNIS | 2 | 2026-04-12 | c1942aad0 | | Array::Utils | 17 | 2026-04-12 | cc5efa220 | -| CGI::Application | 189 | 2026-04-12 | cc5efa220 | +| Asm::Z80::Table | 19641 | 2026-04-12 | c1942aad0 | +| AsposeBarCodeCloud::ApiClient | 3 | 2026-04-12 | c1942aad0 | +| AsposeImagingCloud::ApiClient | 3 | 2026-04-12 | c1942aad0 | +| AsposeStorageCloud::StorageApi | 3 | 2026-04-12 | c1942aad0 | +| AudioFile::Info | 14 | 2026-04-12 | c1942aad0 | +| BBPerl | 93 | 2026-04-12 | c1942aad0 | +| BitArray | 2 | 2026-04-12 | c1942aad0 | +| Builder | 31 | 2026-04-12 | c1942aad0 | +| CDR::Parser::SI3000 | 17 | 2026-04-12 | c1942aad0 | +| CGI::Application | 189 | 2026-04-12 | c1942aad0 | | CGI::Application::Plugin::AbstractCallback | 2 | 2026-04-12 | cc5efa220 | +| CGI::Application::Plugin::DetectAjax | 4 | 2026-04-12 | c1942aad0 | +| CGI::Application::Plugin::Forward | 33 | 2026-04-12 | c1942aad0 | +| CGI::Application::Plugin::MessageStack | 9 | 2026-04-12 | c1942aad0 | +| CGI::Auth::Auto | 1 | 2026-04-12 | c1942aad0 | +| CGI::FormBuilder::Source::Perl | 1 | 2026-04-12 | c1942aad0 | +| CGI::Scriptpaths | 18 | 2026-04-12 | c1942aad0 | +| CGI::Struct | 126 | 2026-04-12 | c1942aad0 | +| CORBA::Python | 14 | 2026-04-12 | c1942aad0 | +| CPAN::DistnameInfo | 829 | 2026-04-12 | c1942aad0 | +| CPAN::Mini | 48 | 2026-04-12 | c1942aad0 | +| CPAN::Tarball::Patch | 1 | 2026-04-12 | c1942aad0 | +| CPAN::Test::Dummy::Perl5::Build::DepeFails | 2 | 2026-04-12 | c1942aad0 | +| CPAN::Test::Dummy::Perl5::ExtUtilsMakeMaker | 1 | 2026-04-12 | c1942aad0 | +| CPAN::Test::Dummy::Perl5::StaticInstall | 1 | 2026-04-12 | c1942aad0 | +| CPAN::Test::Dummy::Perl5::VersionQV | 1 | 2026-04-12 | c1942aad0 | +| CPAN::Testers::Common::DBUtils | 4 | 2026-04-12 | c1942aad0 | +| CPU::Emulator::Memory::Banked | 91 | 2026-04-12 | c1942aad0 | +| CSS::Tiny | 36 | 2026-04-12 | c1942aad0 | +| CSVAWK | 2 | 2026-04-12 | c1942aad0 | +| Calendar::Simple | 83 | 2026-04-12 | c1942aad0 | | Canary::Stability | 1 | 2026-04-12 | cc5efa220 | | Class::ErrorHandler | 10 | 2026-04-12 | cc5efa220 | | Class::ISA | 4 | 2026-04-12 | cc5efa220 | +| Class::StrongSingleton | 26 | 2026-04-12 | c1942aad0 | +| Class::Trigger | 51 | 2026-04-12 | c1942aad0 | +| Config::Frontend | 136 | 2026-04-12 | c1942aad0 | +| Crypt::Random::Seed | 26 | 2026-04-12 | c1942aad0 | +| Crypt::Random::TESHA2 | 20 | 2026-04-12 | c1942aad0 | | Crypt::SaltedHash | 10 | 2026-04-12 | cc5efa220 | -| Cwd::Guard | 6 | 2026-04-12 | cc5efa220 | +| Crypt::SysRandom | 4 | 2026-04-12 | c1942aad0 | +| Cwd::Ext | 31 | 2026-04-12 | c1942aad0 | +| Cwd::Guard | 6 | 2026-04-12 | c1942aad0 | +| D64::Disk::Layout::Base | 23 | 2026-04-12 | c1942aad0 | +| DBD::Multiplex | 1 | 2026-04-12 | c1942aad0 | +| DBIx::Class::InflateColumn::Geo | 3 | 2026-04-12 | c1942aad0 | +| DBIx::Connect::FromConfig | 5 | 2026-04-12 | c1942aad0 | | DBIx::MyPassword | 13 | 2026-04-12 | d833a1ecb | +| DBIx::Wrapper::VerySimple | 20 | 2026-04-12 | c1942aad0 | +| DCE::Perl::RPC | 4 | 2026-04-12 | c1942aad0 | +| DNS::Record::Check | 5 | 2026-04-12 | c1942aad0 | +| DNS::WorldWideDns | 8 | 2026-04-12 | c1942aad0 | | Data::MethodProxy | 10 | 2026-04-12 | cc5efa220 | +| Data::Walk | 266 | 2026-04-12 | c1942aad0 | +| Date::Tolkien::Shire::Data | 5558 | 2026-04-12 | c1942aad0 | | DateTime::Format::Strptime | 143 | 2026-04-12 | cc5efa220 | +| DateTime::Functions | 2 | 2026-04-12 | c1942aad0 | +| DateTime::Moonpig | 22 | 2026-04-12 | c1942aad0 | +| DateTime::TimeZone::Alias | 2453 | 2026-04-12 | c1942aad0 | +| DateTime::TimeZone::Catalog::Extend | 355 | 2026-04-12 | c1942aad0 | +| DateTimeX::Format::Ago | 602 | 2026-04-12 | c1942aad0 | +| Device::ParallelPort | 35 | 2026-04-12 | c1942aad0 | +| DublinCore::Element | 137 | 2026-04-12 | c1942aad0 | +| DynGig::Multiplex | 4 | 2026-04-12 | c1942aad0 | +| DynGig::Range | 10 | 2026-04-12 | c1942aad0 | +| DynGig::Range::Time | 6 | 2026-04-12 | c1942aad0 | +| DynGig::Util | 15 | 2026-04-12 | c1942aad0 | +| ESPPlus::Storage | 45 | 2026-04-12 | c1942aad0 | +| Eidolon | 126 | 2026-04-12 | c1942aad0 | +| Error::Pure::Output::Text | 41 | 2026-04-12 | c1942aad0 | | Exception | 4 | 2026-04-12 | cc5efa220 | +| Exporter::Lite | 30 | 2026-04-12 | c1942aad0 | +| Exporter::Tidy | 44 | 2026-04-12 | c1942aad0 | +| ExtUtils::MakeMaker::CPANfile | 5 | 2026-04-12 | c1942aad0 | +| FAST | 312 | 2026-04-12 | c1942aad0 | +| FSM::Basic | 53 | 2026-04-12 | c1942aad0 | +| FTN::Msg | 1 | 2026-04-12 | c1942aad0 | +| File::Fetch | 304 | 2026-04-12 | c1942aad0 | | File::HomeDir | 90 | 2026-04-12 | cc5efa220 | +| File::ShareDir::Dist | 19 | 2026-04-12 | c1942aad0 | +| File::ShareDir::Tiny | 35 | 2026-04-12 | c1942aad0 | | File::Slurper | 8 | 2026-04-12 | cc5efa220 | +| Getopt::Mixed | 26 | 2026-04-12 | c1942aad0 | | HTML::Form | 223 | 2026-04-12 | cc5efa220 | +| HTTP::BrowserDetect | 2601 | 2026-04-12 | c1942aad0 | +| HTTP::Server::Simple::PSGI | 1 | 2026-04-12 | c1942aad0 | +| Hash::MoreUtils | 66 | 2026-04-12 | c1942aad0 | +| IO::Prompt::Tiny | 5 | 2026-04-12 | c1942aad0 | | IO::Scalar | 127 | 2026-04-12 | cc5efa220 | | IO::TieCombine | 7 | 2026-04-12 | cc5efa220 | | Image::GIF::Encoder::PP | 2 | 2026-04-12 | cc5efa220 | +| LEOCHARRE::CLI | 22 | 2026-04-12 | c1942aad0 | +| LEOCHARRE::DEBUG | 10 | 2026-04-12 | c1942aad0 | +| Lingua::EN::FindNumber | 5 | 2026-04-12 | c1942aad0 | | Lingua::EN::Inflect | 2147 | 2026-04-12 | cc5efa220 | +| Lingua::EN::Inflect::Number | 21 | 2026-04-12 | c1942aad0 | +| Lingua::EN::Number::IsOrdinal | 20 | 2026-04-12 | c1942aad0 | +| Lingua::EN::Words2Nums | 66 | 2026-04-12 | c1942aad0 | +| Lingua::GL::Stemmer | 12 | 2026-04-12 | c1942aad0 | +| Lingua::Stem | 7 | 2026-04-12 | c1942aad0 | +| Lingua::Stem::Fr | 16 | 2026-04-12 | c1942aad0 | +| Lingua::Stem::It | 98 | 2026-04-12 | c1942aad0 | +| Lingua::Stem::Snowball::No | 20633 | 2026-04-12 | c1942aad0 | +| Lingua::Stem::Snowball::Se | 30628 | 2026-04-12 | c1942aad0 | +| Linux::usermod | 10 | 2026-04-12 | c1942aad0 | +| Math::Round::Var | 10 | 2026-04-12 | c1942aad0 | +| Method::WeakCallback | 13 | 2026-04-12 | c1942aad0 | +| Module::Build::WithXSpp | 1 | 2026-04-12 | c1942aad0 | | Module::Pluggable::Fast | 6 | 2026-04-12 | cc5efa220 | +| MooX::Types::MooseLike::Base | 169 | 2026-04-12 | c1942aad0 | +| MooX::Types::MooseLike::Numeric | 43 | 2026-04-12 | c1942aad0 | +| Mozilla::CA | 2 | 2026-04-12 | c1942aad0 | +| Net::CIDR::Lite | 54 | 2026-04-12 | c1942aad0 | +| Net::CIDR::Set | 219 | 2026-04-12 | c1942aad0 | | 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 | +| Protocol::Notifo | 40 | 2026-04-12 | c1942aad0 | +| Regexp::Trie | 1 | 2026-04-12 | c1942aad0 | +| Shell::Config::Generate | 80 | 2026-04-12 | c1942aad0 | +| Shell::Guess | 181 | 2026-04-12 | c1942aad0 | +| Sort::Versions | 96 | 2026-04-12 | c1942aad0 | +| String::Formatter | 22 | 2026-04-12 | c1942aad0 | | String::RewritePrefix | 39 | 2026-04-12 | cc5efa220 | +| Sys::Syscall | 1 | 2026-04-12 | c1942aad0 | | TAP::Harness::Metrics | 4 | 2026-04-12 | cc5efa220 | +| Term::Screen | 29 | 2026-04-12 | c1942aad0 | +| Test::API | 32 | 2026-04-12 | c1942aad0 | +| Test::CPAN::Meta | 196 | 2026-04-12 | c1942aad0 | +| Test::Identity | 10 | 2026-04-12 | c1942aad0 | +| Test::InDistDir | 6 | 2026-04-12 | c1942aad0 | +| Test::Inter | 90 | 2026-04-12 | c1942aad0 | | Test::More::UTF8 | 14 | 2026-04-12 | cc5efa220 | | Test::Most | 88 | 2026-04-12 | cc5efa220 | +| Test::Number::Delta | 72 | 2026-04-12 | c1942aad0 | +| Test::Object | 5 | 2026-04-12 | c1942aad0 | | Test::Output | 1217 | 2026-04-12 | cc5efa220 | | Test::Strict | 80 | 2026-04-12 | cc5efa220 | +| Test::SubCalls | 25 | 2026-04-12 | c1942aad0 | +| Text::Brew | 18 | 2026-04-12 | c1942aad0 | | Text::Diff | 33 | 2026-04-12 | cc5efa220 | +| Text::German | 34 | 2026-04-12 | c1942aad0 | +| Text::Patch | 1 | 2026-04-12 | c1942aad0 | +| Text::Quote | 53 | 2026-04-12 | c1942aad0 | +| Text::SimpleTable | 10 | 2026-04-12 | c1942aad0 | +| Text::SimpleTable::AutoWidth | 6 | 2026-04-12 | c1942aad0 | +| Text::Soundex | 18 | 2026-04-12 | c1942aad0 | +| Text::Unidecode | 1483 | 2026-04-12 | c1942aad0 | +| Thread::Queue | 59 | 2026-04-12 | c1942aad0 | +| Thread::Semaphore | 17 | 2026-04-12 | c1942aad0 | +| Tie::Hash::Vivify | 31 | 2026-04-12 | c1942aad0 | | Tie::ToObject | 10 | 2026-04-12 | cc5efa220 | +| Time::Duration | 250 | 2026-04-12 | c1942aad0 | +| Time::Duration::Parse | 2051 | 2026-04-12 | c1942aad0 | +| Time::Progress | 2 | 2026-04-12 | c1942aad0 | +| Types::Path::Tiny | 20 | 2026-04-12 | c1942aad0 | | UNIVERSAL::require | 25 | 2026-04-12 | cc5efa220 | +| Unicode::Stringprep | 194 | 2026-04-12 | c1942aad0 | +| Unix::Process | 2 | 2026-04-12 | c1942aad0 | | XML::NamespaceSupport | 49 | 2026-04-12 | cc5efa220 | +| XML::SAX::Expat | 2 | 2026-04-12 | c1942aad0 | +| rlib | 7 | 2026-04-12 | c1942aad0 | +| syntax | 4 | 2026-04-12 | c1942aad0 | ## Modules That Fail Tests -### Configure Failed (22 modules) +### Configure Failed (109 modules) | Module | Pass/Total | Error | Date | |--------|-----------|-------|------| +| AE | | Configure failed | 2026-04-12 | +| ALPM | | Configure failed | 2026-04-12 | +| AXL::Client::Simple | | Configure failed | 2026-04-12 | +| Affix | | Configure failed | 2026-04-12 | +| Alien::GMP | | Configure failed | 2026-04-12 | +| Alien::Libxml2 | | Configure failed | 2026-04-12 | +| Alien::libextism | | Configure failed | 2026-04-12 | | Announcements | | Configure failed | 2026-04-12 | +| AnyEvent | | Configure failed | 2026-04-12 | +| AnyEvent::Atom::Stream | | Configure failed | 2026-04-12 | +| AnyEvent::Connection | | Configure failed | 2026-04-12 | +| AnyEvent::FriendFeed::Realtime | | Configure failed | 2026-04-12 | +| AnyEvent::Gearman | | Configure failed | 2026-04-12 | +| AnyEvent::Groonga | | Configure failed | 2026-04-12 | +| AnyEvent::Plurk | | Configure failed | 2026-04-12 | +| AnyEvent::RPC | | Configure failed | 2026-04-12 | +| AnyEvent::mDNS | | Configure failed | 2026-04-12 | +| AnyMQ::Pg | | Configure failed | 2026-04-12 | +| Apache2::AuthenSmb | | Configure failed | 2026-04-12 | +| Apache2::FixupContentLanguage | | Configure failed | 2026-04-12 | +| Apache2::WebApp | | Configure failed | 2026-04-12 | +| Apache2::WebApp::Plugin::DBI | | Configure failed | 2026-04-12 | +| Apache2::WebApp::Plugin::DateTime | | Configure failed | 2026-04-12 | +| Apache2::WebApp::Plugin::File | | Configure failed | 2026-04-12 | +| Apache::Scoreboard | | Configure failed | 2026-04-12 | +| Atompub | | Configure failed | 2026-04-12 | +| BIE::Data::HDF5 | | Configure failed | 2026-04-12 | +| Binding | | Configure failed | 2026-04-12 | +| Bison | | Configure failed | 2026-04-12 | +| CDB::TinyCDB | | Configure failed | 2026-04-12 | | CGI::PSGI | | Configure failed | 2026-04-12 | +| CPAN::Meta::Prereqs::Diff | | Configure failed | 2026-04-12 | +| CWB | | Configure failed | 2026-04-12 | +| Cache::File | | Configure failed | 2026-04-12 | +| Cache::LRU | | Configure failed | 2026-04-12 | +| Cache::Memory | | Configure failed | 2026-04-12 | +| CatalystX::Imports::Context | | Configure failed | 2026-04-12 | +| CatalystX::OAuth2::Provider | | Configure failed | 2026-04-12 | +| CatalystX::Plugin::Blurb | | Configure failed | 2026-04-12 | | Class::Accessor::Lite | | Configure failed | 2026-04-12 | +| Class::MOP | | Configure failed | 2026-04-12 | +| ClearCase::Region_Cfg_Parser | | Configure failed | 2026-04-12 | +| CodeGenRequestResponseType | | Configure failed | 2026-04-12 | +| Commandable | | Configure failed | 2026-04-12 | +| Commandable::Invocation | | Configure failed | 2026-04-12 | +| Corona | | Configure failed | 2026-04-12 | +| CouchDB::View | | Configure failed | 2026-04-12 | +| Curses::UI | | Configure failed | 2026-04-12 | +| DB::Color | | Configure failed | 2026-04-12 | +| DBD::EmpressNet | | Configure failed | 2026-04-12 | +| DBD::JDBC | | Configure failed | 2026-04-12 | +| DBD::Oracle::db | | Configure failed | 2026-04-12 | +| DBD::PgSPI | | Configure failed | 2026-04-12 | +| DBD::Redbase | | Configure failed | 2026-04-12 | +| DBICx::TestDatabase | | Configure failed | 2026-04-12 | +| DBIx::Chart | | Configure failed | 2026-04-12 | +| DBIx::Class::InflateColumn::Currency | | Configure failed | 2026-04-12 | +| DBIx::Class::IntrospectableM2M | | Configure failed | 2026-04-12 | +| DBIx::Class::Row::Slave | | 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 | +| DBIx::Class::Validation | | Configure failed | 2026-04-12 | +| DBIx::SQLite::Deploy | | Configure failed | 2026-04-12 | +| DBIx::TransactionManager | | Configure failed | 2026-04-12 | +| DBIx::TxnPool | | Configure failed | 2026-04-12 | +| Devel::MAT::Dumper | | Configure failed | 2026-04-12 | | Devel::PPPort | | Configure failed | 2026-04-12 | | Devel::Symdump | | Configure failed | 2026-04-12 | +| Diamond | | Configure failed | 2026-04-12 | +| Digest::BubbleBabble | | Configure failed | 2026-04-12 | +| Dist::Build::XS::Conf | | Configure failed | 2026-04-12 | +| DuckCurses::dagobert | | Configure failed | 2026-04-12 | +| ExtUtils::H2PM | | Configure failed | 2026-04-12 | +| FarmBalance | | Configure failed | 2026-04-12 | +| FindBin::libs | | Configure failed | 2026-04-12 | +| Fl_Align_Group | | Configure failed | 2026-04-12 | +| FormValidator::Simple | | Configure failed | 2026-04-12 | | Geo::IP | | Configure failed | 2026-04-12 | +| HTTP::Parser::XS | | Configure failed | 2026-04-12 | +| Hash::FieldHash | | Configure failed | 2026-04-12 | +| IO::Pty | | Configure failed | 2026-04-12 | +| IPC::SysV | | Configure failed | 2026-04-12 | | Iterator::Simple | | Configure failed | 2026-04-12 | +| Locale::gettext | | Configure failed | 2026-04-12 | | Mac::SystemDirectory | | Configure failed | 2026-04-12 | +| Math::Int64 | | Configure failed | 2026-04-12 | | MooX::Enumeration | | Configure failed | 2026-04-12 | +| MooX::Lsub | | Configure failed | 2026-04-12 | +| Moops | | 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::Attribute::ENV | | 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 | +| Pg::PQ | | Configure failed | 2026-04-12 | +| RDF::Query | | Configure failed | 2026-04-12 | +| SQL::QueryMaker | | Configure failed | 2026-04-12 | +| Term::ReadLine::Gnu | | Configure failed | 2026-04-12 | +| Time::HiRes | | Configure failed | 2026-04-12 | +| Tk | | Configure failed | 2026-04-12 | | UNIX::Cal | | Configure failed | 2026-04-12 | | URI::Template::Restrict | | Configure failed | 2026-04-12 | +| Unicode::EastAsianWidth | | Configure failed | 2026-04-12 | | Win32::GUI::HyperLink | | Configure failed | 2026-04-12 | | YAML::Syck | | Configure failed | 2026-04-12 | +| mod_perl2 | | Configure failed | 2026-04-12 | -### Missing Dependencies (9 modules) +### Missing Dependencies (49 modules) | Module | Pass/Total | Error | Date | |--------|-----------|-------|------| +| AAC::Pvoice | | Missing: Wx.pm | 2026-04-12 | +| ANSI::Unicode | | Missing: Moose.pm | 2026-04-12 | +| Abstract::Meta::Class | 104/104 | Missing: Devel/Symdump.pm | 2026-04-12 | +| AnyEvent::Capture | | Missing: AnyEvent.pm | 2026-04-12 | +| AnyEvent::Fork::Remote | | Missing: common/sense.pm | 2026-04-12 | +| AnyEvent::HTTP | | Missing: common/sense.pm | 2026-04-12 | +| AnyEvent::MockTCPServer | | Missing: AnyEvent/Socket.pm | 2026-04-12 | +| AnyEvent::Pg::Pool | | Missing: Pg/PQ.pm | 2026-04-12 | +| AnyEvent::Serialize | | Missing: AnyEvent.pm | 2026-04-12 | +| Archive::CAR | | Missing: Codec/CBOR.pm | 2026-04-12 | +| Bluesky | | Missing: At.pm | 2026-04-12 | +| CAM::EmailTemplate::SMTP | | Missing: CAM/Template.pm | 2026-04-12 | +| Chart | | Missing: GD.pm | 2026-04-12 | +| Class::Container | | Missing: Params/Validate.pm | 2026-04-12 | +| DBD::RDFStore | | Missing: RDFStore.pm | 2026-04-12 | +| DBIx::Class::QueryLog::Conditional | | Missing: aliased.pm | 2026-04-12 | +| DBIx::Class::ResultClass::TrackColumns | | Missing: Moose.pm | 2026-04-12 | +| DBIx::Connection | 86/86 | Missing: Devel/Symdump.pm | 2026-04-12 | +| DBIx::Deployer | | Missing: Moops.pm | 2026-04-12 | +| DBIx::Wrapper::Config | | Missing: DBIx/Wrapper/Config.pm | 2026-04-12 | +| DBUnit | 110/110 | Missing: Devel/Symdump.pm | 2026-04-12 | +| DarkSky::API | | Missing: common/sense.pm | 2026-04-12 | +| DateTime::Event::MultiCron | | Missing: DateTime/Event/Cron.pm | 2026-04-12 | +| DbFramework::Attribute | | Missing: t/Config.pm | 2026-04-12 | +| EV::ADNS | | Missing: common/sense.pm | 2026-04-12 | +| Eircode | | Missing: Const/Fast.pm | 2026-04-12 | +| Entrez | | Missing: Stone/Cursor.pm | 2026-04-12 | +| Expect | | Missing: IO/Pty.pm | 2026-04-12 | +| ExtUtils::XSpp | 3/3 | Missing: t/lib/XSP/Test.pm | 2026-04-12 | +| FCGI::ProcManager::Dynamic | | Missing: IPC/SysV.pm | 2026-04-12 | | Font::Metrics::Courier | 2/2 | Missing: Font/AFM.pm | 2026-04-12 | +| GraphViz | | Missing: IPC/Run.pm | 2026-04-12 | +| HTML::Template::Default | | Missing: LEOCHARRE/Debug.pm | 2026-04-12 | +| Hash::AsObject | 93/93 | Missing: diagnostics.pm | 2026-04-12 | | Iterator::Simple::Lookahead | 1/1 | Missing: Iterator/Simple.pm | 2026-04-12 | | Jcode | | Missing: diagnostics.pm | 2026-04-12 | +| Math::Base::Convert | | Missing: Math/Base/Convert.pm | 2026-04-12 | +| Module::Signature | 2/2 | Missing: IPC/Run.pm | 2026-04-12 | | MooseX::NonMoose | 1/1 | Missing: Moose.pm | 2026-04-12 | +| MooseX::OneArgNew | 1/1 | Missing: Moose.pm | 2026-04-12 | +| MooseX::Role::Parameterized | 4/4 | 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 | +| SGI::FAM | | Missing: Test/Helper.pm | 2026-04-12 | | SpamMonkey | | Missing: File/Path/Expand.pm | 2026-04-12 | +| Statistics::Contingency | | Missing: Params/Validate.pm | 2026-04-12 | +| Tk::WorldCanvas | | Missing: Tk.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) +### Other (156 modules) | Module | Pass/Total | Error | Date | |--------|-----------|-------|------| | A1z::Html | | Unknown test outcome | 2026-04-12 | +| AFS::Monitor | | Unknown test outcome | 2026-04-12 | +| AI::Prolog | | Unknown test outcome | 2026-04-12 | +| Ace | | Unknown test outcome | 2026-04-12 | +| Algorithm::SVM | | | 2026-04-12 | +| Alias | | | 2026-04-12 | +| Alien::Base::ModuleBuild | | Unknown test outcome | 2026-04-12 | | Alien::Base::Wrapper | | Unknown test outcome | 2026-04-12 | | AnnoCPAN::Perldoc::SyncDB | | No parseable output | 2026-04-12 | +| Ao | | | 2026-04-12 | +| Apache::Htpasswd | | Unknown test outcome | 2026-04-12 | +| AsposeCellsCloud::Object::ProtectWorkbookRequst | | Unknown test outcome | 2026-04-12 | +| AsposeSlidesCloud::ApiClient | | Unknown test outcome | 2026-04-12 | +| At | | | 2026-04-12 | +| B::Deobfuscate | | | 2026-04-12 | +| B::Lint | | Unknown test outcome | 2026-04-12 | +| B::Lint::Plugin::Test | | No parseable output | 2026-04-12 | +| BaseLib | | | 2026-04-12 | +| BingoX::Argon | | Unknown test outcome | 2026-04-12 | +| ByteCache | | Unknown test outcome | 2026-04-12 | +| CAD::Calc | | Unknown test outcome | 2026-04-12 | +| CGI::EncryptForm | | | 2026-04-12 | +| CGI::Enurl | | Unknown test outcome | 2026-04-12 | +| CGI::Path | | | 2026-04-12 | +| CGI::Pure | | No parseable output | 2026-04-12 | +| CGI::Session | | No parseable output | 2026-04-12 | +| CGI::Session::Driver::dbic | | No parseable output | 2026-04-12 | +| CGI::Session::Driver::flexmysql | | No parseable output | 2026-04-12 | +| CGI::Snapp::Demo::Three | | No parseable output | 2026-04-12 | +| CGI::Struct::XS | | No parseable output | 2026-04-12 | +| CGI::Untaint::CountyStateProvince::US | | No parseable output | 2026-04-12 | +| CGI::Utils | | No parseable output | 2026-04-12 | +| CGI::Wiki::Formatter::Multiple | | No parseable output | 2026-04-12 | +| CGI::remote_addr | | No parseable output | 2026-04-12 | +| CGIS | | No parseable output | 2026-04-12 | +| CHI | | Unknown test outcome | 2026-04-12 | +| CLI::Coin::Toss | | No parseable output | 2026-04-12 | +| CLI::Osprey | | No parseable output | 2026-04-12 | +| CORBA::C | | Unknown test outcome | 2026-04-12 | +| CORBA::IDL | | | 2026-04-12 | +| CORBA::IDLtree | | No parseable output | 2026-04-12 | +| CPAN::Changes | | | 2026-04-12 | +| CPAN::Changes::Group::Dependencies::Stats | | | 2026-04-12 | +| CPAN::Cpanorg::Auxiliary | | No parseable output | 2026-04-12 | +| CPAN::Diff | | No parseable output | 2026-04-12 | +| CPAN::Digger | | No parseable output | 2026-04-12 | +| CPAN::Mini::Inject::REST::Client | | No parseable output | 2026-04-12 | +| CPAN::Mini::Live | | Unknown test outcome | 2026-04-12 | +| CPAN::Test::Dummy::SCO::Lacks | | Unknown test outcome | 2026-04-12 | +| CTK | | Unknown test outcome | 2026-04-12 | +| Cache::Memcached::Fast | | | 2026-04-12 | +| ClearCase::ClearPrompt | | Unknown test outcome | 2026-04-12 | +| Config_u | | No parseable output | 2026-04-12 | +| Cpanel::JSON::XS | | | 2026-04-12 | +| Crypt::Blowfish | | | 2026-04-12 | +| Crypt::CBC | | Unknown test outcome | 2026-04-12 | +| Crypt::Cipher::AES | | Unknown test outcome | 2026-04-12 | +| Crypt::HCE_SHA | | | 2026-04-12 | +| Crypt::IDEA | | | 2026-04-12 | +| Crypt::Mode::CBC::Easy | | Unknown test outcome | 2026-04-12 | +| DAPNET::API | | Unknown test outcome | 2026-04-12 | +| DB::Ent | | | 2026-04-12 | +| DBD::AnyData::db | | Unknown test outcome | 2026-04-12 | +| DBD::monetdb | | No parseable output | 2026-04-12 | +| DBD::mysql | | No parseable output | 2026-04-12 | +| DBGp::Client | | No parseable output | 2026-04-12 | +| DBI::Log | | No parseable output | 2026-04-12 | +| DBICErrorTest::SyntaxError | | No parseable output | 2026-04-12 | +| DBIx::AbstractStatement | | No parseable output | 2026-04-12 | +| DBIx::Admin::DSNManager | | No parseable output | 2026-04-12 | +| DBIx::CSV | | No parseable output | 2026-04-12 | +| DBIx::Class::Helper::IgnoreWantarray | | Unknown test outcome | 2026-04-12 | +| DBIx::Class::UnicornLogger | | | 2026-04-12 | +| DBIx::Dump | | Unknown test outcome | 2026-04-12 | +| DBIx::HTMLinterface | | Unknown test outcome | 2026-04-12 | +| DBIx::Repgen | | | 2026-04-12 | +| DBIx::TNDBO | | Unknown test outcome | 2026-04-12 | | DBIx::Tree::NestedSet | | | 2026-04-12 | +| DBIx::Version | | Unknown test outcome | 2026-04-12 | +| DDB_File | | | 2026-04-12 | +| Dancer | | Unknown test outcome | 2026-04-12 | | Data::Dump | | | 2026-04-12 | +| DataSexta | | | 2026-04-12 | | DateTime::Format::Flexible | | Unknown test outcome | 2026-04-12 | +| Device::ParallelPort::drv::parport | | | 2026-04-12 | | Digest::SHA1 | | | 2026-04-12 | +| Digest::SHA::PurePerl | | Unknown test outcome | 2026-04-12 | | Directory::Scratch | | Unknown test outcome | 2026-04-12 | +| DocRaptor | | Unknown test outcome | 2026-04-12 | +| ERG::line_formatter | | No parseable output | 2026-04-12 | +| Eeuctw | | Unknown test outcome | 2026-04-12 | +| Elatin8 | | | 2026-04-12 | +| Event | | | 2026-04-12 | +| ExecCmds | | | 2026-04-12 | +| ExtUtils::Constant | | Unknown test outcome | 2026-04-12 | +| FCGI::ProcManager | | | 2026-04-12 | | FFI::CheckLib | | Unknown test outcome | 2026-04-12 | +| FServer | | No parseable output | 2026-04-12 | +| File::Cache | | | 2026-04-12 | | File::Copy::Recursive::Reduced | | | 2026-04-12 | +| File::Spec | | | 2026-04-12 | +| File::Sync | | | 2026-04-12 | +| Guard | | | 2026-04-12 | +| HTML::FillInForm | | Unknown test outcome | 2026-04-12 | | HTML::Summary | | | 2026-04-12 | | I18N::String | | Unknown test outcome | 2026-04-12 | +| Image::Magick | | | 2026-04-12 | | Iterator::Array::Jagged | | Unknown test outcome | 2026-04-12 | | JSON::Validator::Ref | | Unknown test outcome | 2026-04-12 | +| Lingua::Stem::Snowball::Da | | Unknown test outcome | 2026-04-12 | | List::MoreUtils | | Unknown test outcome | 2026-04-12 | +| List::UtilsBy | | Unknown test outcome | 2026-04-12 | +| Log::Log4perl | | Unknown test outcome | 2026-04-12 | +| Log::Sprintf | | | 2026-04-12 | +| Memoize | | Unknown test outcome | 2026-04-12 | +| Memoize::ExpireLRU | | | 2026-04-12 | | MooX::BuildArgs | | | 2026-04-12 | +| MooseX::Types | | Unknown test outcome | 2026-04-12 | +| MooseX::Types::Moose | | Unknown test outcome | 2026-04-12 | | Net::DNS | | Unknown test outcome | 2026-04-12 | | PONAPI::Document | | Unknown test outcome | 2026-04-12 | +| PPI | | 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 | +| Proc::FastSpawn | | | 2026-04-12 | +| SQL::NamedPlaceholder | | Unknown test outcome | 2026-04-12 | +| SQL::Statement | | | 2026-04-12 | | SWIFT::Factory::Tag::Tag30 | | Unknown test outcome | 2026-04-12 | | SWIFT::Factory::Tag::Tag30T | | Unknown test outcome | 2026-04-12 | +| Safe | | Unknown test outcome | 2026-04-12 | +| Session::Token | | Unknown test outcome | 2026-04-12 | +| Set::Object | | Unknown test outcome | 2026-04-12 | +| Set::Scalar | | Unknown test outcome | 2026-04-12 | | Smart::Comments | | Unknown test outcome | 2026-04-12 | +| Sort::Maker | | Unknown test outcome | 2026-04-12 | +| String::CRC32 | | | 2026-04-12 | +| String::ToIdentifier::EN | | Unknown test outcome | 2026-04-12 | +| Syntax::Highlight::Perl::Improved | | Unknown test outcome | 2026-04-12 | | SystemTray::Applet | | No parseable output | 2026-04-12 | +| Term::Cap | | | 2026-04-12 | +| Term::Table | | Unknown test outcome | 2026-04-12 | | Test::Base | | Unknown test outcome | 2026-04-12 | +| Test::Carp | | Unknown test outcome | 2026-04-12 | +| Test::Helper | | Unknown test outcome | 2026-04-12 | +| Text::FillIn | | Unknown test outcome | 2026-04-12 | +| Text::FormatTable | | Unknown test outcome | 2026-04-12 | +| Time::Object | | | 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 | +| autobox::Core | | | 2026-04-12 | +| bigint | | Unknown test outcome | 2026-04-12 | -### PerlOnJava Limits (1 modules) +### PerlOnJava Limits (10 modules) | Module | Pass/Total | Error | Date | |--------|-----------|-------|------| +| AnyEvent::Discord | | PerlOnJava: register limit exceeded | 2026-04-12 | +| AppBase::Grep | | PerlOnJava: register limit exceeded | 2026-04-12 | +| AppBase::Sort | | PerlOnJava: register limit exceeded | 2026-04-12 | +| ArrayData::Lingua::Word::EN::Medical::Glutanimate | | PerlOnJava: register limit exceeded | 2026-04-12 | +| BorderStyle | | PerlOnJava: register limit exceeded | 2026-04-12 | +| ColorTheme | | PerlOnJava: register limit exceeded | 2026-04-12 | +| Comparer | | PerlOnJava: register limit exceeded | 2026-04-12 | +| DateTime::Format::Alami | | PerlOnJava: register limit exceeded | 2026-04-12 | +| DateTimeX::Duration::Lite | | PerlOnJava: register limit exceeded | 2026-04-12 | | HTML::FormatText::Any | | PerlOnJava: register limit exceeded | 2026-04-12 | -### Test Failures (70 modules) +### Stack/Memory (2 modules) | Module | Pass/Total | Error | Date | |--------|-----------|-------|------| +| DB_File::Lock | | StackOverflowError | 2026-04-12 | +| Hash::Ordered | 106/106 | StackOverflowError | 2026-04-12 | + +### Syntax Error (1 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| Switch | | Syntax error | 2026-04-12 | + +### Test Failures (330 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| AI::Categorizer | 0/28 | 67/28 subtests failed | 2026-04-12 | +| AI::MXNetCAPI | 0/1 | 1/1 subtests failed | 2026-04-12 | +| AI::NNVMCAPI | 0/1 | 1/1 subtests failed | 2026-04-12 | +| ARGV::JSON | 1/1 | | 2026-04-12 | +| ARGV::OrDATA | 17/20 | 3/20 subtests failed | 2026-04-12 | +| AWS::IP | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Acrux | 134/139 | 5/139 subtests failed | 2026-04-12 | +| Algorithm::Combinatorics | 1/2 | 1/2 subtests failed | 2026-04-12 | +| Alien::Build::Plugin::Download::GitHub | 2/4 | 2/4 subtests failed | 2026-04-12 | +| Alien::wxWidgets | 0/1 | 8/1 subtests failed | 2026-04-12 | +| AlignDB::DeltaG | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Alter | 73/91 | 18/91 subtests failed | 2026-04-12 | +| Any::Moose | 15/18 | 3/18 subtests failed | 2026-04-12 | +| AnyData | 0/1 | 59/1 subtests failed | 2026-04-12 | +| AnyEvent::AggressiveIdle | 0/4 | 12/4 subtests failed | 2026-04-12 | +| AnyEvent::FTP | 105/116 | 11/116 subtests failed | 2026-04-12 | +| AnyEvent::ForkObject | 0/5 | 51/5 subtests failed | 2026-04-12 | +| AnyEvent::ImageShack | 0/1 | 1/1 subtests failed | 2026-04-12 | +| AnyEvent::OWNet | 15/18 | 3/18 subtests failed | 2026-04-12 | +| AnyEvent::Pg::Pool::Multiserver | 0/1 | 1/1 subtests failed | 2026-04-12 | +| AnyEvent::Processor | 0/5 | 5/5 subtests failed | 2026-04-12 | +| AnyEvent::SSH2 | 0/1 | 1/1 subtests failed | 2026-04-12 | +| AnyEvent::Tools | 0/10 | 103/10 subtests failed | 2026-04-12 | +| AnyEvent::WebService::Notifo | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Apache2::AuthAny | 4/19 | 15/19 subtests failed | 2026-04-12 | +| Apache2::ScoreboardIsFull | 0/1 | 1/1 subtests failed | 2026-04-12 | | App::Cmd::Setup | 31/57 | 26/57 subtests failed | 2026-04-12 | +| Archive::Peek | 4/5 | 1/5 subtests failed | 2026-04-12 | +| Array::Compare | 36/37 | 1/37 subtests failed | 2026-04-12 | +| Arriba | 5/9 | 4/9 subtests failed | 2026-04-12 | | Asm::Preproc | 0/1 | 1/1 subtests failed | 2026-04-12 | +| B::Keywords | 15/15 | | 2026-04-12 | +| B::Module::Info | 70/109 | 39/109 subtests failed | 2026-04-12 | +| BBS::UserInfo::SOB | 0/1 | 1/1 subtests failed | 2026-04-12 | +| BeePack | 0/1 | 1/1 subtests failed | 2026-04-12 | +| BenchmarkAnything::Config | 2/2 | | 2026-04-12 | | BerkeleyDB | 3/3 | | 2026-04-12 | | Bit::Vector | 0/1 | 14/1 subtests failed | 2026-04-12 | +| Blessed::Merge | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Bytes::Random::Secure | 195/207 | 12/207 subtests failed | 2026-04-12 | +| CACertOrg::CA | 3/6 | 3/6 subtests failed | 2026-04-12 | +| CGI::Application::Plugin::AutoRunmode | 71/74 | 3/74 subtests failed | 2026-04-12 | +| CGI::Application::Plugin::TT | 55/58 | 3/58 subtests failed | 2026-04-12 | +| CGI::Auth | 2/4 | 2/4 subtests failed | 2026-04-12 | +| CGI::Builder | 0/5 | 22/5 subtests failed | 2026-04-12 | +| CGI::Capture | 13/14 | 1/14 subtests failed | 2026-04-12 | +| CGI::Easy | 0/2 | 4/2 subtests failed | 2026-04-12 | | CGI::Emulate::PSGI | 28/41 | 13/41 subtests failed | 2026-04-12 | +| CGI::FormBuilder | 313/467 | 154/467 subtests failed | 2026-04-12 | +| CGI::Simple::Cookie | 0/181 | 702/181 subtests failed | 2026-04-12 | +| CGI::Test | 0/8 | 168/8 subtests failed | 2026-04-12 | +| CPAN::Test::Dummy::Perl5::Build::Fails | 1/2 | 1/2 subtests failed | 2026-04-12 | +| CPAN::Test::Reporter | 0/1 | 1/1 subtests failed | 2026-04-12 | +| CPAN::Testers::Data::Release | 0/3 | 11/3 subtests failed | 2026-04-12 | +| CPAN::Testers::Fact::PlatformInfo | 0/1 | 1/1 subtests failed | 2026-04-12 | +| CPU::Emulator::Z80 | 0/56 | 1707/56 subtests failed | 2026-04-12 | +| CPU::Z80::Assembler | 0/106 | 18352/106 subtests failed | 2026-04-12 | +| CSS::Prepare | 0/3 | 1070/3 subtests failed | 2026-04-12 | +| Cache::Cache | 0/1 | 166/1 subtests failed | 2026-04-12 | +| Carp::Assert | 42/44 | 2/44 subtests failed | 2026-04-12 | | Carp::Clan | 58/116 | 58/116 subtests failed | 2026-04-12 | +| CfgTie::CfgArgs | 0/3 | 27/3 subtests failed | 2026-04-12 | | Child | 8/8 | | 2026-04-12 | | Class::Accessor | 137/139 | 2/139 subtests failed | 2026-04-12 | +| Class::C3::Adopt::NEXT | 0/4 | 22/4 subtests failed | 2026-04-12 | +| Class::InsideOut | 173/316 | 143/316 subtests failed | 2026-04-12 | | Class::Load | 70/86 | 16/86 subtests failed | 2026-04-12 | +| Class::Std | 224/255 | 31/255 subtests failed | 2026-04-12 | +| Class::Unload | 10/10 | | 2026-04-12 | +| Class::Util | 323/341 | 18/341 subtests failed | 2026-04-12 | | Class::XSAccessor | 0/10 | 184/10 subtests failed | 2026-04-12 | +| Codec::CBOR | 5/8 | 3/8 subtests failed | 2026-04-12 | +| Colouring::In | 55/65 | 10/65 subtests failed | 2026-04-12 | +| Combine::Keys | 1/1 | | 2026-04-12 | +| Config::Backend::INI | 0/2 | 10/2 subtests failed | 2026-04-12 | +| Config::General | 0/17 | 62/17 subtests failed | 2026-04-12 | +| Config::IniFiles | 0/45 | 175/45 subtests failed | 2026-04-12 | | Const::Fast | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Continuity | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Coro | 0/17 | 43/17 subtests failed | 2026-04-12 | +| CouchWiki | 4/5 | 1/5 subtests failed | 2026-04-12 | +| Crayon | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Crypt::Curve25519 | 0/11 | 11/11 subtests failed | 2026-04-12 | +| Crypt::JWT | 0/3 | 6/3 subtests failed | 2026-04-12 | +| Crypt::PBKDF2 | 0/7 | 4028/7 subtests failed | 2026-04-12 | | Crypt::Sodium | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Crypt::URandom | 34/48 | 14/48 subtests failed | 2026-04-12 | +| Curses | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DB::AsKVS | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DBD::Mock | 161/206 | 45/206 subtests failed | 2026-04-12 | +| DBIx::Class::Candy | 2/4 | 2/4 subtests failed | 2026-04-12 | +| DBIx::Class::Helper::SimpleStats | 1/1 | | 2026-04-12 | +| DBIx::Class::InflateColumn::DateTime::WithTimeZone | 1/4 | 3/4 subtests failed | 2026-04-12 | +| DBIx::Class::InflateColumn::TimeMoment | 1/1 | | 2026-04-12 | +| DBIx::Class::LookupColumn | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DBIx::Class::TimeStamp::WithTimeZone | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DBIx::Connector | 0/118 | 640/118 subtests failed | 2026-04-12 | +| DBIx::FixtureLoader | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DBIx::Introspector | 16/16 | | 2026-04-12 | +| DBIx::NamedBinding | 0/5 | 8/5 subtests failed | 2026-04-12 | +| DBIx::ORM::Declarative | 4/4 | | 2026-04-12 | +| DBIx::TryAgain | 2/2 | | 2026-04-12 | +| DBIx::Wrapper | 0/1 | 50/1 subtests failed | 2026-04-12 | +| DBIx::dbMan | 0/1 | 4/1 subtests failed | 2026-04-12 | +| DBM::Deep | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DBR | 8/9 | 1/9 subtests failed | 2026-04-12 | +| DB_File | 0/49 | 522/49 subtests failed | 2026-04-12 | +| DJabberd | 0/10 | 165/10 subtests failed | 2026-04-12 | +| DJabberd::Authen::DBI | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DMTF::WSMan | 0/1 | 1/1 subtests failed | 2026-04-12 | +| DR::DateTime | 181/182 | 1/182 subtests failed | 2026-04-12 | +| Dancer2::Logger::Syslog | 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 | +| Dancer2::Template::TextTemplate | 1/2 | 1/2 subtests failed | 2026-04-12 | +| Danga::Socket | 27/43 | 16/43 subtests failed | 2026-04-12 | +| DarkPAN::Compare | 0/1 | 1/1 subtests failed | 2026-04-12 | | Data::Alias | 0/1 | 635/1 subtests failed | 2026-04-12 | +| Data::GUID | 0/15 | 63/15 subtests failed | 2026-04-12 | +| Data::Perl | 193/194 | 1/194 subtests failed | 2026-04-12 | | Data::Serializer::JSON | 231/480 | 249/480 subtests failed | 2026-04-12 | +| Data::ShowTable | 12/12 | | 2026-04-12 | +| Data::Stag | 87/95 | 8/95 subtests failed | 2026-04-12 | | Data::Stream::Bulk | 0/13 | 13/13 subtests failed | 2026-04-12 | +| Data::StreamDeserializer | 0/11 | 65/11 subtests failed | 2026-04-12 | +| Data::StreamSerializer | 0/7 | 68/7 subtests failed | 2026-04-12 | | Data::UUID | 0/1 | 32/1 subtests failed | 2026-04-12 | +| Data::Validator | 0/1 | 1/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::Calendar::Mayan | 0/5 | 120/5 subtests failed | 2026-04-12 | +| DateTime::Event::Klingon | 0/3 | 4/3 subtests failed | 2026-04-12 | +| DateTime::Fiction::JRRTolkien::Shire | 179/181 | 2/181 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 | +| DateTime::Format::MySQL | 0/1 | 97/1 subtests failed | 2026-04-12 | +| DateTime::Format::PDF | 1/3 | 2/3 subtests failed | 2026-04-12 | +| DateTime::Format::SQLite | 0/2 | 51/2 subtests failed | 2026-04-12 | +| DateTimeX::AATW | 34/42 | 8/42 subtests failed | 2026-04-12 | +| DateTimeX::Auto | 2/6 | 4/6 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 | +| Devel::CheckLib | 13/25 | 12/25 subtests failed | 2026-04-12 | +| Devel::GlobalDestruction | 3/12 | 9/12 subtests failed | 2026-04-12 | +| Devel::Hide | 55/77 | 22/77 subtests failed | 2026-04-12 | +| Digest::JHash | 0/1 | 6/1 subtests failed | 2026-04-12 | +| Digest::SHA3 | 0/2 | 31/2 subtests failed | 2026-04-12 | +| DirectiveSet | 22/223 | 201/223 subtests failed | 2026-04-12 | +| Dist::Build | 13/14 | 1/14 subtests failed | 2026-04-12 | +| Docopt | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Dotenv | 30/30 | | 2026-04-12 | +| Draft | 13/14 | 1/14 subtests failed | 2026-04-12 | +| DynGig::Range::Cluster | 7/8 | 1/8 subtests failed | 2026-04-12 | +| ELF::sign | 1/2 | 1/2 subtests failed | 2026-04-12 | +| EV | 0/2 | 6850/2 subtests failed | 2026-04-12 | +| EV::ClickHouse | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Email::Date::Format | 4/8 | 4/8 subtests failed | 2026-04-12 | +| Error | 0/9 | 44/9 subtests failed | 2026-04-12 | +| Error::Pure | 113/115 | 2/115 subtests failed | 2026-04-12 | +| Export::Attrs | 0/1 | 2/1 subtests failed | 2026-04-12 | +| ExtUtils::Builder | 80/82 | 2/82 subtests failed | 2026-04-12 | +| ExtUtils::Builder::Compiler | 12/12 | | 2026-04-12 | +| ExtUtils::CppGuess | 13/20 | 7/20 subtests failed | 2026-04-12 | +| ExtUtils::Depends | 0/2 | 17/2 subtests failed | 2026-04-12 | +| ExtUtils::PkgConfig | 0/21 | 42/21 subtests failed | 2026-04-12 | +| FB3 | 0/2 | 2/2 subtests failed | 2026-04-12 | +| FFmpeg::Command | 4/4 | | 2026-04-12 | +| FFmpeg::Thumbnail | 0/1 | 1/1 subtests failed | 2026-04-12 | +| FSA::Rules | 267/340 | 73/340 subtests failed | 2026-04-12 | +| Feature::Compat::Try | 31/38 | 7/38 subtests failed | 2026-04-12 | | File::Path::Expand | 0/1 | 8/1 subtests failed | 2026-04-12 | +| File::PathConvert | 264/266 | 2/266 subtests failed | 2026-04-12 | +| File::chmod | 30/39 | 9/39 subtests failed | 2026-04-12 | | Filter::signatures | 0/10 | 59/10 subtests failed | 2026-04-12 | +| Function::Parameters | 0/14 | 1426/14 subtests failed | 2026-04-12 | +| Future | 757/786 | 29/786 subtests failed | 2026-04-12 | +| Graphics::Toolkit::Color | 1651/2572 | 921/2572 subtests failed | 2026-04-12 | +| HTML::Element | 0/399 | 593/399 subtests failed | 2026-04-12 | +| HTML::FillInForm::Lite | 0/147 | 152/147 subtests failed | 2026-04-12 | | HTML::FormatText | 11/29 | 18/29 subtests failed | 2026-04-12 | +| HTML::TableTiler | 0/1 | 5/1 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::Body | 0/57 | 185/57 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 | +| HTTP::Tiny::SPDY | 0/3 | 16/3 subtests failed | 2026-04-12 | +| Heap | 0/862 | 1612/862 subtests failed | 2026-04-12 | +| Hook::LexWrap | 58/58 | | 2026-04-12 | +| Horus | 0/25 | 157/25 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 | +| IO::String | 41/43 | 2/43 subtests failed | 2026-04-12 | +| IO::Util | 0/43 | 58/43 subtests failed | 2026-04-12 | +| IPC::Run | 0/1 | 640/1 subtests failed | 2026-04-12 | +| IRI | 4/8 | 4/8 subtests failed | 2026-04-12 | +| Import::Export | 27/29 | 2/29 subtests failed | 2026-04-12 | | JSON::RPC | 21/30 | 9/30 subtests failed | 2026-04-12 | +| JSONP | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Kwalify | 133/139 | 6/139 subtests failed | 2026-04-12 | | LabKey::Query | 6/12 | 6/12 subtests failed | 2026-04-12 | +| Lingua::EN::Inflect::Phrase | 137/137 | | 2026-04-12 | +| Lingua::EN::Tagger | 0/41 | 75/41 subtests failed | 2026-04-12 | +| Lingua::Stem::Ru | 0/4 | 4/4 subtests failed | 2026-04-12 | | LinuxRealTime | 0/1 | 1/1 subtests failed | 2026-04-12 | +| List::SomeUtils | 41/45 | 4/45 subtests failed | 2026-04-12 | +| Log::Any | 417/456 | 39/456 subtests failed | 2026-04-12 | +| Log::Structured | 11/13 | 2/13 subtests failed | 2026-04-12 | +| MD5 | 0/3 | 11/3 subtests failed | 2026-04-12 | +| MIME::Charset | 77/93 | 16/93 subtests failed | 2026-04-12 | +| MIME::Lite | 18/24 | 6/24 subtests failed | 2026-04-12 | +| MIME::QuotedPrint | 315/348 | 33/348 subtests failed | 2026-04-12 | +| MIME::Types | 97/97 | | 2026-04-12 | +| Math::BigFloat | 0/4967 | 38173/4967 subtests failed | 2026-04-12 | +| Math::BigInt | 0/4967 | 38173/4967 subtests failed | 2026-04-12 | +| Math::Complex | 0/392 | 841/392 subtests failed | 2026-04-12 | +| Math::Random::ISAAC | 9/609 | 600/609 subtests failed | 2026-04-12 | +| Math::Vec | 13/20 | 7/20 subtests failed | 2026-04-12 | +| Metabase::Fact::Hash | 0/47 | 53/47 subtests failed | 2026-04-12 | | Mixin::Linewise::Readers | 1/1 | | 2026-04-12 | +| Mock::Config | 0/1 | 2/1 subtests failed | 2026-04-12 | +| Modern::Perl | 91/164 | 73/164 subtests failed | 2026-04-12 | | Module::Build::XSUtil | 1/3 | 2/3 subtests failed | 2026-04-12 | +| Module::CPANfile | 37/37 | | 2026-04-12 | +| Module::Extract::Namespaces | 10/14 | 4/14 subtests failed | 2026-04-12 | +| Module::Mask | 0/4 | 28/4 subtests failed | 2026-04-12 | +| Module::Util | 46/47 | 1/47 subtests failed | 2026-04-12 | +| MooX::HandlesVia | 779/787 | 8/787 subtests failed | 2026-04-12 | +| Moose::Autobox | 0/17 | 171/17 subtests failed | 2026-04-12 | +| MooseX::Aliases | 0/8 | 153/8 subtests failed | 2026-04-12 | +| MooseX::ArrayRef | 0/1 | 10/1 subtests failed | 2026-04-12 | +| MooseX::Attribute::Chained | 1/7 | 6/7 subtests failed | 2026-04-12 | | MooseX::Attribute::Localize | 0/2 | 10/2 subtests failed | 2026-04-12 | +| MooseX::Emulate::Class::Accessor::Fast | 0/4 | 76/4 subtests failed | 2026-04-12 | +| MooseX::Getopt | 4/10 | 6/10 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 | +| MooseX::SlurpyConstructor | 4/5 | 1/5 subtests failed | 2026-04-12 | +| NEXT | 0/13 | 47/13 subtests failed | 2026-04-12 | +| Net::NIS | 0/1 | 62/1 subtests failed | 2026-04-12 | +| Net::SSH::Perl | 0/21 | 31/21 subtests failed | 2026-04-12 | +| Net::Server::PreFork | 0/37 | 158/37 subtests failed | 2026-04-12 | +| OPC | 3/4 | 1/4 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 | +| Parse::CPAN::Packages | 0/3 | 3/3 subtests failed | 2026-04-12 | +| Parse::Yapp | 0/10 | 16/10 subtests failed | 2026-04-12 | +| PerlIO::eol | 0/2 | 24/2 subtests failed | 2026-04-12 | | PerlIO::utf8_strict | 2389/5816 | 3427/5816 subtests failed | 2026-04-12 | +| Plack::Session | 423/423 | | 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 | +| Proc::Guard | 0/1 | 1/1 subtests failed | 2026-04-12 | +| RDF::NS | 97/98 | 1/98 subtests failed | 2026-04-12 | | REST::Client | 2/2 | | 2026-04-12 | +| Regexp::Common | 3/3 | | 2026-04-12 | +| Router::Boom | 0/1 | 1/1 subtests failed | 2026-04-12 | | Router::Simple | 0/1 | 1/1 subtests failed | 2026-04-12 | +| SQL::Maker | 17/18 | 1/18 subtests failed | 2026-04-12 | +| SUPER | 46/51 | 5/51 subtests failed | 2026-04-12 | +| Scalar::Util | 0/816 | 1560/816 subtests failed | 2026-04-12 | +| Shell::Perl | 12/25 | 13/25 subtests failed | 2026-04-12 | +| Simple::SAX::Serializer | 33/34 | 1/34 subtests failed | 2026-04-12 | | Smart::Args | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Sort::Key | 0/1 | 36/1 subtests failed | 2026-04-12 | +| Sort::MergeSort | 196/197 | 1/197 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 | +| Struct::Match | 5/5 | | 2026-04-12 | | Sub::Exporter::ForMethods | 6/10 | 4/10 subtests failed | 2026-04-12 | +| Sub::Identify | 86/139 | 53/139 subtests failed | 2026-04-12 | +| Syntax::Feature::Junction | 0/9 | 380/9 subtests failed | 2026-04-12 | +| Sys::Hostname::Long | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Sys::Syslog | 0/112 | 289/112 subtests failed | 2026-04-12 | +| Task::Weaken | 21/22 | 1/22 subtests failed | 2026-04-12 | +| Template::Magic | 0/5 | 51/5 subtests failed | 2026-04-12 | +| Term::ReadLine | 12/15 | 3/15 subtests failed | 2026-04-12 | +| Term::Size | 12/18 | 6/18 subtests failed | 2026-04-12 | +| Test::Class | 159/173 | 14/173 subtests failed | 2026-04-12 | +| Test::CleanNamespaces | 119/134 | 15/134 subtests failed | 2026-04-12 | +| Test::DBIx::Class | 0/23 | 35/23 subtests failed | 2026-04-12 | | Test::Differences | 45/49 | 4/49 subtests failed | 2026-04-12 | +| Test::FailWarnings | 6/8 | 2/8 subtests failed | 2026-04-12 | +| Test::LongString | 32/38 | 6/38 subtests failed | 2026-04-12 | | Test::Memory::Cycle | 19/38 | 19/38 subtests failed | 2026-04-12 | +| Test::MockModule | 1/2 | 1/2 subtests failed | 2026-04-12 | +| Test::MockObject | 0/103 | 136/103 subtests failed | 2026-04-12 | +| Test::More | 31/31 | | 2026-04-12 | +| Test::Pod::Coverage | 0/9 | 20/9 subtests failed | 2026-04-12 | +| Test::Refcount | 15/21 | 6/21 subtests failed | 2026-04-12 | +| Test::Roo | 1/9 | 8/9 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 | +| Test::Trap | 0/5 | 5/5 subtests failed | 2026-04-12 | +| Test::YAML | 0/1 | 1/1 subtests failed | 2026-04-12 | | Text::Template | 100/163 | 63/163 subtests failed | 2026-04-12 | +| Text::VisualWidth::PP | 0/5 | 16/5 subtests failed | 2026-04-12 | +| Text::VisualWidth::UTF8 | 0/3 | 15/3 subtests failed | 2026-04-12 | | Tie::File | 4389/4725 | 336/4725 subtests failed | 2026-04-12 | +| Tie::IxHash | 27/29 | 2/29 subtests failed | 2026-04-12 | +| Time::Format | 0/214 | 319/214 subtests failed | 2026-04-12 | +| Time::Moment | 1/36 | 35/36 subtests failed | 2026-04-12 | +| Time::ParseDate | 2/8 | 6/8 subtests failed | 2026-04-12 | +| UNIVERSAL::can | 56/59 | 3/59 subtests failed | 2026-04-12 | +| UNIVERSAL::isa | 53/76 | 23/76 subtests failed | 2026-04-12 | | URI::Find | 617/619 | 2/619 subtests failed | 2026-04-12 | +| URI::Query | 91/93 | 2/93 subtests failed | 2026-04-12 | +| Unicode::LineBreak | 0/9 | 202/9 subtests failed | 2026-04-12 | | VM::CloudAtCost | 0/1 | 1/1 subtests failed | 2026-04-12 | +| Want | 0/1 | 147/1 subtests failed | 2026-04-12 | | WebService::ChatWorkApi | 4/14 | 10/14 subtests failed | 2026-04-12 | +| XML::Parser::Wrapper | 0/2 | 10/2 subtests failed | 2026-04-12 | | XML::SAX | 105/109 | 4/109 subtests failed | 2026-04-12 | +| XML::Simple | 502/503 | 1/503 subtests failed | 2026-04-12 | | XML::Writer | 267/273 | 6/273 subtests failed | 2026-04-12 | +| YAML::Any | 29/38 | 9/38 subtests failed | 2026-04-12 | +| YAML::PP | 2441/2581 | 140/2581 subtests failed | 2026-04-12 | +| YAML::Tiny | 52/58 | 6/58 subtests failed | 2026-04-12 | | YAML::XS | 0/2 | 48/2 subtests failed | 2026-04-12 | +| aliased | 39/40 | 1/40 subtests failed | 2026-04-12 | +| autobox | 0/2 | 670/2 subtests failed | 2026-04-12 | +| autovivification | 0/41 | 71/41 subtests failed | 2026-04-12 | +| boolean | 87/89 | 2/89 subtests failed | 2026-04-12 | +| smallnum | 51/72 | 21/72 subtests failed | 2026-04-12 | | strictures | 4/5 | 1/5 subtests failed | 2026-04-12 | +| threads | 0/1 | 1/1 subtests failed | 2026-04-12 | +| threads::shared | 80/80 | | 2026-04-12 | + +### Timeout (5 modules) + +| Module | Pass/Total | Error | Date | +|--------|-----------|-------|------| +| Bad_Handle | | TIMEOUT (>120s) | 2026-04-12 | +| CPU::Z80::Assembler::Token | | TIMEOUT (>120s) | 2026-04-12 | +| DBD::Safe | | TIMEOUT (>120s) | 2026-04-12 | +| DBNull_File | | TIMEOUT (>120s) | 2026-04-12 | +| DR::Msgpuck::Bool | | TIMEOUT (>120s) | 2026-04-12 | ## How to Reproduce diff --git a/dev/modules/image_magick.md b/dev/modules/image_magick.md new file mode 100644 index 000000000..8bd9cb431 --- /dev/null +++ b/dev/modules/image_magick.md @@ -0,0 +1,175 @@ +# Image::Magick CLI Wrapper for PerlOnJava + +## Overview + +Provide a working `Image::Magick` module for PerlOnJava by wrapping the +ImageMagick CLI (`magick` command) in pure Perl. This replaces the CPAN +module's XS/C layer entirely — no Java XS stub needed. + +**This is NOT a typical CPAN port.** The original module's XS layer links +directly to libMagickCore. Instead of reimplementing 15,000 lines of C in +Java, we delegate to the `magick` CLI, which is already installed on most +developer machines. + +--- + +## Architecture + +### Object Model + +An `Image::Magick` object is a **blessed array reference** (matching CPAN): + +```perl +$img = Image::Magick->new(size => '100x100'); +# $img is blessed [], each element is an image frame +``` + +Each element stores a path to a temp file (MIFF format, lossless). + +### Deferred Execution + +Operations are **not executed immediately**. Instead, CLI flags are queued +and flushed as a single `magick` invocation when output is needed: + +```perl +$img->Read('photo.jpg'); # stores source path +$img->Blur(radius => 5); # queues "-blur 5x2" +$img->Rotate(degrees => 45); # queues "-rotate 45" +$img->Write('out.png'); # executes: magick photo.jpg -blur 5x2 -rotate 45 out.png +``` + +**Flush triggers:** +- `Write()` / `ImageToBlob()` — need output +- `Get()` on computed attributes (columns, rows, etc.) — need metadata +- `Clone()` — must materialize to temp file + +After flush, the result is saved to a temp MIFF file that becomes the new +baseline for subsequent operations. + +### CLI Detection + +At `use Image::Magick` time, detect the ImageMagick CLI: + +1. Try `magick --version` (IM7 — macOS/Homebrew, Windows, Ubuntu 25.04+) +2. Try `convert --version` (IM6 — Ubuntu 24.04 LTS and older) +3. Die with a clear error listing install commands if neither found + +IM6 support adds minimal complexity since the CLI flags are identical — +only the command name differs. + +### Command Mapping + +| IM7 | IM6 | Used for | +|-----|-----|----------| +| `magick` | `convert` | Read/transform/write pipeline | +| `magick identify` | `identify` | Get image attributes (Ping, Get) | +| `magick composite` | `composite` | Composite operations | +| `magick montage` | `montage` | Montage operations | + +### Temp File Management + +- Use `File::Temp` for cross-platform temp files +- MIFF format preserves all metadata and supports multi-frame +- `DESTROY` cleans up temp files per object +- `END` block cleans up any remaining temp files as safety net + +--- + +## API Coverage + +### Fully Supported + +| Method | Implementation | +|--------|---------------| +| `new()` / `New()` | Pure Perl constructor (blessed arrayref) | +| `Read()` / `ReadImage()` | Store source path, defer | +| `Write()` / `WriteImage()` | Flush pipeline to output file | +| `Set()` / `SetAttribute()` | Store attributes, queue as CLI flags | +| `Get()` / `GetAttribute()` | Flush if needed, run `identify` | +| `Ping()` / `PingImage()` | Run `identify` without full decode | +| `Clone()` | Flush and copy temp file | +| `Composite()` | Run `magick composite` / `composite` | +| `Montage()` | Run `magick montage` / `montage` | +| `ImageToBlob()` | Flush to stdout, capture | +| `BlobToImage()` | Write blob to temp, read | +| `Mogrify()` | Generic dispatch to operation queue | +| All transform methods | Queue as CLI flags (Blur, Crop, Rotate, etc.) | + +### Not Supported (die with clear error) + +| Method | Reason | +|--------|--------| +| `Display()` / `Animate()` | Requires X11 server | +| `GetPixel()` / `SetPixel()` | Per-pixel access impractical via CLI | +| `GetPixels()` / `SetPixels()` | Bulk pixel access impractical via CLI | +| `GetAuthenticPixels()` etc. | Low-level C pointer access | +| `RemoteCommand()` | X11 display control | + +### Constants + +Exported constants (QuantumDepth, error codes, etc.) are defined as +numeric values matching ImageMagick defaults. + +--- + +## Return Value Convention + +All methods follow the CPAN convention: + +- **Success**: Return falsy value (`''` in string context) +- **Failure**: Return truthy string `"Exception NNN: reason (description)"` +- Methods returning new objects (Clone, Montage, etc.): Return blessed + arrayref on success, error string on failure + +--- + +## Cross-Platform Support + +| Platform | IM version | Command | Install | +|----------|-----------|---------|---------| +| macOS (Homebrew) | IM7 | `magick` | `brew install imagemagick` | +| Ubuntu 25.04+ | IM7 | `magick` | `apt install imagemagick` | +| Ubuntu 24.04 LTS | IM6 | `convert` | `apt install imagemagick` | +| Windows | IM7 | `magick` | `choco install imagemagick` | + +### Key cross-platform details: +- Use `File::Temp` (not `/tmp/` hardcoded) for temp files +- Use list-form `system(@cmd)` to avoid shell quoting issues +- Detect IM version at load time, cache command paths +- On Windows, avoid `convert` (conflicts with system `convert.exe`) + +--- + +## Files + +| File | Purpose | +|------|---------| +| `src/main/perl/lib/Image/Magick.pm` | CLI wrapper module | +| `src/test/resources/module/Image-Magick/t/basic.t` | Unit test (skips if `magick` not installed) | + +### Removed + +| File | Reason | +|------|--------| +| `src/main/java/.../ImageMagick.java` | No longer needed; `.pm` replaces it entirely | + +--- + +## Progress Tracking + +### Current Status: Implementation + +### Completed +- [x] API analysis (2025-04-12) +- [x] Cross-platform CLI research (2025-04-12) +- [x] Design document (2025-04-12) + +### In Progress +- [ ] Implement `Image/Magick.pm` CLI wrapper +- [ ] Write unit tests +- [ ] Remove Java stub + +### Next Steps +- [ ] Run `make` to verify no regressions +- [ ] Create PR +- [ ] Test on CI (verify graceful skip when `magick` not installed) diff --git a/docs/README.md b/docs/README.md index 53ad6d341..c24561d4a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,7 @@ How-to guides for common tasks: Technical reference documentation: - **[Feature Matrix](reference/feature-matrix.md)** - What Perl features are implemented +- **[Bundled Modules](reference/bundled-modules.md)** - Complete list of included modules - **[XS Compatibility](reference/xs-compatibility.md)** - XS modules and Java implementations - **[CLI Options](reference/cli-options.md)** - Command-line reference - **[Testing](reference/testing.md)** - Test suite information diff --git a/docs/guides/module-porting.md b/docs/guides/module-porting.md index ec0ea91b6..ea72850dd 100644 --- a/docs/guides/module-porting.md +++ b/docs/guides/module-porting.md @@ -207,6 +207,7 @@ To add tests for a new bundled module: - [ ] Compare output with system Perl - [ ] `make` passes all unit tests - [ ] `make test-bundled-modules` passes module-specific tests +- [ ] Update `docs/reference/bundled-modules.md` — add the module to the appropriate category table (include external requirements if any) --- @@ -557,6 +558,7 @@ Run with `perl` (not `jperl`) because it uses fork. ## See Also +- [Bundled Modules Reference](../reference/bundled-modules.md) — Complete list of included modules (update when adding new ones) - [XS Compatibility Reference](../reference/xs-compatibility.md) — XS modules with Java implementations and PP fallbacks - [Using CPAN Modules](using-cpan-modules.md) — Installing and using CPAN modules with jcpan - [Feature Matrix](../reference/feature-matrix.md) — Perl feature compatibility diff --git a/docs/guides/using-cpan-modules.md b/docs/guides/using-cpan-modules.md index 909cab66c..90df62228 100644 --- a/docs/guides/using-cpan-modules.md +++ b/docs/guides/using-cpan-modules.md @@ -92,7 +92,9 @@ When these modules are loaded, the Java implementation is used automatically for ## Available Built-in Modules -PerlOnJava includes these modules without installation: +PerlOnJava includes 150+ modules without installation. The highlights are +listed below; for the complete list see +**[Bundled Modules Reference](../reference/bundled-modules.md)**. ### Core Modules - `strict`, `warnings`, `utf8`, `feature` @@ -261,6 +263,7 @@ Check the module's documentation for fallback behavior. ## See Also +- [Bundled Modules Reference](../reference/bundled-modules.md) - Complete list of included modules - [XS Compatibility Reference](../reference/xs-compatibility.md) - Detailed XS module compatibility - [Module Porting Guide](module-porting.md) - How to port modules to PerlOnJava - [Feature Matrix](../reference/feature-matrix.md) - Perl feature compatibility diff --git a/docs/reference/bundled-modules.md b/docs/reference/bundled-modules.md new file mode 100644 index 000000000..03d1086ff --- /dev/null +++ b/docs/reference/bundled-modules.md @@ -0,0 +1,354 @@ +# Bundled Modules Reference + +PerlOnJava ships with 150+ modules built into the JAR — no installation needed. +Additional pure-Perl modules can be installed from CPAN with +[jcpan](../guides/using-cpan-modules.md). + +This page lists every bundled module, grouped by category, and documents +modules that have **external requirements** or **special instructions**. + +--- + +## Modules with External Requirements + +Some bundled modules need external software to be installed separately. + +### DBI — Database Access + +`DBI` is bundled with a JDBC backend. **SQLite works out of the box** (the +driver is included in the JAR). Other databases require adding a JDBC driver. + +```perl +use DBI; +my $dbh = DBI->connect("dbi:SQLite:dbname=:memory:", "", ""); +``` + +For MySQL, PostgreSQL, Oracle, BigQuery, and other databases, see the full +guide: **[Database Access Guide](../guides/database-access.md)**. + +| Database | Driver setup | +|------------|-------------| +| SQLite | Built-in — nothing to install | +| MySQL | `./jperl Configure.pl --search mysql-connector-java` | +| PostgreSQL | `./jperl Configure.pl --search postgresql` | +| Oracle | `./jperl Configure.pl --search ojdbc` | + +### Image::Magick — Image Processing + +`Image::Magick` is a pure-Perl CLI wrapper that delegates to ImageMagick +command-line tools. It provides the same API as the CPAN XS module +(Read, Write, Resize, Crop, Annotate, etc.) without requiring native +PerlMagick bindings. + +**Requires ImageMagick CLI tools to be installed and in PATH.** + +```bash +# macOS +brew install imagemagick + +# Ubuntu / Debian +sudo apt install imagemagick + +# Windows +choco install imagemagick +``` + +ImageMagick 7 (`magick` command) is preferred. ImageMagick 6 +(`convert`/`identify`) is also supported on Linux and macOS. + +```perl +use Image::Magick; +my $img = Image::Magick->new; +$img->Set(size => '200x200'); +$img->Read('xc:white'); +$img->Resize(geometry => '100x100'); +$img->Write('output.png'); +``` + +See the design document for implementation details: +[dev/modules/image_magick.md](../../dev/modules/image_magick.md). + +### Net::SSLeay / IO::Socket::SSL — TLS/SSL + +These modules have Java-backed implementations that use the JVM's built-in +TLS stack (JSSE). No OpenSSL installation is needed — TLS just works. + +```perl +use IO::Socket::SSL; +my $sock = IO::Socket::SSL->new( + PeerHost => 'example.com', + PeerPort => 443, + SSL_verify_mode => SSL_VERIFY_PEER, +); +``` + +--- + +## Module Categories + +### Core / Pragmas + +These are loaded automatically or via `use`: + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `strict` | Java | | +| `warnings` | Java | | +| `utf8` | Java | | +| `feature` | Java | | +| `integer` | Java | | +| `bytes` | Java | | +| `lib` | Java | | +| `base` | Java | | +| `parent` | Java | | +| `vars` | Java | | +| `subs` | Java | | +| `attributes` | Java | | +| `overload` | Java + Perl | | +| `overloading` | Java | | +| `re` | Java | | +| `mro` | Java | | +| `builtin` | Java | | +| `version` | Java + Perl | | +| `Exporter` | Perl | | +| `AutoLoader` | Perl | | +| `English` | Perl | | +| `Env` | Perl | | +| `Fatal` | Perl | | +| `Config` | Perl | | +| `Errno` | Perl | | +| `Fcntl` | Perl | | +| `B` | Perl | Partial — enough for B::Deparse | +| `UNIVERSAL` | Java | `isa`, `can`, `DOES`, `VERSION` | + +### Data Processing + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Data::Dumper` | Java + Perl | | +| `JSON` / `JSON::PP` | Java | Fast encode/decode via fastjson2 | +| `YAML::PP` | Java + Perl | | +| `TOML` | Java | | +| `Text::CSV` | Java | | +| `Storable` | Java + Perl | `freeze`, `thaw`, `dclone` | +| `Clone` | Java + Perl | Deep copy | +| `Scalar::Util` | Java | `blessed`, `reftype`, `weaken`, `dualvar`, etc. | +| `List::Util` | Java | `reduce`, `first`, `min`, `max`, `sum`, etc. | +| `Hash::Util` | Java | `lock_keys`, `lock_hash`, etc. | + +### File & I/O + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `File::Spec` | Java + Perl | Platform-specific variants included | +| `File::Basename` | Perl | | +| `File::Copy` | Perl | | +| `File::Find` | Perl | | +| `File::Glob` | Perl | | +| `File::Path` | Perl | `make_path`, `remove_tree` | +| `File::Temp` | Java + Perl | | +| `File::stat` | Perl | | +| `File::Compare` | Perl | | +| `Cwd` | Java | | +| `IO::File` | Perl | | +| `IO::Handle` | Java + Perl | | +| `IO::Dir` | Perl | | +| `IO::Select` | Perl | | +| `IO::Seekable` | Perl | | +| `IO::Zlib` | Perl | | +| `FileHandle` | Perl | | +| `DirHandle` | Perl | | +| `SelectSaver` | Perl | | +| `PerlIO::encoding` | Perl | | + +### Network & Web + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `HTTP::Tiny` | Java + Perl | Java `HttpClient` backend | +| `HTTP::Date` | Perl | | +| `HTTP::CookieJar` | Perl | | +| `Socket` | Java + Perl | | +| `IO::Socket::INET` | Perl | | +| `IO::Socket::IP` | Perl | | +| `IO::Socket::UNIX` | Perl | | +| `IO::Socket::SSL` | Java + Perl | Uses JVM TLS (JSSE) | +| `Net::SSLeay` | Java + Perl | Uses JVM TLS (JSSE) | +| `Net::FTP` | Perl | | +| `Net::SMTP` | Perl | | +| `Net::POP3` | Perl | | +| `Net::NNTP` | Perl | | +| `Net::Ping` | Perl | | +| `Net::Cmd` | Perl | | +| `URI::Escape` | Perl | | + +### Database + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `DBI` | Java + Perl | JDBC backend; see [Database Access](../guides/database-access.md) | +| `DBD::SQLite` | Perl | Built-in — no driver setup needed | +| `DBD::Mem` | Perl | In-memory tables | + +### Cryptography & Encoding + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Digest::MD5` | Java | `java.security.MessageDigest` | +| `Digest::SHA` | Java | `java.security.MessageDigest` | +| `Digest` | Perl | | +| `MIME::Base64` | Java | | +| `MIME::QuotedPrint` | Java | | +| `Encode` | Java + Perl | | +| `Unicode::Normalize` | Java | | +| `Unicode::UCD` | Java | | + +### Archives & Compression + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Archive::Tar` | Perl | | +| `Archive::Zip` | Java + Perl | Uses `java.util.zip` | +| `Compress::Raw::Zlib` | Java | Uses `java.util.zip` | +| `Compress::Zlib` | Java + Perl | | +| `IO::Zlib` | Perl | | + +### XML & HTML + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `XML::Parser` | Perl | | +| `XML::Parser::Expat` | Java | Uses Java SAX parser | +| `HTML::Parser` | Java | | + +### Image Processing + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Image::Magick` | Perl | CLI wrapper; requires `magick` in PATH | + +### Date & Time + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Time::HiRes` | Java | `System.nanoTime()` | +| `Time::Piece` | Java + Perl | | +| `Time::Local` | Perl | | +| `DateTime` | Java + Perl | Uses `java.time` APIs | +| `POSIX` | Java | Includes `strftime`, `mktime`, etc. | + +### Math + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Math::BigInt` | Java | Uses `java.math.BigInteger` | + +### Process Control + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `IPC::Open2` | Perl | | +| `IPC::Open3` | Java | | +| `IPC::System::Simple` | Perl | | + +### Terminal + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Term::ReadKey` | Java | | +| `Term::ReadLine` | Java | | +| `Term::ANSIColor` | Perl | | +| `Term::Table` | Perl | | + +### OOP & Introspection + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Scalar::Util` | Java | | +| `Sub::Name` | Java | | +| `Sub::Util` | Java | | +| `Class::Struct` | Perl | | +| `Attribute::Handlers` | Perl | | +| `Devel::Cycle` | Perl | | +| `Devel::Peek` | Perl | | + +### Testing + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Test::More` | Perl | | +| `Test::Simple` | Perl | | +| `Test::Builder` | Perl | | +| `Test::Harness` | Perl | | +| `Test2::Suite` | Perl | Full Test2 stack (~100 files) | +| `TAP::Harness` | Perl | Full TAP stack (~43 files) | +| `App::Prove` | Perl | `prove` command | + +### Build & Install + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `ExtUtils::MakeMaker` | Perl | PerlOnJava-specific version | +| `CPAN` | Perl | Full CPAN client (~30 sub-modules) | +| `CPAN::Meta` | Perl | | +| `Module::Build::Base` | Perl | | +| `Parse::CPAN::Meta` | Perl | | +| `DynaLoader` / `XSLoader` | Java | Routes XS loads to Java implementations | + +### Documentation + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Pod::Simple` | Perl | ~22 sub-modules | +| `Pod::Perldoc` | Perl | ~12 sub-modules | +| `Pod::Text` | Perl | + Color, Overstrike, Termcap | +| `Pod::Man` | Perl | | +| `Pod::Usage` | Perl | | +| `Pod::Checker` | Perl | | + +### Java Integration + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Java::System` | Java + Perl | Access JVM system properties | + +### Miscellaneous + +| Module | Implementation | Notes | +|--------|---------------|-------| +| `Getopt::Long` | Perl | | +| `Getopt::Std` | Perl | | +| `Sys::Hostname` | Java | | +| `I18N::Langinfo` | Java | | +| `Benchmark` | Perl | | +| `Filter::Simple` | Perl | | +| `Filter::Util::Call` | Java | | +| `Tie::Hash` / `Tie::Array` / `Tie::Scalar` | Perl | | +| `Tie::RefHash` | Perl | | + +--- + +## Implementation Types + +- **Java** — Core functionality implemented in Java for performance or JVM + integration. These are in `src/main/java/org/perlonjava/runtime/perlmodule/`. +- **Perl** — Pure-Perl module bundled in the JAR + (`src/main/perl/lib/`). +- **Java + Perl** — Java provides the XS-equivalent functions; a Perl `.pm` + file provides the high-level API. + +## Adding a New Bundled Module + +To bundle a new module into PerlOnJava, see the +**[Module Porting Guide](../guides/module-porting.md)**. After adding the +module, update this page — add an entry to the appropriate category table +with the module name, implementation type, and any notes about external +requirements. + +## See Also + +- [Module Porting Guide](../guides/module-porting.md) — How to bundle new modules +- [Using CPAN Modules](../guides/using-cpan-modules.md) — Installing additional modules +- [Database Access Guide](../guides/database-access.md) — DBI + JDBC setup +- [XS Compatibility](xs-compatibility.md) — Status of XS module support +- [Feature Matrix](feature-matrix.md) — Perl language feature support diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 8674dcb14..af870e82a 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,14 +33,14 @@ 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 = "d833a1ecb"; + public static final String gitCommitId = "849185954"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitDate = "2026-04-11"; + public static final String gitCommitDate = "2026-04-12"; /** * Build timestamp in Perl 5 "Compiled at" format (e.g., "Apr 7 2026 11:20:00"). @@ -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 12 2026 14:02:47"; + public static final String buildTimestamp = "Apr 12 2026 19:01:31"; // Prevent instantiation private Configuration() { diff --git a/src/main/perl/lib/Image/Magick.pm b/src/main/perl/lib/Image/Magick.pm new file mode 100644 index 000000000..9e814254c --- /dev/null +++ b/src/main/perl/lib/Image/Magick.pm @@ -0,0 +1,1239 @@ +package Image::Magick; + +use strict; +use warnings; + +our $VERSION = '7.1.2'; +our @ISA = qw(Exporter); +our @EXPORT = qw( + Success Transparent Opaque QuantumDepth QuantumRange MaxRGB + WarningException ResourceLimitWarning TypeWarning OptionWarning + DelegateWarning MissingDelegateWarning CorruptImageWarning + FileOpenWarning BlobWarning StreamWarning CacheWarning CoderWarning + ModuleWarning DrawWarning ImageWarning XServerWarning RegistryWarning + ConfigureWarning ErrorException ResourceLimitError TypeError + OptionError DelegateError MissingDelegateError CorruptImageError + FileOpenError BlobError StreamError CacheError CoderError + ModuleError DrawError ImageError XServerError RegistryError + ConfigureError FatalErrorException +); + +require Exporter; + +# --- Constants (match PerlMagick severity codes) --- + +use constant Success => 0; +use constant WarningException => 300; +use constant ResourceLimitWarning => 300; +use constant TypeWarning => 305; +use constant OptionWarning => 310; +use constant DelegateWarning => 315; +use constant MissingDelegateWarning => 320; +use constant CorruptImageWarning => 325; +use constant FileOpenWarning => 330; +use constant BlobWarning => 335; +use constant StreamWarning => 340; +use constant CacheWarning => 345; +use constant CoderWarning => 350; +use constant ModuleWarning => 355; +use constant DrawWarning => 360; +use constant ImageWarning => 365; +use constant XServerWarning => 370; +use constant RegistryWarning => 375; +use constant ConfigureWarning => 380; +use constant ErrorException => 400; +use constant ResourceLimitError => 400; +use constant TypeError => 405; +use constant OptionError => 410; +use constant DelegateError => 415; +use constant MissingDelegateError => 420; +use constant CorruptImageError => 425; +use constant FileOpenError => 430; +use constant BlobError => 435; +use constant StreamError => 440; +use constant CacheError => 445; +use constant CoderError => 450; +use constant ModuleError => 455; +use constant DrawError => 460; +use constant ImageError => 465; +use constant XServerError => 470; +use constant RegistryError => 475; +use constant ConfigureError => 480; +use constant FatalErrorException => 700; + +use constant Transparent => 0; +use constant Opaque => 65535; +use constant QuantumDepth => 16; +use constant QuantumRange => 65535; +use constant MaxRGB => 65535; + +# --- CLI detection (runs once at load time) --- + +my $MAGICK_CMD; # e.g. 'magick' or undef +my $CONVERT_CMD; # e.g. 'magick' (IM7) or 'convert' (IM6) +my $IDENTIFY_CMD; # e.g. 'magick identify' or 'identify' +my $COMPOSITE_CMD; # e.g. 'magick composite' or 'composite' +my $MONTAGE_CMD; # e.g. 'magick montage' or 'montage' +my $IM_VERSION; # 6 or 7 + +sub _detect_cli { + return if defined $IM_VERSION; # already detected + + # Try IM7 first + for my $cmd (qw(magick magick.exe)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick\s+(\d+)/) { + $IM_VERSION = $1; + $MAGICK_CMD = $cmd; + $CONVERT_CMD = $cmd; + $IDENTIFY_CMD = "$cmd identify"; + $COMPOSITE_CMD = "$cmd composite"; + $MONTAGE_CMD = "$cmd montage"; + return; + } + } + + # Try IM6 (but not on Windows where 'convert' is a system tool) + if ($^O ne 'MSWin32') { + for my $cmd (qw(convert)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick\s+(\d+)/) { + $IM_VERSION = $1; + $CONVERT_CMD = 'convert'; + $IDENTIFY_CMD = 'identify'; + $COMPOSITE_CMD = 'composite'; + $MONTAGE_CMD = 'montage'; + return; + } + } + } + + die "Image::Magick requires ImageMagick CLI tools.\n" + . "Install with:\n" + . " macOS: brew install imagemagick\n" + . " Ubuntu: sudo apt install imagemagick\n" + . " Windows: choco install imagemagick\n"; +} + +# Detect at load time +_detect_cli(); + +# --- Temp file management --- + +use File::Temp (); + +my @ALL_TEMPS; # safety net for END block + +sub _new_temp { + my $tmp = File::Temp->new(SUFFIX => '.miff', UNLINK => 0); + my $path = $tmp->filename; + close $tmp; + push @ALL_TEMPS, $path; + return $path; +} + +END { + for my $f (@ALL_TEMPS) { + unlink $f if defined $f && -e $f; + } +} + +# --- Constructor --- + +sub new { + my $class = shift; + $class = ref($class) || $class || 'Image::Magick'; + my $self = bless [], $class; + # Internal state stored in a tied hash on the array + # We use index -1 trick: store metadata in a hash ref at a known location + # Actually, we store state in a package-scoped hash keyed by refaddr + $self->Set(@_) if @_; + return $self; +} + +*New = \&new; + +# --- Per-object state (keyed by refaddr) --- + +my %STATE; # refaddr => { attrs => {}, pending => [], sources => [] } + +sub _state { + my $self = shift; + my $addr = Scalar::Util::refaddr($self); + $STATE{$addr} ||= { + attrs => {}, # attributes set via Set() + pending => [], # queued CLI flags + sources => [], # source file paths (one per image in sequence) + }; + return $STATE{$addr}; +} + +require Scalar::Util; + +sub DESTROY { + my $self = shift; + my $addr = Scalar::Util::refaddr($self); + if (my $st = delete $STATE{$addr}) { + # Clean up temp files owned by this object + for my $src (@{$st->{sources}}) { + unlink $src if defined $src && $src =~ /\.miff$/ && -e $src; + } + } +} + +# --- Error helpers --- + +sub _ok { return _dual(0, '') } + +sub _err { + my ($code, $msg) = @_; + return _dual($code, "Exception $code: $msg"); +} + +# Create a dual-valued scalar (integer + string) +sub _dual { + my ($iv, $pv) = @_; + my $sv = $pv; + { no warnings 'numeric'; $sv += 0 if 0; } # vivify IV slot + # Use dualvar from Scalar::Util + return Scalar::Util::dualvar($iv, $pv); +} + +# --- Run a CLI command, return (exit_code, stdout, stderr) --- + +sub _run_cmd { + my @cmd = @_; + # Use open3-like approach with temp files for portability + my $out_file = File::Temp->new(SUFFIX => '.out', UNLINK => 1); + my $err_file = File::Temp->new(SUFFIX => '.err', UNLINK => 1); + my $out_path = $out_file->filename; + my $err_path = $err_file->filename; + close $out_file; + close $err_file; + + # Build shell command with redirections + # We use system() with a shell to get stdout/stderr redirection + my $cmd_str = join(' ', map { _shell_quote($_) } @cmd); + system("$cmd_str >$out_path 2>$err_path"); + my $exit = $?; + + my $stdout = ''; + my $stderr = ''; + if (open my $fh, '<', $out_path) { local $/; $stdout = <$fh> // ''; close $fh; } + if (open my $fh, '<', $err_path) { local $/; $stderr = <$fh> // ''; close $fh; } + unlink $out_path, $err_path; + + return ($exit, $stdout, $stderr); +} + +sub _shell_quote { + my $s = shift; + if ($^O eq 'MSWin32') { + $s =~ s/"/\\"/g; + return qq{"$s"}; + } + $s =~ s/'/'\\''/g; + return "'$s'"; +} + +# --- Flush: execute queued operations --- + +sub _flush { + my ($self) = @_; + my $st = $self->_state; + + return unless @{$st->{pending}} || !@{$st->{sources}}; + + # If no sources and no pending ops, nothing to do + return unless @{$st->{sources}}; + + # Build command: convert_cmd source [pending ops] output + my $output = _new_temp(); + + my @cmd; + my @cmd_parts = split(/\s+/, $CONVERT_CMD); + push @cmd, @cmd_parts; + + # Add sources + push @cmd, @{$st->{sources}}; + + # Add queued operations + push @cmd, @{$st->{pending}}; + + # Add output + push @cmd, $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + + if ($exit != 0) { + return _err(OptionError, "magick command failed: $stderr"); + } + + # Update state: output becomes new source, clear pending + @{$st->{sources}} = ($output); + @{$st->{pending}} = (); + + # Update the array (one image for now; multi-frame support later) + @$self = ($output); + + return; +} + +# --- Read --- + +sub Read { + my $self = shift; + my $st = $self->_state; + + # Parse arguments: can be filenames or file=>$fh or filename=>$name + my @files; + my %opts; + while (@_) { + if ($_[0] =~ /^(file|filename)$/i && @_ >= 2) { + my $key = shift; + my $val = shift; + $opts{lc $key} = $val; + } else { + push @files, shift; + } + } + + if ($opts{filename}) { + push @files, $opts{filename}; + } + + # For file handles, write to temp first + if ($opts{file}) { + my $tmp = _new_temp(); + open my $out, '>', $tmp or return _err(FileOpenError, "Cannot write temp: $!"); + binmode $out; + my $fh = $opts{file}; + while (read($fh, my $buf, 8192)) { + print $out $buf; + } + close $out; + push @files, $tmp; + } + + return _err(OptionError, "No filename specified") unless @files; + + # Check files exist (unless pseudo-image like 'logo:' or 'xc:white') + for my $f (@files) { + next if $f =~ /^[a-z]+:/i; # pseudo-image + next if $f eq '-'; # stdin + unless (-e $f) { + return _err(FileOpenError, "unable to open image '$f': No such file or directory"); + } + } + + # Convert each file to a temp MIFF for our pipeline + for my $f (@files) { + my $tmp = _new_temp(); + + # Apply any pre-read attributes (size, etc.) + my @pre_flags; + if (my $size = $st->{attrs}{size}) { + push @pre_flags, '-size', $size; + } + + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, @pre_flags, $f, $tmp; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(FileOpenError, "unable to read '$f': $stderr"); + } + + push @{$st->{sources}}, $tmp; + push @$self, $tmp; + } + + return _ok(); +} + +*ReadImage = \&Read; +*read = \&Read; +*readimage = \&Read; + +# --- Write --- + +sub Write { + my $self = shift; + my $st = $self->_state; + + # Parse arguments + my %opts; + my $filename; + while (@_) { + if ($_[0] =~ /^(file|filename|quality|compression|depth|magick)$/i && @_ >= 2) { + my $key = shift; + my $val = shift; + $opts{lc $key} = $val; + } else { + $filename = shift; + } + } + $filename //= $opts{filename}; + return _err(OptionError, "No filename specified") unless defined $filename; + + # Flush pending operations + my $err = $self->_flush; + return $err if defined $err && "$err"; + + return _err(OptionError, "No image to write") unless @{$st->{sources}}; + + # Build write command with any output attributes + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, @{$st->{sources}}; + + push @cmd, '-quality', $opts{quality} if defined $opts{quality}; + push @cmd, '-compress', $opts{compression} if defined $opts{compression}; + push @cmd, '-depth', $opts{depth} if defined $opts{depth}; + + push @cmd, $filename; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(BlobError, "unable to write '$filename': $stderr"); + } + + return _ok(); +} + +*WriteImage = \&Write; +*write = \&Write; +*writeimage = \&Write; + +# --- Ping --- + +sub Ping { + my $self = shift; + my @files = @_; + + my @cmd = split(/\s+/, $IDENTIFY_CMD); + push @cmd, '-format', '%w %h %b %m\n', @files; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(FileOpenError, "unable to ping: $stderr"); + } + + # Parse: width height filesize format + my @lines = split /\n/, $stdout; + if (@lines && $lines[0] =~ /^(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/) { + return ($1, $2, $3, $4); + } + + return _err(CorruptImageError, "unable to parse identify output"); +} + +*PingImage = \&Ping; +*ping = \&Ping; +*pingimage = \&Ping; + +# --- Set --- + +sub Set { + my $self = shift; + my $st = $self->_state; + + # Single argument = size + if (@_ == 1) { + $st->{attrs}{size} = $_[0]; + return _ok(); + } + + my %args = @_; + for my $key (keys %args) { + $st->{attrs}{lc $key} = $args{$key}; + } + + return _ok(); +} + +*SetAttribute = \&Set; +*SetAttributes = \&Set; +*set = \&Set; +*setattribute = \&Set; +*setattributes = \&Set; + +# --- Get --- + +sub Get { + my ($self, @names) = @_; + my $st = $self->_state; + + # Some attributes are available without flushing + my %static = ( + version => "PerlOnJava Image::Magick $VERSION (CLI wrapper)", + copyright => 'Copyright (C) 1999 ImageMagick Studio LLC', + ); + + # For image-dependent attributes, we need to flush and run identify + my @results; + my $need_identify = 0; + + for my $name (@names) { + my $lc = lc $name; + if (exists $static{$lc}) { + push @results, $static{$lc}; + } elsif (exists $st->{attrs}{$lc}) { + push @results, $st->{attrs}{$lc}; + } else { + $need_identify = 1; + push @results, undef; # placeholder + } + } + + if ($need_identify && @{$st->{sources}}) { + # Flush pending ops so identify sees current state + my $err = $self->_flush; + return (map { undef } @names) if defined $err && "$err"; + + # Build identify format string + my %fmt_map = ( + columns => '%w', + width => '%w', + rows => '%h', + height => '%h', + 'x-resolution' => '%x', + 'y-resolution' => '%y', + depth => '%z', + magick => '%m', + format => '%r', + filesize => '%b', + colorspace => '%[colorspace]', + type => '%[type]', + class => '%r', + colors => '%k', + compression => '%C', + signature => '%#', + geometry => '%wx%h', + size => '%wx%h', + ); + + # Run identify for each requested attribute + for my $i (0 .. $#names) { + next if defined $results[$i]; # already resolved + my $lc = lc $names[$i]; + + my $fmt = $fmt_map{$lc}; + if (defined $fmt) { + my @cmd = split(/\s+/, $IDENTIFY_CMD); + push @cmd, '-format', $fmt, $st->{sources}[0]; + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit == 0) { + chomp $stdout; + # Convert numeric values + if ($lc =~ /^(columns|width|rows|height|depth|colors|filesize)$/) { + $stdout =~ s/[^0-9]//g if $lc ne 'filesize'; + $results[$i] = $stdout + 0; + } else { + $results[$i] = $stdout; + } + } + } + } + } + + return wantarray ? @results : $results[0]; +} + +*GetAttribute = \&Get; +*GetAttributes = \&Get; +*get = \&Get; +*getattribute = \&Get; +*getattributes = \&Get; + +# --- Clone --- + +sub Clone { + my $self = shift; + my $st = $self->_state; + + # Flush so we have a materialized image + my $err = $self->_flush; + return _err(ImageError, "Clone failed: $err") if defined $err && "$err"; + + my $clone = Image::Magick->new(); + my $cst = $clone->_state; + + # Copy temp files + for my $src (@{$st->{sources}}) { + my $tmp = _new_temp(); + require File::Copy; + File::Copy::copy($src, $tmp) + or return _err(FileOpenError, "Clone copy failed: $!"); + push @{$cst->{sources}}, $tmp; + push @$clone, $tmp; + } + + # Copy attributes + %{$cst->{attrs}} = %{$st->{attrs}}; + + return $clone; +} + +*CopyImage = \&Clone; +*CloneImage = \&Clone; +*clone = \&Clone; +*cloneimage = \&Clone; +*copy = \&Clone; +*copyimage = \&Clone; + +# --- Composite --- + +sub Composite { + my $self = shift; + my $st = $self->_state; + my %args = @_; + + # Flush base image + my $err = $self->_flush; + return $err if defined $err && "$err"; + + return _err(OptionError, "No base image") unless @{$st->{sources}}; + + # Get overlay image + my $overlay = $args{image}; + return _err(OptionError, "No overlay image specified") unless $overlay; + + my $overlay_st = $overlay->_state; + $overlay->_flush; + return _err(OptionError, "Overlay has no image") unless @{$overlay_st->{sources}}; + + my $output = _new_temp(); + my @cmd = split(/\s+/, $COMPOSITE_CMD); + + # Add compose method + if (my $compose = $args{compose}) { + push @cmd, '-compose', $compose; + } + + # Add geometry/offset + if (my $geom = $args{geometry}) { + push @cmd, '-geometry', $geom; + } elsif (defined $args{x} || defined $args{y}) { + my $x = $args{x} // 0; + my $y = $args{y} // 0; + my $sign_x = $x >= 0 ? '+' : ''; + my $sign_y = $y >= 0 ? '+' : ''; + push @cmd, '-geometry', "${sign_x}${x}${sign_y}${y}"; + } + + # Add gravity + push @cmd, '-gravity', $args{gravity} if $args{gravity}; + + push @cmd, $overlay_st->{sources}[0], $st->{sources}[0], $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(DelegateError, "composite failed: $stderr"); + } + + @{$st->{sources}} = ($output); + @{$st->{pending}} = (); + @$self = ($output); + + return _ok(); +} + +*CompositeImage = \&Composite; +*composite = \&Composite; + +# --- Montage --- + +sub Montage { + my $self = shift; + my $st = $self->_state; + my %args = @_; + + # Flush + my $err = $self->_flush; + return _err(ImageError, "Montage failed: $err") if defined $err && "$err"; + + return _err(OptionError, "No images for montage") unless @{$st->{sources}}; + + my $output = _new_temp(); + my @cmd = split(/\s+/, $MONTAGE_CMD); + + # Montage options + push @cmd, '-geometry', $args{geometry} if $args{geometry}; + push @cmd, '-tile', $args{tile} if $args{tile}; + push @cmd, '-background', $args{background} if $args{background}; + push @cmd, '-title', $args{title} if $args{title}; + push @cmd, '-frame', $args{frame} if $args{frame}; + push @cmd, '-shadow' if $args{shadow} && $args{shadow} =~ /^(true|1)$/i; + push @cmd, '-label', $args{label} if $args{label}; + push @cmd, '-font', $args{font} if $args{font}; + push @cmd, '-pointsize', $args{pointsize} if $args{pointsize}; + push @cmd, '-gravity', $args{gravity} if $args{gravity}; + push @cmd, '-mode', $args{mode} if $args{mode}; + + push @cmd, @{$st->{sources}}; + push @cmd, $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(DelegateError, "montage failed: $stderr"); + } + + # Return new Image::Magick object + my $result = Image::Magick->new(); + my $rst = $result->_state; + push @{$rst->{sources}}, $output; + push @$result, $output; + + return $result; +} + +*MontageImage = \&Montage; +*montage = \&Montage; +*montageimage = \&Montage; + +# --- ImageToBlob --- + +sub ImageToBlob { + my $self = shift; + my $st = $self->_state; + my %args = @_; + + my $err = $self->_flush; + return () if defined $err && "$err"; + + return () unless @{$st->{sources}}; + + my $format = $args{magick} || $args{format} || 'PNG'; + my $tmp = File::Temp->new(SUFFIX => ".$format", UNLINK => 1); + my $tmp_path = $tmp->filename; + close $tmp; + + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, $st->{sources}[0], "$format:$tmp_path"; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return (); + } + + open my $fh, '<:raw', $tmp_path or return (); + local $/; + my $blob = <$fh>; + close $fh; + unlink $tmp_path; + + return ($blob); +} + +*imagetoblob = \&ImageToBlob; +*toblob = \&ImageToBlob; +*blob = \&ImageToBlob; + +# --- BlobToImage --- + +sub BlobToImage { + my $self = shift; + my $st = $self->_state; + + for my $blob (@_) { + my $tmp_in = File::Temp->new(SUFFIX => '.blob', UNLINK => 1); + binmode $tmp_in; + print $tmp_in $blob; + my $in_path = $tmp_in->filename; + close $tmp_in; + + my $tmp_out = _new_temp(); + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, $in_path, $tmp_out; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + unlink $in_path; + if ($exit != 0) { + return _err(BlobError, "BlobToImage failed: $stderr"); + } + + push @{$st->{sources}}, $tmp_out; + push @$self, $tmp_out; + } + + return _ok(); +} + +*blobtoimage = \&BlobToImage; +*blobto = \&BlobToImage; + +# --- Mogrify (generic dispatch) --- + +# Map of method name => CLI flag +my %MOGRIFY_MAP = ( + 'Blur' => ['-blur', 'geometry|radius,sigma'], + 'GaussianBlur' => ['-gaussian-blur', 'geometry|radius,sigma'], + 'MotionBlur' => ['-motion-blur', 'geometry|radius,sigma,angle'], + 'Sharpen' => ['-sharpen', 'geometry|radius,sigma'], + 'UnsharpMask' => ['-unsharp', 'geometry|radius,sigma,gain,threshold'], + 'AdaptiveBlur' => ['-adaptive-blur', 'geometry|radius,sigma'], + 'AdaptiveSharpen' => ['-adaptive-sharpen', 'geometry|radius,sigma'], + 'Despeckle' => ['-despeckle', ''], + 'Enhance' => ['-enhance', ''], + 'ReduceNoise' => ['-enhance', ''], # IM7 uses enhance + 'Resize' => ['-resize', 'geometry|width,height'], + 'Scale' => ['-scale', 'geometry|width,height'], + 'Sample' => ['-sample', 'geometry|width,height'], + 'Thumbnail' => ['-thumbnail', 'geometry|width,height'], + 'AdaptiveResize' => ['-adaptive-resize', 'geometry|width,height'], + 'Crop' => ['-crop', 'geometry|width,height,x,y'], + 'Chop' => ['-chop', 'geometry|width,height,x,y'], + 'Extent' => ['-extent', 'geometry|width,height'], + 'Trim' => ['-trim', ''], + 'Flip' => ['-flip', ''], + 'Flop' => ['-flop', ''], + 'Rotate' => ['-rotate', 'degrees'], + 'Shear' => ['-shear', 'geometry|x,y'], + 'Roll' => ['-roll', 'geometry|x,y'], + 'Transpose' => ['-transpose', ''], + 'Transverse' => ['-transverse', ''], + 'AutoOrient' => ['-auto-orient', ''], + 'Strip' => ['-strip', ''], + 'Negate' => ['-negate', ''], + 'Normalize' => ['-normalize', ''], + 'Equalize' => ['-equalize', ''], + 'Contrast' => ['-contrast', ''], + 'AutoGamma' => ['-auto-gamma', ''], + 'AutoLevel' => ['-auto-level', ''], + 'Modulate' => ['-modulate', 'brightness,saturation,hue'], + 'Gamma' => ['-gamma', 'gamma'], + 'Level' => ['-level', 'levels'], + 'BrightnessContrast'=> ['-brightness-contrast','brightness,contrast'], + 'SigmoidalContrast' => ['-sigmoidal-contrast', 'geometry|contrast,mid-point'], + 'Posterize' => ['-posterize', 'levels'], + 'Solarize' => ['-solarize', 'threshold'], + 'Threshold' => ['-threshold', 'threshold'], + 'BlackThreshold' => ['-black-threshold', 'threshold'], + 'WhiteThreshold' => ['-white-threshold', 'threshold'], + 'Clamp' => ['-clamp', ''], + 'Colorize' => ['-colorize', 'blend'], + 'Colorspace' => ['-colorspace', 'colorspace'], + 'Grayscale' => ['-grayscale', 'method'], + 'Quantize' => ['-quantize', 'colorspace'], + 'Annotate' => ['-annotate', '_annotate'], + 'Draw' => ['-draw', '_draw'], + 'Border' => ['-border', 'geometry|width,height'], + 'Frame' => ['-frame', 'geometry|width,height,outer,inner'], + 'Raise' => ['-raise', 'geometry|width,height'], + 'Shade' => ['-shade', 'geometry|azimuth,elevation'], + 'Charcoal' => ['-charcoal', 'geometry|radius,sigma'], + 'Edge' => ['-edge', 'radius'], + 'Emboss' => ['-emboss', 'radius'], + 'Implode' => ['-implode', 'amount'], + 'OilPaint' => ['-paint', 'radius'], + 'Spread' => ['-spread', 'radius'], + 'Swirl' => ['-swirl', 'degrees'], + 'Wave' => ['-wave', 'geometry|amplitude,wavelength'], + 'Vignette' => ['-vignette', 'geometry|radius,sigma,x,y'], + 'Sepia' => ['-sepia-tone', 'threshold'], + 'SepiaTone' => ['-sepia-tone', 'threshold'], + 'Shadow' => ['-shadow', 'geometry|opacity,sigma,x,y'], + 'Sketch' => ['-sketch', 'geometry|radius,sigma,angle'], + 'Stegano' => ['-stegano', 'offset'], + 'Tint' => ['-tint', 'fill'], + 'Transparent' => ['-transparent', 'color'], + 'AddNoise' => ['-noise', 'noise'], + 'Deskew' => ['-deskew', 'threshold'], + 'Signature' => ['-identify', ''], # triggers SHA digest + 'Magnify' => ['-magnify', ''], + 'Minify' => ['-minify', ''], + 'Unique' => ['-unique-colors', ''], + 'Shave' => ['-shave', 'geometry|width,height'], + 'Splice' => ['-splice', 'geometry|width,height,x,y'], + 'WhiteBalance' => ['-white-balance', ''], +); + +sub Mogrify { + my $self = shift; + my $method = shift; + return $self->_do_mogrify($method, @_); +} + +*MogrifyImage = \&Mogrify; +*mogrify = \&Mogrify; + +sub _do_mogrify { + my ($self, $method, @args) = @_; + my $st = $self->_state; + + my $entry = $MOGRIFY_MAP{$method}; + return _err(OptionError, "Unrecognized method: $method") unless $entry; + + my ($flag, $param_spec) = @$entry; + + if ($param_spec eq '') { + # No-argument flag + push @{$st->{pending}}, $flag; + return _ok(); + } + + # Parse arguments + my %args; + if (@args == 1 && !ref $args[0]) { + # Single positional argument (e.g., Crop('100x100+10+10')) + # Use as geometry or first named param + my $val = $args[0]; + if ($param_spec eq '_annotate') { + return $self->_do_annotate(text => $val); + } elsif ($param_spec eq '_draw') { + return $self->_do_draw(primitive => $val); + } + push @{$st->{pending}}, $flag, $val; + return _ok(); + } + + %args = @args; + + # Special handlers + if ($param_spec eq '_annotate') { + return $self->_do_annotate(%args); + } + if ($param_spec eq '_draw') { + return $self->_do_draw(%args); + } + + # Build geometry or value from named params + my $value; + if (defined $args{geometry}) { + $value = $args{geometry}; + } elsif ($param_spec =~ /^geometry\|(.+)/) { + my @parts = split /,/, $1; + my @vals; + for my $p (@parts) { + last unless defined $args{$p}; + push @vals, $args{$p}; + } + if (@vals) { + $value = join('x', @vals[0..min(1,$#vals)]); + # Add offset for x,y params + if (@vals > 2) { + my $x = $vals[2] // 0; + my $y = $vals[3] // 0; + my $sx = $x >= 0 ? '+' : ''; + my $sy = $y >= 0 ? '+' : ''; + $value .= "${sx}${x}${sy}${y}"; + } + } + } else { + # Named params matching spec + my @parts = split /,/, $param_spec; + for my $p (@parts) { + if (defined $args{$p}) { + $value = $args{$p}; + last; + } + } + } + + if (defined $value) { + push @{$st->{pending}}, $flag, "$value"; + } else { + push @{$st->{pending}}, $flag; + } + + return _ok(); +} + +sub min { $_[0] < $_[1] ? $_[0] : $_[1] } + +# --- Annotate --- + +sub _do_annotate { + my ($self, %args) = @_; + my $st = $self->_state; + + my $text = $args{text} // ''; + + # Build annotate command + push @{$st->{pending}}, '-font', $args{font} if $args{font}; + push @{$st->{pending}}, '-pointsize', $args{pointsize} if $args{pointsize}; + push @{$st->{pending}}, '-fill', $args{fill} if $args{fill}; + push @{$st->{pending}}, '-stroke', $args{stroke} if $args{stroke}; + push @{$st->{pending}}, '-strokewidth', $args{strokewidth} if $args{strokewidth}; + push @{$st->{pending}}, '-gravity', $args{gravity} if $args{gravity}; + push @{$st->{pending}}, '-undercolor', $args{undercolor} if $args{undercolor}; + push @{$st->{pending}}, '-kerning', $args{kerning} if $args{kerning}; + + my $geom = '+0+0'; + if (defined $args{x} || defined $args{y}) { + my $x = $args{x} // 0; + my $y = $args{y} // 0; + my $sx = $x >= 0 ? '+' : ''; + my $sy = $y >= 0 ? '+' : ''; + $geom = "${sx}${x}${sy}${y}"; + } elsif ($args{geometry}) { + $geom = $args{geometry}; + } + + push @{$st->{pending}}, '-annotate', $geom, $text; + + return _ok(); +} + +# --- Draw --- + +sub _do_draw { + my ($self, %args) = @_; + my $st = $self->_state; + + push @{$st->{pending}}, '-fill', $args{fill} if $args{fill}; + push @{$st->{pending}}, '-stroke', $args{stroke} if $args{stroke}; + push @{$st->{pending}}, '-strokewidth', $args{strokewidth} if $args{strokewidth}; + + my $primitive = $args{primitive} // 'point'; + my $points = $args{points} // '0,0'; + push @{$st->{pending}}, '-draw', "$primitive $points"; + + return _ok(); +} + +# --- Compare --- + +sub Compare { + my $self = shift; + my %args = @_; + my $st = $self->_state; + + my $other = $args{image}; + return _err(OptionError, "No comparison image") unless $other; + + $self->_flush; + $other->_flush; + + my $other_st = $other->_state; + return _err(OptionError, "No images to compare") unless @{$st->{sources}} && @{$other_st->{sources}}; + + my $metric = $args{metric} || 'RMSE'; + my $output = _new_temp(); + + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, '-metric', $metric, $st->{sources}[0], $other_st->{sources}[0], $output; + + # compare writes metric to stderr + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + + # Create result image + my $result = Image::Magick->new(); + my $rst = $result->_state; + if (-e $output) { + push @{$rst->{sources}}, $output; + push @$result, $output; + } + + # Parse error metric from stderr + if ($stderr =~ /([\d.]+(?:e[+-]?\d+)?)/) { + $rst->{attrs}{error} = $1 + 0; + } + + return $result; +} + +*CompareImages = \&Compare; +*compare = \&Compare; +*compareimage = \&Compare; + +# --- Append --- + +sub Append { + my $self = shift; + my $st = $self->_state; + my %args = @_; + + $self->_flush; + return _err(OptionError, "No images to append") unless @{$st->{sources}}; + + my $output = _new_temp(); + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, @{$st->{sources}}; + push @cmd, ($args{stack} && $args{stack} =~ /^(true|1)$/i) ? '-append' : '+append'; + push @cmd, $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(ImageError, "Append failed: $stderr"); + } + + my $result = Image::Magick->new(); + my $rst = $result->_state; + push @{$rst->{sources}}, $output; + push @$result, $output; + return $result; +} + +*AppendImage = \&Append; +*append = \&Append; +*appendimage = \&Append; + +# --- Flatten --- + +sub Flatten { + my $self = shift; + my $st = $self->_state; + my %args = @_; + + $self->_flush; + return _err(OptionError, "No images to flatten") unless @{$st->{sources}}; + + my $output = _new_temp(); + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, @{$st->{sources}}; + push @cmd, '-background', $args{background} if $args{background}; + push @cmd, '-flatten', $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(ImageError, "Flatten failed: $stderr"); + } + + my $result = Image::Magick->new(); + my $rst = $result->_state; + push @{$rst->{sources}}, $output; + push @$result, $output; + return $result; +} + +*FlattenImage = \&Flatten; +*flatten = \&Flatten; +*flattenimage = \&Flatten; + +# --- Unsupported methods (die with clear error) --- + +for my $unsupported (qw( + Display DisplayImage display displayimage + Animate AnimateImage animate animateimage + GetPixel getpixel getPixel + SetPixel setpixel setPixel + GetPixels getpixels getPixels + SetPixels setpixels setPixels + GetAuthenticPixels GetVirtualPixels + GetAuthenticMetacontent GetVirtualMetacontent + SyncAuthenticPixels + RemoteCommand remote Remote +)) { + no strict 'refs'; + *{$unsupported} = sub { + die "Image::Magick::$unsupported() is not supported in the CLI wrapper.\n" + . "This method requires direct pixel/display access not available via the magick CLI.\n"; + }; +} + +# --- AUTOLOAD for Mogrify aliases --- + +sub AUTOLOAD { + my $self = shift; + our $AUTOLOAD; + my $method = $AUTOLOAD; + $method =~ s/.*:://; + + return if $method eq 'DESTROY'; + return if $method eq 'END'; + return if $method eq 'UNLOAD'; + + # Strip trailing 'Image' suffix (e.g. CropImage -> Crop) + my $base = $method; + $base =~ s/Image$//; + + # Try case-insensitive match in MOGRIFY_MAP + my $found; + for my $key (keys %MOGRIFY_MAP) { + if (lc $key eq lc $base) { + $found = $key; + last; + } + } + + if ($found) { + return $self->_do_mogrify($found, @_); + } + + die "Undefined Image::Magick method: $method\n"; +} + +# --- Coalesce --- + +sub Coalesce { + my $self = shift; + my $st = $self->_state; + + $self->_flush; + return _err(OptionError, "No images") unless @{$st->{sources}}; + + my $output = _new_temp(); + my @cmd = split(/\s+/, $CONVERT_CMD); + push @cmd, @{$st->{sources}}, '-coalesce', $output; + + my ($exit, $stdout, $stderr) = _run_cmd(@cmd); + if ($exit != 0) { + return _err(ImageError, "Coalesce failed: $stderr"); + } + + my $result = Image::Magick->new(); + my $rst = $result->_state; + push @{$rst->{sources}}, $output; + push @$result, $output; + return $result; +} + +*CoalesceImage = \&Coalesce; +*coalesce = \&Coalesce; +*coalesceimage = \&Coalesce; + +1; + +__END__ + +=head1 NAME + +Image::Magick - PerlOnJava CLI wrapper for ImageMagick + +=head1 SYNOPSIS + + use Image::Magick; + + my $img = Image::Magick->new; + $img->Read('input.jpg'); + $img->Resize(geometry => '50%'); + $img->Write('output.png'); + +=head1 DESCRIPTION + +This is a PerlOnJava-specific implementation of the Image::Magick API that +delegates to the ImageMagick CLI tools (C for IM7, C/C +for IM6). It provides the same API as the CPAN Image::Magick module but does not +require the native PerlMagick XS library. + +Requires ImageMagick CLI tools to be installed and in PATH. + +=head1 LIMITATIONS + +The following methods are not supported and will die with a descriptive error: + +=over 4 + +=item * Display(), Animate() — require X11 + +=item * GetPixel(), SetPixel(), GetPixels(), SetPixels() — per-pixel access + +=item * GetAuthenticPixels(), GetVirtualPixels() — low-level C pointer access + +=back + +=head1 AUTHOR + +Original PerlMagick by Kyle Shorter, ImageMagick Studio LLC. + +CLI wrapper for PerlOnJava by the PerlOnJava project. + +=head1 COPYRIGHT + +Copyright (C) 1999 ImageMagick Studio LLC. + +This is free software; you can redistribute it and/or modify it under +the same terms as Perl itself. + +=cut diff --git a/src/test/resources/module/Image-Magick/t/basic.t b/src/test/resources/module/Image-Magick/t/basic.t new file mode 100644 index 000000000..32ec9f2bd --- /dev/null +++ b/src/test/resources/module/Image-Magick/t/basic.t @@ -0,0 +1,128 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Test::More; +use File::Temp qw(tempdir tempfile); + +# Check if ImageMagick CLI is available +my $has_magick = 0; +for my $cmd (qw(magick convert)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick/) { + $has_magick = 1; + last; + } +} + +unless ($has_magick) { + plan skip_all => 'ImageMagick CLI tools not installed'; +} + +plan tests => 30; + +# --- Module loading --- +use_ok('Image::Magick'); + +# --- Constants --- +is(Image::Magick::Success(), 0, 'Success constant is 0'); +is(Image::Magick::ErrorException(), 400, 'ErrorException constant is 400'); +is(Image::Magick::QuantumDepth(), 16, 'QuantumDepth constant is 16'); +ok(Image::Magick::MaxRGB() > 0, 'MaxRGB is positive'); + +# --- Constructor --- +my $img = Image::Magick->new; +isa_ok($img, 'Image::Magick'); +is(ref $img, 'Image::Magick', 'Object is blessed arrayref'); +is(scalar @$img, 0, 'New object has no images'); + +# --- Set/Get static attributes --- +$img->Set(size => '100x100'); +is($img->Get('version'), "PerlOnJava Image::Magick $Image::Magick::VERSION (CLI wrapper)", + 'Get version returns wrapper version string'); + +# --- Create test image using pseudo-image --- +my $tmpdir = tempdir(CLEANUP => 1); + +my $img2 = Image::Magick->new; +$img2->Set(size => '100x100'); +my $err = $img2->Read('xc:red'); +ok(!$err, 'Read pseudo-image xc:red succeeds') or diag("Read error: $err"); +is(scalar @$img2, 1, 'One image in sequence after Read'); + +# --- Get image attributes --- +SKIP: { + skip "No image loaded", 4 unless @$img2; + + my $w = $img2->Get('width'); + my $h = $img2->Get('height'); + ok(defined $w && $w == 100, "Width is 100 (got: " . ($w // 'undef') . ")"); + ok(defined $h && $h == 100, "Height is 100 (got: " . ($h // 'undef') . ")"); + + my $fmt = $img2->Get('magick'); + ok(defined $fmt, "Format is defined (got: " . ($fmt // 'undef') . ")"); + + my $depth = $img2->Get('depth'); + ok(defined $depth && $depth > 0, "Depth is positive (got: " . ($depth // 'undef') . ")"); +} + +# --- Write --- +{ + my $outfile = "$tmpdir/test_write.png"; + my $err = $img2->Write($outfile); + ok(!$err, 'Write to PNG succeeds') or diag("Write error: $err"); + ok(-e $outfile, 'Output file exists'); + ok(-s $outfile > 0, 'Output file is non-empty'); +} + +# --- Read written file back --- +{ + my $img3 = Image::Magick->new; + my $err = $img3->Read("$tmpdir/test_write.png"); + ok(!$err, 'Read PNG file succeeds') or diag("Read error: $err"); + is(scalar @$img3, 1, 'One image after reading PNG'); +} + +# --- Ping --- +{ + my @info = $img2->Ping("$tmpdir/test_write.png"); + ok(@info >= 2, 'Ping returns at least width and height'); + is($info[0], 100, 'Ping width is 100'); + is($info[1], 100, 'Ping height is 100'); +} + +# --- Clone --- +{ + my $clone = $img2->Clone; + isa_ok($clone, 'Image::Magick'); + is(scalar @$clone, 1, 'Clone has one image'); +} + +# --- Image manipulation (Resize) --- +{ + my $img4 = Image::Magick->new; + $img4->Set(size => '200x200'); + $img4->Read('xc:blue'); + $img4->Resize(geometry => '50x50'); + my $out = "$tmpdir/resized.png"; + $img4->Write($out); + ok(-e $out, 'Resized image written'); + + my $check = Image::Magick->new; + $check->Read($out); + my $w = $check->Get('width'); + # Width should be 50 after resize + ok(defined $w && $w == 50, "Resized width is 50 (got: " . ($w // 'undef') . ")"); +} + +# --- ImageToBlob / BlobToImage --- +{ + my @blob = $img2->ImageToBlob(magick => 'PNG'); + ok(@blob > 0, 'ImageToBlob returns data'); + ok(length($blob[0]) > 0, 'Blob is non-empty'); + + my $img5 = Image::Magick->new; + my $err = $img5->BlobToImage($blob[0]); + ok(!$err, 'BlobToImage succeeds') or diag("BlobToImage error: $err"); +} + +done_testing(); diff --git a/src/test/resources/module/Image-Magick/t/errors.t b/src/test/resources/module/Image-Magick/t/errors.t new file mode 100644 index 000000000..7c38cfae2 --- /dev/null +++ b/src/test/resources/module/Image-Magick/t/errors.t @@ -0,0 +1,77 @@ +#!/usr/bin/perl +# Test error handling: missing files, bad operations, edge cases. +use strict; +use warnings; +use Test::More; +use File::Temp qw(tempdir); + +my $has_magick = 0; +for my $cmd (qw(magick convert)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick/) { + $has_magick = 1; + last; + } +} +unless ($has_magick) { + plan skip_all => 'ImageMagick CLI tools not installed'; +} + +plan tests => 10; + +use_ok('Image::Magick'); + +my $tmpdir = tempdir(CLEANUP => 1); + +# --- Read nonexistent file returns error --- +{ + my $img = Image::Magick->new; + my $err = $img->Read('/nonexistent/path/image.png'); + ok($err, 'Read nonexistent file returns error'); + like("$err", qr/unable to open|No such file/i, 'Error message mentions file not found'); +} + +# --- Write without reading returns error --- +{ + my $img = Image::Magick->new; + my $err = $img->Write("$tmpdir/empty.png"); + ok($err, 'Write with no image returns error'); +} + +# --- Read with no arguments returns error --- +{ + my $img = Image::Magick->new; + my $err = $img->Read(); + ok($err, 'Read with no arguments returns error'); +} + +# --- Get on empty image returns undef --- +{ + my $img = Image::Magick->new; + my $w = $img->Get('width'); + ok(!defined $w, 'Get width on empty image returns undef'); +} + +# --- Unsupported method dies --- +{ + my $img = Image::Magick->new; + $img->Set(size => '50x50'); + $img->Read('xc:white'); + eval { $img->Display() }; + like($@, qr/not supported/i, 'Display() dies with "not supported"'); +} + +# --- Undefined method dies --- +{ + my $img = Image::Magick->new; + eval { $img->CompletelyBogusMethod() }; + ok($@, 'Undefined method dies'); +} + +# --- Constants are correct values --- +{ + is(Image::Magick::Success(), 0, 'Success is 0'); + ok(Image::Magick::ErrorException() > 0, 'ErrorException is positive'); +} + +done_testing(); diff --git a/src/test/resources/module/Image-Magick/t/mogrify.t b/src/test/resources/module/Image-Magick/t/mogrify.t new file mode 100644 index 000000000..bdc3d1b14 --- /dev/null +++ b/src/test/resources/module/Image-Magick/t/mogrify.t @@ -0,0 +1,150 @@ +#!/usr/bin/perl +# Test image manipulation operations (Mogrify dispatch) +# Verifies operations produce measurable changes in image attributes. +use strict; +use warnings; +use Test::More; +use File::Temp qw(tempdir); + +my $has_magick = 0; +for my $cmd (qw(magick convert)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick/) { + $has_magick = 1; + last; + } +} +unless ($has_magick) { + plan skip_all => 'ImageMagick CLI tools not installed'; +} + +plan tests => 21; + +use_ok('Image::Magick'); + +my $tmpdir = tempdir(CLEANUP => 1); + +# Helper: create a test image of given size +sub make_image { + my ($w, $h, $color) = @_; + $color //= 'white'; + my $img = Image::Magick->new; + $img->Set(size => "${w}x${h}"); + $img->Read("xc:$color"); + return $img; +} + +# --- Resize --- +{ + my $img = make_image(200, 200); + $img->Resize(geometry => '100x100'); + $img->Write("$tmpdir/resized.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/resized.png"); + is($check->Get('width'), 100, 'Resize: width is 100'); + is($check->Get('height'), 100, 'Resize: height is 100'); +} + +# --- Crop --- +{ + my $img = make_image(200, 200, 'blue'); + $img->Crop(geometry => '50x50+10+10'); + $img->Write("$tmpdir/cropped.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/cropped.png"); + my $w = $check->Get('width'); + my $h = $check->Get('height'); + ok($w == 50, "Crop: width is 50 (got $w)"); + ok($h == 50, "Crop: height is 50 (got $h)"); +} + +# --- Rotate --- +{ + my $img = make_image(100, 50, 'red'); + $img->Rotate(degrees => 90); + $img->Write("$tmpdir/rotated.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/rotated.png"); + my $w = $check->Get('width'); + my $h = $check->Get('height'); + # After 90-degree rotation, width and height should swap + ok($w == 50, "Rotate 90: width is 50 (got $w)"); + ok($h == 100, "Rotate 90: height is 100 (got $h)"); +} + +# --- Flip (vertical mirror) --- +{ + my $img = make_image(100, 100, 'green'); + my $err = $img->Flip(); + ok(!$err, 'Flip: no error') or diag("Flip error: $err"); + $img->Write("$tmpdir/flipped.png"); + ok(-s "$tmpdir/flipped.png" > 0, 'Flip: output file non-empty'); +} + +# --- Flop (horizontal mirror) --- +{ + my $img = make_image(100, 100, 'green'); + my $err = $img->Flop(); + ok(!$err, 'Flop: no error') or diag("Flop error: $err"); + $img->Write("$tmpdir/flopped.png"); + ok(-s "$tmpdir/flopped.png" > 0, 'Flop: output file non-empty'); +} + +# --- Negate --- +{ + my $img = make_image(50, 50, 'black'); + my $err = $img->Negate(); + ok(!$err, 'Negate: no error') or diag("Negate error: $err"); + $img->Write("$tmpdir/negated.png"); + ok(-s "$tmpdir/negated.png" > 0, 'Negate: output file non-empty'); +} + +# --- Scale --- +{ + my $img = make_image(200, 200); + $img->Scale(geometry => '75x75'); + $img->Write("$tmpdir/scaled.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/scaled.png"); + is($check->Get('width'), 75, 'Scale: width is 75'); + is($check->Get('height'), 75, 'Scale: height is 75'); +} + +# --- Thumbnail --- +{ + my $img = make_image(400, 400); + $img->Thumbnail(geometry => '50x50'); + $img->Write("$tmpdir/thumb.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/thumb.png"); + is($check->Get('width'), 50, 'Thumbnail: width is 50'); + is($check->Get('height'), 50, 'Thumbnail: height is 50'); +} + +# --- Trim (trim blank borders) --- +{ + # Create 200x200 with content only in center 50x50 + my $img = make_image(200, 200, 'white'); + $img->Draw(fill => 'red', primitive => 'rectangle', points => '75,75 125,125'); + $img->Trim(); + $img->Write("$tmpdir/trimmed.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/trimmed.png"); + my $w = $check->Get('width'); + my $h = $check->Get('height'); + ok($w < 200, "Trim: width reduced (got $w)"); + ok($h < 200, "Trim: height reduced (got $h)"); +} + +# --- Border --- +{ + my $img = make_image(100, 100, 'blue'); + $img->Border(geometry => '10x10'); + $img->Write("$tmpdir/bordered.png"); + my $check = Image::Magick->new; + $check->Read("$tmpdir/bordered.png"); + is($check->Get('width'), 120, 'Border: width is 120 (100 + 2*10)'); + is($check->Get('height'), 120, 'Border: height is 120 (100 + 2*10)'); +} + +done_testing(); diff --git a/src/test/resources/module/Image-Magick/t/pipeline.t b/src/test/resources/module/Image-Magick/t/pipeline.t new file mode 100644 index 000000000..8e2247507 --- /dev/null +++ b/src/test/resources/module/Image-Magick/t/pipeline.t @@ -0,0 +1,130 @@ +#!/usr/bin/perl +# Test deferred execution pipeline and multi-operation chains. +# Verifies that queued operations are flushed correctly. +use strict; +use warnings; +use Test::More; +use File::Temp qw(tempdir); + +my $has_magick = 0; +for my $cmd (qw(magick convert)) { + my $out = `$cmd --version 2>&1`; + if ($? == 0 && $out =~ /ImageMagick/) { + $has_magick = 1; + last; + } +} +unless ($has_magick) { + plan skip_all => 'ImageMagick CLI tools not installed'; +} + +plan tests => 16; + +use_ok('Image::Magick'); + +my $tmpdir = tempdir(CLEANUP => 1); + +# --- Chain: Resize then Rotate --- +{ + my $img = Image::Magick->new; + $img->Set(size => '200x100'); + $img->Read('xc:red'); + $img->Resize(geometry => '100x50'); + $img->Rotate(degrees => 90); + $img->Write("$tmpdir/chain1.png"); + + my $check = Image::Magick->new; + $check->Read("$tmpdir/chain1.png"); + # 100x50 rotated 90 = 50x100 + is($check->Get('width'), 50, 'Chain resize+rotate: width is 50'); + is($check->Get('height'), 100, 'Chain resize+rotate: height is 100'); +} + +# --- Chain: Resize then Flip then Flop --- +{ + my $img = Image::Magick->new; + $img->Set(size => '150x150'); + $img->Read('xc:blue'); + $img->Resize(geometry => '75x75'); + $img->Flip(); + $img->Flop(); + $img->Write("$tmpdir/chain2.png"); + + my $check = Image::Magick->new; + $check->Read("$tmpdir/chain2.png"); + is($check->Get('width'), 75, 'Chain resize+flip+flop: width is 75'); + is($check->Get('height'), 75, 'Chain resize+flip+flop: height is 75'); +} + +# --- Get triggers flush --- +{ + my $img = Image::Magick->new; + $img->Set(size => '300x200'); + $img->Read('xc:green'); + $img->Resize(geometry => '150x100'); + # Get should flush pending resize before querying + my $w = $img->Get('width'); + my $h = $img->Get('height'); + is($w, 150, 'Get flushes pending ops: width is 150'); + is($h, 100, 'Get flushes pending ops: height is 100'); +} + +# --- Clone preserves state --- +{ + my $img = Image::Magick->new; + $img->Set(size => '100x100'); + $img->Read('xc:white'); + $img->Resize(geometry => '50x50'); + $img->Write("$tmpdir/pre_clone.png"); + + my $clone = $img->Clone; + $clone->Resize(geometry => '25x25'); + $clone->Write("$tmpdir/clone_out.png"); + + # Original should still be 50x50 + my $orig_check = Image::Magick->new; + $orig_check->Read("$tmpdir/pre_clone.png"); + is($orig_check->Get('width'), 50, 'Clone: original unchanged at 50'); + + # Clone should be 25x25 + my $clone_check = Image::Magick->new; + $clone_check->Read("$tmpdir/clone_out.png"); + is($clone_check->Get('width'), 25, 'Clone: clone resized to 25'); +} + +# --- Write to different formats --- +{ + my $img = Image::Magick->new; + $img->Set(size => '80x80'); + $img->Read('xc:red'); + + $img->Write("$tmpdir/out.jpg"); + ok(-s "$tmpdir/out.jpg" > 0, 'Write JPEG: non-empty'); + + $img->Write("$tmpdir/out.gif"); + ok(-s "$tmpdir/out.gif" > 0, 'Write GIF: non-empty'); + + $img->Write("$tmpdir/out.bmp"); + ok(-s "$tmpdir/out.bmp" > 0, 'Write BMP: non-empty'); + + $img->Write("$tmpdir/out.png"); + ok(-s "$tmpdir/out.png" > 0, 'Write PNG: non-empty'); +} + +# --- Blob round-trip preserves dimensions --- +{ + my $img = Image::Magick->new; + $img->Set(size => '64x64'); + $img->Read('xc:blue'); + $img->Resize(geometry => '32x32'); + + my @blob = $img->ImageToBlob(magick => 'PNG'); + ok(length($blob[0]) > 0, 'Blob round-trip: blob is non-empty'); + + my $img2 = Image::Magick->new; + $img2->BlobToImage($blob[0]); + is($img2->Get('width'), 32, 'Blob round-trip: width preserved at 32'); + is($img2->Get('height'), 32, 'Blob round-trip: height preserved at 32'); +} + +done_testing(); diff --git a/src/test/resources/module/Net-SSLeay/t/local/01_pod.t b/src/test/resources/module/Net-SSLeay/t/local/01_pod.t index 6a0361716..36b7335a7 100644 --- a/src/test/resources/module/Net-SSLeay/t/local/01_pod.t +++ b/src/test/resources/module/Net-SSLeay/t/local/01_pod.t @@ -9,15 +9,8 @@ use Test::Net::SSLeay; # Here's a snippet from the Changes file for Test::Pod 1.41: # Test::Pod no longer complains about the construct L, as it is no # longer illegal (as of Perl 5.11.3). -eval "use Test::Pod 1.41"; -if ($@) { - plan skip_all => "Test::Pod 1.41 required for testing pod"; -} - -all_pod_files_ok(qw( - blib/lib/Net/SSLeay.pm - blib/lib/Net/SSLeay/Handle.pm - helper_script/generate-test-pki - inc/Test/Net/SSLeay.pm - inc/Test/Net/SSLeay/Socket.pm -)); +# PerlOnJava: Net::SSLeay is an XS module that cannot be built, so the +# blib/ and helper_script/ directories do not exist. When Test::Pod is +# installed (e.g. via CPAN into ~/.perlonjava/lib/) the test would run +# and report "not ok" for every missing file. Skip unconditionally. +plan skip_all => "POD distribution test not applicable without a full build (no blib/ directory)";