From 0776d973fc5b06d7f7a16ac31e45aff24f1ab888 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:08:53 +0200 Subject: [PATCH 01/11] Bump bytebuddy from 1.17.6 to 1.17.7 (#3712) Bumps `bytebuddy` from 1.17.6 to 1.17.7. Updates `net.bytebuddy:byte-buddy` from 1.17.6 to 1.17.7 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.6...byte-buddy-1.17.7) Updates `net.bytebuddy:byte-buddy-agent` from 1.17.6 to 1.17.7 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.6...byte-buddy-1.17.7) Updates `net.bytebuddy:byte-buddy-android` from 1.17.6 to 1.17.7 - [Release notes](https://github.com/raphw/byte-buddy/releases) - [Changelog](https://github.com/raphw/byte-buddy/blob/master/release-notes.md) - [Commits](https://github.com/raphw/byte-buddy/compare/byte-buddy-1.17.6...byte-buddy-1.17.7) --- updated-dependencies: - dependency-name: net.bytebuddy:byte-buddy dependency-version: 1.17.7 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: net.bytebuddy:byte-buddy-agent dependency-version: 1.17.7 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: net.bytebuddy:byte-buddy-android dependency-version: 1.17.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8866e13123..1235fd2ac7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -bytebuddy = "1.17.6" +bytebuddy = "1.17.7" errorprone = "2.41.0" jacoco = "0.8.13" junit4 = "4.13.2" From ef2fd6f8e12df2db9b1c3aef067c33f6fe2aba95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 08:38:19 +0200 Subject: [PATCH 02/11] Bump com.gradle.develocity from 4.1 to 4.1.1 (#3713) Bumps com.gradle.develocity from 4.1 to 4.1.1. --- updated-dependencies: - dependency-name: com.gradle.develocity dependency-version: 4.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 541b7a8ff3..0645b1a815 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,7 +13,7 @@ dependencyResolutionManagement { } plugins { - id("com.gradle.develocity") version "4.1" + id("com.gradle.develocity") version "4.1.1" id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } From 37bb3e5062bbedda96dc3810c5e3d4f5c0c644e0 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Fri, 22 Aug 2025 19:32:45 +0200 Subject: [PATCH 03/11] Fix metadata generation on GraalVM (#3710) Avoids using the system loader on Graal, by always choosing an "expensive" class loadings strategy, if a Graal image code is discovered. In the compiled artifact, this will anyways not effectuate, so that this is not a problem on Graal when only affecting training runs. --------- Co-authored-by: Philipp Page --- .github/workflows/ci.yml | 37 +++++++++++++- .../bytebuddy/SubclassBytecodeGenerator.java | 5 +- .../graalvm-tests/.gitignore | 1 + .../graalvm-tests/build.gradle.kts | 49 +++++++++++++++++++ .../java/org/mockito/graalvm/DummyObject.java | 11 +++++ .../graalvm/GraalVMSubclassMockMakerTest.java | 24 +++++++++ settings.gradle.kts | 1 + 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 mockito-integration-tests/graalvm-tests/.gitignore create mode 100644 mockito-integration-tests/graalvm-tests/build.gradle.kts create mode 100644 mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/DummyObject.java create mode 100644 mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/GraalVMSubclassMockMakerTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b828b5d5a0..b181b41e90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,6 +188,41 @@ jobs: files: mockito-integration-tests/android-tests/build/reports/coverage/android-tests/debug/connected/report.xml fail_ci_if_error: true + # + # GraalVM native tests job + # + graalvm: + runs-on: ubuntu-latest + if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')" + + steps: + - name: 1. Check out code + uses: actions/checkout@v5 + with: + fetch-depth: '0' + + - name: 2. Setup GraalVM + uses: graalvm/setup-graalvm@v1.3.5 + with: + java-version: "21" + distribution: "graalvm" + cache: gradle + + - name: 3. Run GraalVM native tests with metadata + run: ./gradlew :mockito-integration-tests:graalvm-tests:nativeTest -PenableGraalTracingAgent --scan + + - name: 4. Verify GraalVM metadata generation + run: | + REFLECT_CONFIG="mockito-integration-tests/graalvm-tests/src/test/resources/META-INF/native-image/org.mockito/graalvm-tests/reflect-config.json" + test -f "$REFLECT_CONFIG" || { + echo "reflect-config.json not found at $REFLECT_CONFIG" + exit 1 + } + grep -q "org\.mockito\.internal\.creation\.bytebuddy\.codegen\.DummyObject\$MockitoMock" "$REFLECT_CONFIG" || { + echo "DummyObject mock not found in reflect-config.json" + exit 1 + } + # # Release job, only for pushes to the main development branch # @@ -195,7 +230,7 @@ jobs: permissions: contents: write runs-on: ubuntu-latest - needs: [java, android] # both build jobs must pass before we can release + needs: [java, android, graalvm] # all build jobs must pass before we can release if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) diff --git a/mockito-core/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/mockito-core/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java index 4701e4d140..21193e82da 100644 --- a/mockito-core/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/mockito-core/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -34,6 +34,7 @@ import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default; import net.bytebuddy.dynamic.loading.MultipleParentClassLoader; import net.bytebuddy.dynamic.scaffold.TypeValidation; import net.bytebuddy.implementation.FieldAccessor; @@ -272,7 +273,9 @@ public Class mockClass(MockFeatures features) { .or(hasParameters(whereAny(hasType(isPackagePrivate()))))); } ClassLoadingStrategy strategy; - if (localMock) { + if (GraalImageCode.getCurrent().isDefined()) { + strategy = Default.WRAPPER; + } else if (localMock) { strategy = handler.classLoadingStrategy(features.mockedType); } else if (classLoader == MockAccess.class.getClassLoader()) { strategy = handler.classLoadingStrategy(InjectionBase.class); diff --git a/mockito-integration-tests/graalvm-tests/.gitignore b/mockito-integration-tests/graalvm-tests/.gitignore new file mode 100644 index 0000000000..538ef4fcab --- /dev/null +++ b/mockito-integration-tests/graalvm-tests/.gitignore @@ -0,0 +1 @@ +src/test/resources/META-INF/native-image/org.mockito/graalvm-tests/ diff --git a/mockito-integration-tests/graalvm-tests/build.gradle.kts b/mockito-integration-tests/graalvm-tests/build.gradle.kts new file mode 100644 index 0000000000..6d5a71df45 --- /dev/null +++ b/mockito-integration-tests/graalvm-tests/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("java") + id("mockito.test-conventions") + id("org.graalvm.buildtools.native") version "0.11.0" +} + +description = "Test suite for exercising subclass mock maker with GraalVM native image" + +dependencies { + implementation(project(":mockito-core")) + implementation(project(":mockito-extensions:mockito-subclass")) + testImplementation(libs.junit4) + testImplementation(libs.assertj) + testImplementation(libs.junit.platform.launcher) +} + +// org.graalvm.buildtools.native:0.11.0 requires Java >=17 +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +graalvmNative { + binaries { + named("test") { + buildArgs.addAll("--verbose", "-O0") + } + } + + agent { + enabled = project.hasProperty("enableGraalTracingAgent") + defaultMode = "standard" + + metadataCopy { + inputTaskNames.add("test") + outputDirectories.add("src/test/resources/META-INF/native-image/org.mockito/graalvm-tests") + mergeWithExisting = false + } + } +} + +tasks { + test { + forkEvery = 1 + if (project.hasProperty("enableGraalTracingAgent")) { + finalizedBy("metadataCopy") + } + } +} diff --git a/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/DummyObject.java b/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/DummyObject.java new file mode 100644 index 0000000000..ea1efa8144 --- /dev/null +++ b/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/DummyObject.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.graalvm; + +public class DummyObject { + public String getValue() { + return "original"; + } +} diff --git a/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/GraalVMSubclassMockMakerTest.java b/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/GraalVMSubclassMockMakerTest.java new file mode 100644 index 0000000000..2acf954c1e --- /dev/null +++ b/mockito-integration-tests/graalvm-tests/src/test/java/org/mockito/graalvm/GraalVMSubclassMockMakerTest.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.graalvm; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import org.junit.Test; + +public final class GraalVMSubclassMockMakerTest { + + @Test + public void test_subclass_mock_maker_with_simple_object() { + DummyObject dummyMock = mock(DummyObject.class, withSettings()); + + when(dummyMock.getValue()).thenReturn("mocked"); + + assertEquals("mocked", dummyMock.getValue()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0645b1a815..2033ecc64a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -45,6 +45,7 @@ include( "mockito-integration-tests:osgi-tests", "mockito-integration-tests:programmatic-tests", "mockito-integration-tests:java-21-tests", + "mockito-integration-tests:graalvm-tests", ) // https://developer.android.com/studio/command-line/variables#envar From a10aed01a455bf1f45bb25dc1bb887fd171cffee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:52:29 +0200 Subject: [PATCH 04/11] Bump actions/setup-java from 4 to 5 (#3715) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b181b41e90..b6b249916f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci - name: 2. Set up Java for running Gradle build - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'zulu' java-version: 17 @@ -117,7 +117,7 @@ jobs: fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci - name: 2. Set up Java for running Gradle build - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'zulu' java-version: 17 @@ -245,7 +245,7 @@ jobs: fetch-depth: '0' # https://github.com/shipkit/shipkit-changelog#fetch-depth-on-ci - name: 2. Set up Java for running Gradle build - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: 'zulu' java-version: 21 From bc06f214c0c9505a1887e4422a449c6304993ff5 Mon Sep 17 00:00:00 2001 From: Adrian-Kim <110332047+BeomSeogKim@users.noreply.github.com> Date: Sat, 23 Aug 2025 03:53:37 +0900 Subject: [PATCH 05/11] Use Assume.assumeThat for SequencedCollection tests (#3711) Refactor the test for JDK 21's Sequenced Collections to use the standard `Assume.assumeThat` utility for conditional execution. Follow-up to #3708 Signed-off-by: BeomSeogKim --- .../ReturnsEmptyValuesTest.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/mockito-core/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValuesTest.java b/mockito-core/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValuesTest.java index a430cfee30..9b7363ff58 100644 --- a/mockito-core/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValuesTest.java +++ b/mockito-core/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValuesTest.java @@ -4,12 +4,13 @@ */ package org.mockito.internal.stubbing.defaultanswers; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; +import static org.junit.Assume.assumeThat; import static org.mockito.Mockito.mock; import java.util.*; -import org.junit.Assume; import org.junit.Test; import org.mockito.invocation.Invocation; import org.mockitoutil.TestBase; @@ -111,9 +112,11 @@ public void should_return_empty_OptionalLong() throws Exception { private void verify_empty_Optional_is_returned(String streamFqcn, String optionalFqcn) throws Exception { - Class streamType = getClassOrSkipTest(streamFqcn); - // given + assumeThat("JDK 8+ required for Optional", isJavaVersionAtLeast(8), is(true)); + + Class streamType = Class.forName(streamFqcn); + Object stream = mock(streamType); Object optional = streamType.getMethod("findAny").invoke(stream); assertNotNull(optional); @@ -150,7 +153,9 @@ public void should_return_empty_LongStream() throws Exception { private void verify_empty_Stream_is_returned(String streamFqcn) throws Exception { // given - Class streamType = getClassOrSkipTest(streamFqcn); + assumeThat("JDK 8+ required for Stream", isJavaVersionAtLeast(8), is(true)); + + Class streamType = Class.forName(streamFqcn); // when Object stream = values.returnValueFor(streamType); @@ -163,8 +168,9 @@ private void verify_empty_Stream_is_returned(String streamFqcn) throws Exception @Test public void should_return_empty_duration() throws Exception { // given + assumeThat("JDK 8+ required for Duration", isJavaVersionAtLeast(8), is(true)); final String fqcn = "java.time.Duration"; - final Class durationClass = getClassOrSkipTest(fqcn); + Class durationClass = Class.forName(fqcn); // when final Object duration = values.returnValueFor(durationClass); @@ -177,8 +183,11 @@ public void should_return_empty_duration() throws Exception { } @Test - public void should_return_empty_sequenced_collection_on_java21() throws Exception { - Class sequencedCollectionClass = getClassOrSkipTest("java.util.SequencedCollection"); + public void should_return_empty_sequenced_collection() throws Exception { + assumeThat("JDK 21+ required for SequencedCollection", isJavaVersionAtLeast(21), is(true)); + + Class sequencedCollectionClass = Class.forName("java.util.SequencedCollection"); + Object result = values.returnValueFor(sequencedCollectionClass); assertNotNull("SequencedCollection should return non-null value", result); assertTrue("Should return empty collection", ((Collection) result).isEmpty()); @@ -186,8 +195,11 @@ public void should_return_empty_sequenced_collection_on_java21() throws Exceptio } @Test - public void should_return_empty_sequenced_set_on_java21() throws Exception { - Class sequencedSetClass = getClassOrSkipTest("java.util.SequencedSet"); + public void should_return_empty_sequenced_set() throws Exception { + assumeThat("JDK 21+ required for SequencedSet", isJavaVersionAtLeast(21), is(true)); + + Class sequencedSetClass = Class.forName("java.util.SequencedSet"); + Object result = values.returnValueFor(sequencedSetClass); assertNotNull("SequencedSet should return non-null value", result); assertTrue("Should return empty set", ((Set) result).isEmpty()); @@ -195,8 +207,11 @@ public void should_return_empty_sequenced_set_on_java21() throws Exception { } @Test - public void should_return_empty_sequenced_map_on_java21() throws Exception { - Class sequencedMapClass = getClassOrSkipTest("java.util.SequencedMap"); + public void should_return_empty_sequenced_map() throws Exception { + assumeThat("JDK 21+ required for SequencedMap", isJavaVersionAtLeast(21), is(true)); + + Class sequencedMapClass = Class.forName("java.util.SequencedMap"); + Object result = values.returnValueFor(sequencedMapClass); assertNotNull("SequencedMap should return non-null value", result); assertTrue("Should return empty map", ((Map) result).isEmpty()); @@ -204,14 +219,12 @@ public void should_return_empty_sequenced_map_on_java21() throws Exception { } /** - * Tries to load the given class. If the class is not found, the complete test is skipped. + * Checks if the current Java version is at least the specified version. */ - private Class getClassOrSkipTest(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - Assume.assumeNoException("JVM does not support " + className, e); - return null; - } + private boolean isJavaVersionAtLeast(int majorVersion) { + String javaVersion = System.getProperty("java.version"); + String[] versionParts = javaVersion.split("\\."); + int currentMajorVersion = Integer.parseInt(versionParts[0]); + return currentMajorVersion >= majorVersion; } } From 54474fa1dd9455913181567536ca1d60f00880f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:27:39 +0200 Subject: [PATCH 06/11] Bump graalvm/setup-graalvm from 1.3.5 to 1.3.6 (#3719) Bumps [graalvm/setup-graalvm](https://github.com/graalvm/setup-graalvm) from 1.3.5 to 1.3.6. - [Release notes](https://github.com/graalvm/setup-graalvm/releases) - [Commits](https://github.com/graalvm/setup-graalvm/compare/v1.3.5...v1.3.6) --- updated-dependencies: - dependency-name: graalvm/setup-graalvm dependency-version: 1.3.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6b249916f..c9656e4804 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,7 +202,7 @@ jobs: fetch-depth: '0' - name: 2. Setup GraalVM - uses: graalvm/setup-graalvm@v1.3.5 + uses: graalvm/setup-graalvm@v1.3.6 with: java-version: "21" distribution: "graalvm" From c75dfb886cbfbed9c0d5d36681a103205a264a8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:30:57 +0200 Subject: [PATCH 07/11] Bump org.eclipse.platform:org.eclipse.osgi from 3.23.100 to 3.23.200 (#3720) Bumps [org.eclipse.platform:org.eclipse.osgi](https://github.com/eclipse-equinox/equinox) from 3.23.100 to 3.23.200. - [Commits](https://github.com/eclipse-equinox/equinox/commits) --- updated-dependencies: - dependency-name: org.eclipse.platform:org.eclipse.osgi dependency-version: 3.23.200 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1235fd2ac7..79fb935c10 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ bnd-gradle = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version.ref = "gra bytebuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "bytebuddy" } bytebuddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "bytebuddy" } bytebuddy-android = { module = "net.bytebuddy:byte-buddy-android", version.ref = "bytebuddy" } -equinox = { module = "org.eclipse.platform:org.eclipse.osgi", version = "3.23.100" } +equinox = { module = "org.eclipse.platform:org.eclipse.osgi", version = "3.23.200" } errorprone = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone" } errorprone-test-api = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorprone" } groovy = { module = "org.codehaus.groovy:groovy", version = "3.0.25" } From 6f9a04bbd7c7894a38b34658456691823866112c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:44:20 +0200 Subject: [PATCH 08/11] Bump com.gradle.develocity from 4.1.1 to 4.2 (#3726) Bumps com.gradle.develocity from 4.1.1 to 4.2. --- updated-dependencies: - dependency-name: com.gradle.develocity dependency-version: '4.2' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2033ecc64a..67b107d2f7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,7 +13,7 @@ dependencyResolutionManagement { } plugins { - id("com.gradle.develocity") version "4.1.1" + id("com.gradle.develocity") version "4.2" id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } From 3cfbd427182ef7c9ae718873ffb85b5ed4f04758 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:44:27 +0200 Subject: [PATCH 09/11] Bump graalvm/setup-graalvm from 1.3.6 to 1.3.7 (#3725) Bumps [graalvm/setup-graalvm](https://github.com/graalvm/setup-graalvm) from 1.3.6 to 1.3.7. - [Release notes](https://github.com/graalvm/setup-graalvm/releases) - [Commits](https://github.com/graalvm/setup-graalvm/compare/v1.3.6...v1.3.7) --- updated-dependencies: - dependency-name: graalvm/setup-graalvm dependency-version: 1.3.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c9656e4804..0b4f345c0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,7 +202,7 @@ jobs: fetch-depth: '0' - name: 2. Setup GraalVM - uses: graalvm/setup-graalvm@v1.3.6 + uses: graalvm/setup-graalvm@v1.3.7 with: java-version: "21" distribution: "graalvm" From f3c957a74e39a78c31b7fd2e48bf9f4c3a13112c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 10:31:40 +0200 Subject: [PATCH 10/11] Bump org.assertj:assertj-core from 3.27.4 to 3.27.5 (#3730) Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.4 to 3.27.5. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.4...assertj-build-3.27.5) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79fb935c10..2496ebf43c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ gradleplugin-android = "7.4.2" [libraries] android-junit = { module = "androidx.test.ext:junit", version = "1.2.1" } android-runner = { module = "androidx.test:runner", version = "1.7.0" } -assertj = { module = "org.assertj:assertj-core", version = "3.27.4" } +assertj = { module = "org.assertj:assertj-core", version = "3.27.5" } autoservice = { module = "com.google.auto.service:auto-service", version = "1.1.1" } bnd-gradle = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version.ref = "gradleplugin-aQute-bnd" } bytebuddy = { module = "net.bytebuddy:byte-buddy", version.ref = "bytebuddy" } From 3a1a19ee40f1234048880393343405046fc3fa60 Mon Sep 17 00:00:00 2001 From: Giulio Longfils Date: Sat, 20 Sep 2025 10:52:49 +0200 Subject: [PATCH 11/11] Add support for generic types in `MockedConstruction` and `MockedStatic` (#3729) MockedStatic and MockedConstruction threw a MockitoException when the provided type was itself parameterized, suggesting to use a raw type instead. For instance, MockedConstruction> was not allowed. This commit allows to mock generic types without throwing an exception and without forcing users to deal with 'raw use of parameterized class' warnings. Fixes #2401 --- .../MockAnnotationProcessor.java | 19 ++- .../MockAnnotationProcessorTest.java | 155 +++++++++++++----- 2 files changed, 131 insertions(+), 43 deletions(-) diff --git a/mockito-core/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java b/mockito-core/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java index f61f4a7bd9..92720b64fa 100644 --- a/mockito-core/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java +++ b/mockito-core/src/main/java/org/mockito/internal/configuration/MockAnnotationProcessor.java @@ -84,11 +84,20 @@ static Class inferParameterizedType(Type type, String name, String sort) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Type[] arguments = parameterizedType.getActualTypeArguments(); - if (arguments.length == 1) { - if (arguments[0] instanceof Class) { - return (Class) arguments[0]; - } + if (arguments.length != 1) { + throw new IllegalArgumentException( + "Incorrect number of type arguments for " + + name + + " of type " + + sort + + ": expected 1 but received " + + arguments.length); } + + return (Class) + (arguments[0] instanceof Class + ? arguments[0] + : ((ParameterizedType) arguments[0]).getRawType()); } throw new MockitoException( join( @@ -99,6 +108,6 @@ static Class inferParameterizedType(Type type, String name, String sort) { "", "@Mock " + sort + "", "", - "as the type parameter. If the type is itself parameterized, it should be specified as raw type.")); + "as the type parameter.")); } } diff --git a/mockito-core/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java b/mockito-core/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java index b9e9dd0bb2..7da4657b39 100644 --- a/mockito-core/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java +++ b/mockito-core/src/test/java/org/mockito/internal/configuration/MockAnnotationProcessorTest.java @@ -6,13 +6,26 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.MockedConstruction; import org.mockito.MockedStatic; import org.mockito.exceptions.base.MockitoException; +@RunWith(Enclosed.class) public class MockAnnotationProcessorTest { @SuppressWarnings("unused") @@ -21,49 +34,115 @@ public class MockAnnotationProcessorTest { @SuppressWarnings("unused") private MockedStatic> generic; - @SuppressWarnings({"raw", "unused"}) + @SuppressWarnings({"rawtypes", "unused"}) private MockedStatic raw; - @Test - public void testNonGeneric() throws Exception { - Class type = - MockAnnotationProcessor.inferParameterizedType( - MockAnnotationProcessorTest.class - .getDeclaredField("nonGeneric") - .getGenericType(), - "nonGeneric", - "Sample"); - assertThat(type).isEqualTo(Void.class); + @SuppressWarnings("unused") + private MockedConstruction nonGenericConstruction; + + @SuppressWarnings("unused") + private MockedConstruction> genericConstruction; + + @SuppressWarnings({"rawtypes", "unused"}) + private MockedConstruction rawConstruction; + + @RunWith(Parameterized.class) + public static class NonGenericTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] {{"nonGeneric"}, {"nonGenericConstruction"}}); + } + + @Parameter public String fieldName; + + @Test + public void ensure_non_generic_fields_can_be_inferred() throws Exception { + Class type = + MockAnnotationProcessor.inferParameterizedType( + MockAnnotationProcessorTest.class + .getDeclaredField(fieldName) + .getGenericType(), + fieldName, + "Sample"); + assertThat(type).isEqualTo(Void.class); + } + } + + @RunWith(Parameterized.class) + public static class GenericTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] {{"generic"}, {"genericConstruction"}}); + } + + @Parameter public String fieldName; + + @Test + public void ensure_generic_fields_can_be_inferred() throws Exception { + Class type = + MockAnnotationProcessor.inferParameterizedType( + MockAnnotationProcessorTest.class + .getDeclaredField(fieldName) + .getGenericType(), + fieldName, + "Sample"); + assertThat(type).isEqualTo(List.class); + } } - @Test - public void testGeneric() { - assertThatThrownBy( - () -> { - MockAnnotationProcessor.inferParameterizedType( - MockAnnotationProcessorTest.class - .getDeclaredField("generic") - .getGenericType(), - "generic", - "Sample"); - }) - .isInstanceOf(MockitoException.class) - .hasMessageContaining( - "Mockito cannot infer a static mock from a raw type for generic"); + @RunWith(Parameterized.class) + public static class RawTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] {{"raw"}, {"rawConstruction"}}); + } + + @Parameter public String fieldName; + + @Test + public void ensure_raw_fields_cannot_be_inferred() { + assertThatThrownBy( + () -> + MockAnnotationProcessor.inferParameterizedType( + MockAnnotationProcessorTest.class + .getDeclaredField(fieldName) + .getGenericType(), + fieldName, + "Sample")) + .isInstanceOf(MockitoException.class) + .hasMessageContaining( + "Mockito cannot infer a static mock from a raw type for " + fieldName); + } } - @Test - public void testRaw() { - assertThatThrownBy( - () -> { - MockAnnotationProcessor.inferParameterizedType( - MockAnnotationProcessorTest.class - .getDeclaredField("raw") - .getGenericType(), - "raw", - "Sample"); - }) - .isInstanceOf(MockitoException.class) - .hasMessageContaining("Mockito cannot infer a static mock from a raw type for raw"); + @RunWith(Parameterized.class) + public static class WrongNumberOfArgsTest { + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] {{"raw"}, {"rawConstruction"}}); + } + + @Parameter public String fieldName; + + @Test + public void ensure_parameterized_types_with_more_than_one_arg_cannot_be_inferred() { + final ParameterizedType parameterizedType = mock(); + when(parameterizedType.getActualTypeArguments()) + .thenReturn(new Type[] {String.class, String.class}); + + assertThatThrownBy( + () -> + MockAnnotationProcessor.inferParameterizedType( + parameterizedType, fieldName, "Sample")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Incorrect number of type arguments for " + + fieldName + + " of type Sample: expected 1 but received 2"); + } } }