JUnit 5.4.0 started crashing on Android due to unsupported UNICODE_CHARACTER_CLASS flag in Pattern.
Android 9.0 (Google Play) x86_64 image./gradlew :sample:connectedExperimentalDebugAndroidTest from the root of checked out repojava.lang.ExceptionInInitializerError
at org.junit.platform.commons.util.StringUtils.isNotBlank(StringUtils.java:64)
at org.junit.platform.commons.util.Preconditions.notBlank(Preconditions.java:248)
at org.junit.platform.launcher.core.LauncherConfigurationParameters.<init>(LauncherConfigurationParameters.java:44)
at org.junit.platform.launcher.core.LauncherConfigurationParameters.<init>(LauncherConfigurationParameters.java:39)
at org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.build(LauncherDiscoveryRequestBuilder.java:194)
at org.junit.platform.runner.JUnitPlatform.createDiscoveryRequest(JUnitPlatform.java:153)
at org.junit.platform.runner.JUnitPlatform.<init>(JUnitPlatform.java:124)
at org.junit.platform.runner.JUnitPlatform.<init>(JUnitPlatform.java:117)
at de.mannodermaus.junit5.AndroidJUnit5.<init>(Runner.kt:17)
at de.mannodermaus.junit5.RunnerKt.createJUnit5Runner(Runner.kt:22)
at de.mannodermaus.junit5.AndroidJUnit5Builder.runnerForClass(RunnerBuilder.kt:69)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at androidx.test.internal.runner.AndroidRunnerBuilder.runnerForClass(AndroidRunnerBuilder.java:147)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at androidx.test.internal.runner.TestLoader.doCreateRunner(TestLoader.java:73)
at androidx.test.internal.runner.TestLoader.getRunnersFor(TestLoader.java:104)
at androidx.test.internal.runner.TestRequestBuilder.build(TestRequestBuilder.java:789)
at androidx.test.runner.AndroidJUnitRunner.buildRequest(AndroidJUnitRunner.java:543)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:386)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
Caused by: java.lang.IllegalArgumentException: Unsupported flags: 256
at java.util.regex.Pattern.<init>(Pattern.java:1324)
at java.util.regex.Pattern.compile(Pattern.java:975)
at org.junit.platform.commons.util.StringUtils.<clinit>(StringUtils.java:36)
... 20 more
It looks like UNICODE_CHARACTER_CLASS was added to StringUtils in JUnit 5.4.0. This flag is not supported on Android (not even on the latest devices) and causes crash whenever StringUtils class is initialized.
I remember that late change in line https://github.com/junit-team/junit5/commit/db8b3d4cdd588fad4660fbedaf1c6b2f68daed77#diff-6620c2fff8288084514a095acc4cb5a8R36 ... used another in-line regular expression before.
The constant is available since Java 1.7, though. Copied from java.util.regex.Pattern#UNICODE_CHARACTER_CLASS:
/**
* Enables the Unicode version of <i>Predefined character classes</i> and
* <i>POSIX character classes</i>.
*
* <p> When this flag is specified then the (US-ASCII only)
* <i>Predefined character classes</i> and <i>POSIX character classes</i>
* are in conformance with
* <a href="http://www.unicode.org/reports/tr18/"><i>Unicode Technical
* Standard #18: Unicode Regular Expression</i></a>
* <i>Annex C: Compatibility Properties</i>.
* <p>
* The UNICODE_CHARACTER_CLASS mode can also be enabled via the embedded
* flag expression {@code (?U)}.
* <p>
* The flag implies UNICODE_CASE, that is, it enables Unicode-aware case
* folding.
* <p>
* Specifying this flag may impose a performance penalty. </p>
* @since 1.7
*/
public static final int UNICODE_CHARACTER_CLASS = 0x100;
Reading the documention again...
* The UNICODE_CHARACTER_CLASS mode can also be enabled via the embedded
* flag expression {@code (?U)}.
...(?U) is the pattern snippet, aka flag expression.
@matejdro Does Android support (?U)?
The constant is available since Java 1.7, though
Yes, the constant itself is available. Unfortunately it appears Android folks forgot to update validation code, so this constant is still not allowed to be passed into Pattern constructor.
Does Android support
(?U)?
Just checked and Android 9.0 (latest one at the moment) throws syntax error when I try to create Pattern with this text added in. On the bright side, this mode appears to be always enabled on Android according to Android Documentation.
Thanks for checking @matejdro -- mh, maybe we have to resort to only look for non-Unicode control chars on Android. Perhaps we'll enhance the OS enumeration (is it possible to determine Android OS at runtime?) with an ANDROID constant.
Marked for @junit-team discussion.
...or guard the compilation of the patterns in StringUtils https://github.com/junit-team/junit5/blob/master/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java#L36-L37 in a static initializer and fall-back to a non-Unicode one.
@matejdro @mannodermaus With https://github.com/junit-team/junit5/commit/30ab9f4fa8dcf05b44343faa6836c46e0c2d88be applied Android support should be fixed.
Can you please double-check on your systems using latest SNAPSHOT of JUnit 5?
https://oss.sonatype.org/content/repositories/snapshots/org/junit/
I've cherry-picked 30ab9f4 to the releases/5.4.x branch.
Perhaps we'll enhance the
OSenumeration (is it possible to determine Android OS at runtime?) with anANDROIDconstant.
The way this is usually checked by multi-platform libraries is through a reflective class lookup for android.os.Build, followed by a conditional around the android.os.Build.Version.SDK_INT constant. Here is an example of that!
I've just checked and 5.5.0-SNAPSHOT works on Android!
Thanks a lot.
Thanks for the heads-up, @mannodermaus and @matejdro!
Regarding OS.ANDROID ... how is the relation to OS.LINUX? Does OS.currentOs() report LINUX on Android at the momemt? If we were to introduce OS.ANDROID would that also imply OS.LINUX? Perhaps we a secondary attribute/enum, like OS.Flavor... or something.
The fix for Android will be released soon with version 5.4.1.
Indeed, OS.LINUX.isCurrentOs() is true when running on an Android device:
@Test
fun testOsCurrent() {
val currentOs = OS.values().first { it.isCurrentOs }
Log.i("AndroidLog", "Current OS: $currentOs")
Log.i("AndroidLog", "os.name: ${System.getProperty("os.name")}")
Log.i("AndroidLog", "java.vm.name: ${System.getProperty("java.vm.name")}")
}
Output:
I/AndroidLog: Current OS: LINUX
I/AndroidLog: os.name: Linux
I/AndroidLog: java.vm.name: Dalvik
If we wanted to stay with system property lookups, java.vm.name might be another good candidate to detect Android, I suppose. Then again, this would warrant its own ticket and discussion!
If we wanted to stay with system property lookups,
java.vm.namemight be another good candidate to detect Android, I suppose. Then again, this would warrant its own ticket and discussion!
Agreed. Please open a new ticket if you want to follow up on Android OS detection.