Skip to content

fix: Test::Unit::Lite — three bug fixes + caller() line-number for both backends#656

Merged
fglock merged 5 commits intomasterfrom
feature/test-unit-lite-compat
May 2, 2026
Merged

fix: Test::Unit::Lite — three bug fixes + caller() line-number for both backends#656
fglock merged 5 commits intomasterfrom
feature/test-unit-lite-compat

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented May 1, 2026

Summary

Fixes three bugs uncovered by ./jcpan -t Test::Unit::Lite, plus the
underlying caller() line-number issue that prevented the last 2/39
subtests from passing. All 39/39 subtests now pass.

Bug 1 — File::Spec->splitdir scalar context

splitdir was always returning a list; in scalar context it now returns
the count, matching Perl's behaviour.

Bug 2 — Symbol::qualify_to_ref return type

Was returning a plain GLOB instead of a GLOB reference. Fixed to return
\*{"pkg::name"}.

Bug 3 — select auto-vivification of undef scalar

select select my $fh now auto-vivifies $fh into an anonymous GLOB
reference (matching Perl's open-my-filehandle behaviour), so a
subsequent tie *$fh, ... works correctly.

Bug 4 — caller() line numbers for multi-line calls (JVM backend)

handleArrowOperator (Dereference.java) and handleApplyOperator
(EmitSubroutine.java) emitted callCached/apply with the JVM
line-number attribute pointing to the closing ) token, not the
call-site token. Added ByteCodeSourceMapper.setDebugInfoLineNumber
with node.left.getIndex() immediately before each call instruction.

Bug 5 — same caller() fix for the interpreter backend

CALL_METHOD and CALL_SUB opcodes in CompileBinaryOperator.java
and CompileBinaryOperatorHelper.java were emitted without a
pcToTokenIndex entry, so getCallSiteInfo() fell back to the
statement-level entry (token after )). Changed to emitWithToken
using node.left.getIndex() at all three call-emit sites.

After both fixes, ./jperl and ./jperl --interpreter agree with
system Perl on the line reported by caller() inside a called
subroutine whose argument list spans multiple lines.

Test plan

  • make — all unit tests pass
  • ./jcpan -t Test::Unit::Lite39/39 subtests pass (was 37/39)
  • Updated dev/cpan-reports/cpan-compatibility.md: PARTIAL → PASS

Generated with Devin

fglock and others added 4 commits May 1, 2026 23:23
1. FileSpec.splitdir scalar context: `scalar File::Spec->splitdir($dir)` was
   returning the last component instead of the component count. Fixed by adding
   a scalar-context branch that returns `dirs.length` as a RuntimeScalar.
   Affected Test::Unit::Lite::AllTests::suite() path-depth calculation.

2. Symbol::qualify_to_ref returned GLOB instead of GLOBREFERENCE: the Java
   implementation was using `new RuntimeScalar().set(new RuntimeGlob(...))` which
   creates a GLOB-typed scalar, not a reference-to-glob. Changed to
   `new RuntimeGlob(object.toString()).createReference()` which produces
   GLOBREFERENCE (type=GLOBREFERENCE), matching how gensym() works.
   Without this fix, `keys %{ *{Symbol::qualify_to_ref("Pkg::")} }` returned
   empty, so list_tests() found 0 test methods.

3. IOOperator.select auto-vivification: when `select $var` is called with an
   undefined scalar, Perl auto-vivifies it into an anonymous GLOB reference
   (enabling the idiom `select select my $fh_null; tie *$fh_null, 'Class'`).
   Added the same pattern used by open/socket/pipe: create a new RuntimeGlob,
   wrap a fresh RuntimeIO in it, create a GLOBREFERENCE, and set() it back on
   the lvalue via runtimeList.getFirst().

Also added unit tests for cases 1 and 2 in directory.t and typeglob.t.

After these fixes: Test::Unit::Lite goes from 0/39 passing to 37/39 passing.
Remaining 2 failures are a pre-existing caller() line-number off-by-one for
multi-line function calls (not introduced by this PR).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
In handleArrowOperator (Dereference.java) and handleApplyOperator
(EmitSubroutine.java), the JVM line number at the callCached/apply
INVOKESTATIC was set to the token index of the closing ')' of the
argument list, not to the token index of the call site itself.

This caused caller() inside a called subroutine to report the wrong
source line — specifically the last line of the argument list rather
than the line where the call begins.

Fix: emit setDebugInfoLineNumber with node.left.getIndex() (the token
index of the receiver object / function name) immediately before the
INVOKESTATIC instruction in both call paths. This anchors the JVM
bytecode to the Perl line where the call starts, so caller() returns
the correct line number.

Verified by ./jcpan -t Test::Unit::Lite: all 39 subtests now pass
(was 37/39; test_assert_deep_equals and test_fail_assert_not_equals
had been failing because check_failures() verified caller() line
numbers from assertion failures).

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The caller() line number fix resolved the 2 remaining failures.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Same root cause as the JVM fix (af48192): CALL_SUB and CALL_METHOD
instructions in the interpreter's bytecode had their pcToTokenIndex
entries pointing to the token AFTER the closing ')' (inherited from the
statement-level emit), instead of the token at the call-site.

Fix all three interpreter call-emit sites:
- CompileBinaryOperator.java, coderef->() path: use emitWithToken with
  node.left.getIndex() (the coderef token index).
- CompileBinaryOperator.java, ->method() path: same, with the invocant's
  token index.
- CompileBinaryOperatorHelper.java, "()" switch case: change from
  emit() to emitWithToken(), driven by the call-site tokenIndex now
  passed from CompileBinaryOperator (node.left.getIndex()).

After fix: --interpreter and JVM both report the same line as system
Perl for caller() inside a called subroutine with multi-line args.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock changed the title fix: Test::Unit::Lite compatibility (splitdir scalar, qualify_to_ref, select auto-vivify) fix: Test::Unit::Lite — three bug fixes + caller() line-number for both backends May 2, 2026
…eparator

On Windows, File.separator is '\', so the previous code split only on
backslash, returning 1 component for forward-slash paths like "a/b/c".

Perl's File::Spec::Win32::splitdir splits on [/\\] (both separators).
Mirror that by using a platform-aware regex: "[/\\\\]" on Windows and
Pattern.quote(File.separator) elsewhere.

Fixes unit/directory.t test 10 on Windows CI.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit ca6f781 into master May 2, 2026
2 checks passed
@fglock fglock deleted the feature/test-unit-lite-compat branch May 2, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant