From d8cb1efbef12232d872beb7133dd327ef6cf2d7d Mon Sep 17 00:00:00 2001 From: ascopes <73482956+ascopes@users.noreply.github.com> Date: Tue, 24 Oct 2023 08:59:54 +0100 Subject: [PATCH 1/2] Handle Termux in InlineDelegateByteBuddyMockMaker.java Termux on Android can run any JVM, so we cannot rely on the vendor here to determine if we are in Termux. Nonetheless, ByteBuddy can fail to instrument correctly on this platform, so it is worth showing a meaninful error message in this scenario. --- .../bytebuddy/InlineDelegateByteBuddyMockMaker.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java index d40967f94e..8bb4fdb58d 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java @@ -207,9 +207,17 @@ class InlineDelegateByteBuddyMockMaker InlineDelegateByteBuddyMockMaker() { if (INITIALIZATION_ERROR != null) { String detail; - if (System.getProperty("java.specification.vendor", "") + + boolean isNativeAndroid = System.getProperty("java.specification.vendor", "") .toLowerCase() - .contains("android")) { + .contains("android"); + + // Termux can run any JVM, such as Oracle, but may still fail to instrument the + // JVM correctly, so it is worth detecting this as well. + boolean isTermuxAndroid = System.getProperty("java.home", "") + .contains("/com.termux/"); + + if (isNativeAndroid || isTermuxAndroid) { detail = "It appears as if you are trying to run this mock maker on Android which does not support the instrumentation API."; } else { From 41df2d3624e30a709cdec3e10cca6fe78c874a58 Mon Sep 17 00:00:00 2001 From: ascopes <73482956+ascopes@users.noreply.github.com> Date: Tue, 24 Oct 2023 08:50:49 +0000 Subject: [PATCH 2/2] Enable testing platform conditions - Implement unit tests for the ByteBuddy platform conditions - Extract the ByteBuddy platform conditions to a separate utility class to enable unit testing them separately. This also enables adding additional checks in the future without making the InlineDelegateByteBuddyMockMaker much more complicated. - Added dependency at test time for junit-jupiter-params to enable writing parameterized unit tests. --- build.gradle | 2 + gradle/dependencies.gradle | 1 + .../InlineDelegateByteBuddyMockMaker.java | 12 +-- .../creation/bytebuddy/PlatformUtils.java | 29 ++++++ .../creation/bytebuddy/PlatformUtilsTest.java | 90 +++++++++++++++++++ 5 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/mockito/internal/creation/bytebuddy/PlatformUtils.java create mode 100644 src/test/java/org/mockito/internal/creation/bytebuddy/PlatformUtilsTest.java diff --git a/build.gradle b/build.gradle index a5c9fb25e0..c755b3bd4f 100644 --- a/build.gradle +++ b/build.gradle @@ -96,6 +96,8 @@ dependencies { implementation libraries.objenesis testImplementation libraries.assertj + testImplementation libraries.junitJupiterApi + testImplementation libraries.junitJupiterParams testUtil sourceSets.test.output diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 53e1dcdc5b..30354d1f42 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -10,6 +10,7 @@ versions.errorprone = '2.23.0' libraries.junit4 = 'junit:junit:4.13.2' libraries.junitJupiterApi = "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}" +libraries.junitJupiterParams = "org.junit.jupiter:junit-jupiter-params:${versions.junitJupiter}" libraries.junitPlatformLauncher = 'org.junit.platform:junit-platform-launcher:1.10.0' libraries.junitJupiterEngine = "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}" libraries.junitVintageEngine = "org.junit.vintage:junit-vintage-engine:${versions.junitJupiter}" diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java index 8bb4fdb58d..02f39a35a5 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java @@ -207,17 +207,7 @@ class InlineDelegateByteBuddyMockMaker InlineDelegateByteBuddyMockMaker() { if (INITIALIZATION_ERROR != null) { String detail; - - boolean isNativeAndroid = System.getProperty("java.specification.vendor", "") - .toLowerCase() - .contains("android"); - - // Termux can run any JVM, such as Oracle, but may still fail to instrument the - // JVM correctly, so it is worth detecting this as well. - boolean isTermuxAndroid = System.getProperty("java.home", "") - .contains("/com.termux/"); - - if (isNativeAndroid || isTermuxAndroid) { + if (PlatformUtils.isAndroidPlatform() || PlatformUtils.isProbablyTermuxEnvironment()) { detail = "It appears as if you are trying to run this mock maker on Android which does not support the instrumentation API."; } else { diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/PlatformUtils.java b/src/main/java/org/mockito/internal/creation/bytebuddy/PlatformUtils.java new file mode 100644 index 0000000000..0fa235d3d7 --- /dev/null +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/PlatformUtils.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.creation.bytebuddy; + +/** + * Helpers for various platform detection mechanisms related to the ByteBuddy mock maker. + * + * @author Ashley Scopes + */ +final class PlatformUtils { + private PlatformUtils() { + // Static-only class. + } + + static boolean isAndroidPlatform() { + return System.getProperty("java.specification.vendor", "") + .toLowerCase() + .contains("android"); + } + + static boolean isProbablyTermuxEnvironment() { + boolean isLinux = System.getProperty("os.name", "").equalsIgnoreCase("linux"); + boolean isInTermuxData = + System.getProperty("java.home", "").toLowerCase().contains("/com.termux/"); + return isLinux && isInTermuxData; + } +} diff --git a/src/test/java/org/mockito/internal/creation/bytebuddy/PlatformUtilsTest.java b/src/test/java/org/mockito/internal/creation/bytebuddy/PlatformUtilsTest.java new file mode 100644 index 0000000000..ba2263715d --- /dev/null +++ b/src/test/java/org/mockito/internal/creation/bytebuddy/PlatformUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.creation.bytebuddy; + +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ashley Scopes + */ +@Isolated("modifies system properties temporarily") +class PlatformUtilsTest { + Properties globalProperties; + + @BeforeEach + void setUp() { + globalProperties = new Properties(); + globalProperties.putAll(System.getProperties()); + System.getProperties().clear(); + } + + @AfterEach + void tearDown() { + System.getProperties().clear(); + System.getProperties().putAll(globalProperties); + } + + @CsvSource( + value = { + "java.specification.vendor, expected result", + " android, true", + " AnDrOId, true", + " anythingElse, false", + " , false", + }, + useHeadersInDisplayName = true) + @ParameterizedTest + void isAndroidPlatform_returns_expected_value(String vendor, boolean result) { + // Given + setProperty("java.specification.vendor", vendor); + + // Then + assertThat(PlatformUtils.isAndroidPlatform()).isEqualTo(result); + } + + @CsvSource( + value = { + " os.name, java.home, expected result", + " linux, /data/data/com.termux/whatever, true", + " linux, /foo/bar/com.termux/somedir, true", + " LINUX, /data/data/com.termux/whatever, true", + " Linux, /foo/bar/com.termux/somedir, true", + "Mac OS X 13.0, /foo/bar/com.termux/somedir, false", + " Windows 10, /foo/bar/com.termux/somedir, false", + " OS/2, /foo/bar/com.termux/somedir, false", + " Linux, /usr/share/java, false", + " , , false", + " foo, , false", + " , foo, false", + " linux, , false", + " , /data/data/com.termux/whatever, false", + }, + useHeadersInDisplayName = true) + @ParameterizedTest + void isProbablyTermuxEnvironment_returns_expected_value( + String os, String javaHome, boolean result) { + // Given + setProperty("os.name", os); + setProperty("java.home", javaHome); + + // Then + assertThat(PlatformUtils.isProbablyTermuxEnvironment()).isEqualTo(result); + } + + static void setProperty(String name, String value) { + if (value == null) { + System.clearProperty(name); + } else { + System.setProperty(name, value); + } + } +}