Fix Mock does not intercept package-private methods in OSGi correctly - #2385
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (6)
📝 WalkthroughWalkthroughThe PR updates ByteBuddy mock locality detection for OSGi class loaders, adds an OSGi-style loader and Java fixtures, and tests package-private mocking from Java and Groovy, including Java 8 behavior. Release notes document the limitation. ChangesOSGi package-private mock fix
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant EmbeddedSpecRunner
participant OsgiTestClassLoader
participant ByteBuddyMockFactory
participant PackagePrivateMember
EmbeddedSpecRunner->>OsgiTestClassLoader: load embedded OSGi test classes
EmbeddedSpecRunner->>ByteBuddyMockFactory: create ByteBuddy mock
ByteBuddyMockFactory->>OsgiTestClassLoader: check interface visibility
EmbeddedSpecRunner->>PackagePrivateMember: invoke package-private method from Java or Groovy
PackagePrivateMember-->>EmbeddedSpecRunner: return mocked or original value
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java`:
- Around line 103-124: Handle bootstrap-loaded targets in
ByteBuddyMockFactory.isLocalMock before calling
loadClassIfAvailableInClassLoader, since targetClass.getClassLoader() can be
null. Update the null-handling so a bootstrap-loaded target is treated as
non-local without invoking loader.loadClass(), and make
loadClassIfAvailableInClassLoader safely cope with a null ClassLoader. Use the
existing symbols isLocalMock and loadClassIfAvailableInClassLoader to apply the
fix.
In
`@spock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/OsgiPackagePrivateMemberMockSpec.groovy`:
- Around line 27-30: The embedded spec in OsgiPackagePrivateMemberMockSpec
should explicitly use the ByteBuddy mock maker instead of relying on the
default. Update the Mock call inside the EmbeddedSpecRunner string in
OsgiPackagePrivateMemberMockSpec to pass the mockMaker argument with
spock.mock.MockMakers.byteBuddy so this regression test specifically exercises
the ByteBuddyMockFactory path and does not pass accidentally under a different
default mock maker.
In
`@spock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/OsgiTestClassLoader.groovy`:
- Around line 45-50: The resource stream opened in
OsgiTestClassLoader.getResourceAsStream is not closed after reading, which can
leave classpath handles open across repeated test runs. Update the class loading
logic around hostCl.getResourceAsStream and defineClass to read the bytes within
a close-safe block, ensuring the stream is always closed even when
ClassNotFoundException is thrown or class data is loaded successfully.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d0aa8646-b26e-4d7d-b597-1b4677c79e86
📒 Files selected for processing (6)
docs/release_notes.adocspock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.javaspock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/OsgiPackagePrivateMemberMockSpec.groovyspock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/OsgiTestClassLoader.groovyspock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/testclasses/InvocationFromJava.javaspock-specs/src/test/groovy/org/spockframework/smoke/mock/osgi/testclasses/PkgPrivateMemberClass.java
Greptile SummaryThis PR fixes a bug where Spock mocks fail to intercept package-private methods in OSGi environments by replacing the
Confidence Score: 3/5The core logic change is correct and well-tested for the OSGi scenario, but The fix correctly addresses the OSGi locality check and is backed by a targeted integration test. The missing null guard on the ClassLoader is a real, reproducible NPE (not caught by the existing spock-core/src/main/java/org/spockframework/mock/runtime/ByteBuddyMockFactory.java — specifically Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant BB as ByteBuddyMockFactory
participant DM as determineBestClassLoadingStrategy
participant LM as isLocalMock
participant TL as targetClassLoader
BB->>DM: determineBestClassLoadingStrategy(targetClass, settings)
DM->>LM: isLocalMock(targetClass, additionalInterfaces)
LM->>TL: loadClass(ISpockMockObject.class.name)
TL-->>LM: "loaded Class<?> (or ClassNotFoundException)"
LM->>LM: "identity check: loaded == ISpockMockObject.class?"
loop each additionalInterface
LM->>TL: loadClass(ifClass.name)
TL-->>LM: "loaded Class<?> (or ClassNotFoundException)"
LM->>LM: "identity check: loaded == ifClass?"
end
LM-->>DM: true (local) / false (non-local)
alt "isLocal == true"
DM-->>BB: ClassLoadingStrategy.UsingLookup (package-private access)
else "isLocal == false"
DM-->>BB: ClassLoadingStrategy.INJECTION or WRAPPER
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant BB as ByteBuddyMockFactory
participant DM as determineBestClassLoadingStrategy
participant LM as isLocalMock
participant TL as targetClassLoader
BB->>DM: determineBestClassLoadingStrategy(targetClass, settings)
DM->>LM: isLocalMock(targetClass, additionalInterfaces)
LM->>TL: loadClass(ISpockMockObject.class.name)
TL-->>LM: "loaded Class<?> (or ClassNotFoundException)"
LM->>LM: "identity check: loaded == ISpockMockObject.class?"
loop each additionalInterface
LM->>TL: loadClass(ifClass.name)
TL-->>LM: "loaded Class<?> (or ClassNotFoundException)"
LM->>LM: "identity check: loaded == ifClass?"
end
LM-->>DM: true (local) / false (non-local)
alt "isLocal == true"
DM-->>BB: ClassLoadingStrategy.UsingLookup (package-private access)
else "isLocal == false"
DM-->>BB: ClassLoadingStrategy.INJECTION or WRAPPER
end
Reviews (1): Last reviewed commit: "Fix Mock does not intercept package-priv..." | Re-trigger Greptile |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2385 +/- ##
============================================
- Coverage 82.29% 82.28% -0.01%
- Complexity 4882 4887 +5
============================================
Files 474 474
Lines 15258 15267 +9
Branches 1961 1964 +3
============================================
+ Hits 12557 12563 +6
- Misses 2002 2004 +2
- Partials 699 700 +1
🚀 New features to boost your workflow:
|
This comment has been minimized.
This comment has been minimized.
We now do a real search in the mock targets ClassLoader to check if the Spock classes and additional interfaces are visible instead of asking the Parent ClassLoader hierarchy, which does not work in an OSGi environment. Fixes spockframework#2384
We now do a real search in the mock targets ClassLoader to check if the Spock classes and additional interfaces are visible instead of asking the Parent ClassLoader hierarchy, which does not work in an OSGi environment.
Fixes #2384