|
| 1 | +# Fix Unit Test Parity with System Perl |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +Three unit test files in `src/test/resources/unit/` produce different results under jperl vs system Perl (v5.42). The goal is to fix jperl to match system Perl behavior, then update the tests accordingly. |
| 6 | + |
| 7 | +## Failing Tests Summary |
| 8 | + |
| 9 | +| Test File | Failing Tests | Root Cause | |
| 10 | +|-----------|--------------|------------| |
| 11 | +| `string_interpolation.t` | Test 4: `$\` interpolation | `"$\\"` in jperl produces only the value of `$\`, but system Perl produces `value_of($\)` + literal `\` | |
| 12 | +| `sysread_syswrite.t` | Test 3: sysread from in-memory handle | jperl returns bytes read; system Perl returns `undef` | |
| 13 | +| `ipc_open3.t` | Tests 3, 5, 7: stderr capture with false `$err` | jperl creates a separate stderr handle; system Perl merges stderr into stdout when `$err` is false (`""`) | |
| 14 | + |
| 15 | +## Detailed Analysis |
| 16 | + |
| 17 | +### 1. `$\` interpolation in double-quoted strings |
| 18 | + |
| 19 | +**Observed behavior:** |
| 20 | +``` |
| 21 | +# System Perl: |
| 22 | +perl -e '$\ = "ABC"; print "[$\\]";' # → [ABC\]ABC |
| 23 | +# jperl: |
| 24 | +./jperl -e '$\ = "ABC"; print "[$\\]";' # → [ABC]ABC |
| 25 | +``` |
| 26 | + |
| 27 | +In `"$\\"`, system Perl parses this as: interpolate `$\` + literal backslash (from `\\`). jperl only interpolates `$\` and loses the trailing `\`. |
| 28 | + |
| 29 | +The issue is in how jperl's string interpolation scanner handles the `\\` escape sequence after recognizing the `$\` variable. After consuming `$` + `\` for the variable name, the second `\` should still be processed as part of the string (forming `\\` → `\` with the next char... but the next char is `"` which ends the string). Actually, the correct parse is: `$\` consumes only one `\`, then `\` + `"` → but that would be an escaped quote... |
| 30 | + |
| 31 | +System Perl must be parsing `"$\\"` as: `$\` (variable, consumes one `\`) then `\"` → no, because the string would be unterminated. |
| 32 | + |
| 33 | +**More likely**: system Perl parses `"$\\"` by recognizing `$\` as the variable AND recognizing `\\` as an escape producing `\`, with the variable consuming only the `$` + first `\`, and the escape consuming `\` + `\`. This means the variable name `$\` and the escape `\\` share the middle `\` character... which seems unlikely. |
| 34 | + |
| 35 | +**Investigation needed:** Write additional unit tests to clarify exact parsing behavior for `$\` in various DQ string contexts. |
| 36 | + |
| 37 | +### 2. sysread on in-memory file handles |
| 38 | + |
| 39 | +System Perl's `sysread()` does not work on in-memory file handles (opened via `open(my $fh, '<', \$scalar)`). It returns `undef`. jperl currently supports this, returning actual bytes read. |
| 40 | + |
| 41 | +**Fix:** jperl's sysread should return `undef` (and warn) when the filehandle is an in-memory handle, matching system Perl. |
| 42 | + |
| 43 | +### 3. IPC::Open3 with false `$err` argument |
| 44 | + |
| 45 | +Per IPC::Open3 documentation: "If CHLD_ERR is false, or the same file descriptor as CHLD_OUT, then STDOUT and STDERR of the child are on the same filehandle." |
| 46 | + |
| 47 | +When `$err = ""` (which is false), system Perl merges stderr with stdout. jperl incorrectly creates a separate stderr handle. |
| 48 | + |
| 49 | +**Fix:** jperl's IPC::Open3 implementation should check if the `$err` argument is false and merge stderr into stdout accordingly. |
| 50 | + |
| 51 | +## Plan |
| 52 | + |
| 53 | +For each issue: |
| 54 | +1. Fix jperl to match system Perl behavior (jperl will then fail the same tests system Perl fails) |
| 55 | +2. Fix the tests so they pass on both system Perl and jperl |
| 56 | + |
| 57 | +Only when uncertain about system Perl behavior, add new unit tests to clarify before fixing jperl. |
| 58 | + |
| 59 | +### Issue 1: `$\` interpolation in DQ strings |
| 60 | + |
| 61 | +**Fix jperl:** In the string interpolation scanner, `$\` greedily captures the `\` as part of the variable name. After `$\` is consumed, the next characters are processed normally as string content (not as escape sequences starting with the consumed `\`). So `"$\\"` = value_of(`$\`) + literal `\` (from the remaining `\\` → escaped backslash). jperl currently drops the trailing `\`. |
| 62 | + |
| 63 | +Key system Perl behavior (confirmed by exploratory tests): |
| 64 | +- `"$\\"` with `$\ = "SEP"` → `SEP\` |
| 65 | +- `"$\n"` → value_of(`$\`) + literal `n` (NOT newline — the `\` was consumed by `$\`) |
| 66 | +- `"$\t"` → value_of(`$\`) + literal `t` (NOT tab) |
| 67 | + |
| 68 | +**Then fix tests:** Update `string_interpolation.t` test 4 expected value. |
| 69 | + |
| 70 | +### Issue 2: sysread on in-memory file handles |
| 71 | + |
| 72 | +**Fix jperl:** `sysread()` on in-memory file handles (opened via `open($fh, '<', \$scalar)`) should return `undef`, matching system Perl. |
| 73 | + |
| 74 | +**Then fix tests:** Update `sysread_syswrite.t` test 3 expected value. |
| 75 | + |
| 76 | +### Issue 3: IPC::Open3 with false `$err` argument |
| 77 | + |
| 78 | +**Fix jperl:** When the `$err` argument to `open3()` is false (`""`, `0`, `undef`), stderr should be merged into stdout, not captured separately. This matches system Perl and the IPC::Open3 documentation. |
| 79 | + |
| 80 | +**Then fix tests:** Update `ipc_open3.t` tests 3, 5, 7 expected values. |
| 81 | + |
| 82 | +### Final verification |
| 83 | + |
| 84 | +- Run `make` to ensure no regressions |
| 85 | +- Run `prove src/test/resources/unit` to confirm all tests pass |
| 86 | + |
| 87 | +## Progress Tracking |
| 88 | + |
| 89 | +### Current Status: Complete (2026-04-09) |
| 90 | + |
| 91 | +PR: https://github.com/fglock/PerlOnJava/pull/476 |
| 92 | + |
| 93 | +### Completed Phases |
| 94 | +- [x] Analysis: identified 3 issues via diff of perl vs jperl output |
| 95 | +- [x] Exploratory tests: confirmed `$\` parsing behavior in system Perl |
| 96 | +- [x] Issue 1: Fixed `$\` interpolation in `StringDoubleQuoted.java` — removed `\` from non-interpolating chars |
| 97 | +- [x] Issue 1: Updated `string_interpolation.t` test 4 |
| 98 | +- [x] Issue 2: Fixed sysread in `IOOperator.java` — return undef for in-memory handles |
| 99 | +- [x] Issue 2: Updated `sysread_syswrite.t` test 3 |
| 100 | +- [x] Issue 3: Fixed `IPCOpen3.java` `isUsableHandle()` — check truthiness not definedness |
| 101 | +- [x] Issue 3: Updated `ipc_open3.t` tests 3, 5, 7 |
| 102 | +- [x] `make` passes, `prove src/test/resources/unit` all tests successful |
| 103 | + |
| 104 | +### Files Modified |
| 105 | +- `src/main/java/org/perlonjava/frontend/parser/StringDoubleQuoted.java` |
| 106 | +- `src/main/java/org/perlonjava/runtime/operators/IOOperator.java` |
| 107 | +- `src/main/java/org/perlonjava/runtime/perlmodule/IPCOpen3.java` |
| 108 | +- `src/test/resources/unit/string_interpolation.t` |
| 109 | +- `src/test/resources/unit/sysread_syswrite.t` |
| 110 | +- `src/test/resources/unit/ipc_open3.t` |
0 commit comments