Skip to content

fix: jcpan -t LWP::UserAgent::Mockable#591

Merged
fglock merged 1 commit intomasterfrom
fix/lwp-mockable-20260428-161531
Apr 28, 2026
Merged

fix: jcpan -t LWP::UserAgent::Mockable#591
fglock merged 1 commit intomasterfrom
fix/lwp-mockable-20260428-161531

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 28, 2026

Summary

Fixes three independent bugs surfaced by jcpan -t LWP::UserAgent::Mockable. After the fix, all 73 tests across the 14 test files pass.

1. IO::Socket::INET with Timeout always failed (EIO)

SocketIO.connect() was auto-binding the local NIO socket to the target's IP address, which fails with BindException: Can't assign requested address for any remote host — surfacing as a bogus Input/output error ($! = EIO). Now binds to the wildcard address (0.0.0.0:0) instead, best-effort.

2. Storable::dclone died on objects whose package inherits AUTOLOAD

InheritanceResolver.findMethodInHierarchy falls back to AUTOLOAD when a method isn't found, but Storable's STORABLE_freeze / STORABLE_thaw lookup must NOT — Perl's real Storable uses gv_fetchmethod_autoload(..., FALSE). With AUTOLOAD fallback enabled, our code tried to invoke HTTP::Message::AUTOLOAD as STORABLE_freeze and crashed with Can't locate object method "" via package "".

Added a checkAutoload parameter (with a separate cache slot) to findMethodInHierarchy and used it for all STORABLE_freeze / STORABLE_thaw lookups.

3. Storable::retrieve broke URI overload globally and built the wrong reference kind for STORABLE_thaw

Two sub-issues:

  • The SX_HOOK and !!perl/freeze: records didn't encode the original reference type, so on read we always built a hash ref. URI's STORABLE_thaw does $$self = $str and croaks with Not a SCALAR reference. Now encode the ref type (S/A/H byte for binary; freezeS: / freezeA: / freeze: tag prefix for YAML); the reader builds a scalar / array / hash ref accordingly. The plain freeze: tag is still accepted on read for backward compatibility.

  • Blessing into a class before it's loaded allocates a positive (non-overloaded) blessId. That id is cached forever, so even after the class is later loaded with use overload, every subsequent URI->new(...) skipped overload dispatch — strings printed as URI::http=SCALAR(0x...) and eq comparisons failed. On retrieve, best-effort require the class before bless so its overload registers first.

Test plan

  • jcpan -t LWP::UserAgent::MockableResult: PASS (was FAIL — 7 of 14 test files dubious / non-zero exit).
  • make (full unit test suite) — green.
  • Configuration.java updated by injectGitInfo.

@fglock fglock force-pushed the fix/lwp-mockable-20260428-161531 branch from d5c8c8b to 846d4d8 Compare April 28, 2026 14:21
Three independent bugs surfaced by LWP::UserAgent::Mockable's
record/playback test suite:

1. IO::Socket::INET with Timeout always failed with EIO.
   SocketIO.connect() was auto-binding the local NIO socket to the
   target's IP address, which fails with BindException for any remote
   target. Bind to the wildcard address (0.0.0.0:0) instead and make
   the bind best-effort.

2. Storable::dclone died on objects whose package inherits AUTOLOAD
   (e.g. HTTP::Request via HTTP::Message::AUTOLOAD).
   InheritanceResolver.findMethodInHierarchy falls back to AUTOLOAD,
   but Storable's STORABLE_freeze/thaw lookup must NOT — Perl's real
   Storable uses gv_fetchmethod_autoload(..., FALSE). Added a
   checkAutoload parameter (with a separate cache slot) and used it
   for all STORABLE_freeze / STORABLE_thaw lookups.

3. Storable::retrieve broke URI overload globally and passed the wrong
   reference kind to STORABLE_thaw.
   - SX_HOOK and !!perl/freeze: records didn't encode the original
     reference type, so on read we always built a hash ref; URI's
     STORABLE_thaw does "$$self = $str" and died with "Not a SCALAR
     reference". Encode the ref type (S/A/H byte for binary,
     freezeS: / freezeA: / freeze: tag prefix for YAML); reader
     builds a scalar / array / hash ref accordingly. The plain
     "freeze:" tag is still accepted on read for backward
     compatibility.
   - Blessing into a class before it's loaded allocates a *positive*
     (non-overloaded) blessId that is then cached forever; later
     "URI->new(...)" skipped overload dispatch and "$uri" stringified
     as "URI::http=SCALAR(0x...)". On retrieve, best-effort require
     the class before bless so its overload registers first.

jcpan -t LWP::UserAgent::Mockable now passes all 73 tests across 14
files; all PerlOnJava unit tests still green.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the fix/lwp-mockable-20260428-161531 branch from 846d4d8 to 9210e35 Compare April 28, 2026 15:19
@fglock fglock merged commit 2070d2d into master Apr 28, 2026
2 checks passed
@fglock fglock deleted the fix/lwp-mockable-20260428-161531 branch April 28, 2026 15:41
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