blob: b6b3cb1e522d09ed055a395d1c4f3c545176f77d [file] [log] [blame]
// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getSafeVarArgsFile;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK11_PATH_JAVA_BASE_EXT;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK8_JAVA_BASE_EXT;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8CF2CF_L8DEBUG;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public abstract class Jdk11StreamAbstractTests extends DesugaredLibraryTestBase {
final TestParameters parameters;
final LibraryDesugaringSpecification libraryDesugaringSpecification;
final CompilationSpecification compilationSpecification;
@Parameters(name = "{0}, spec: {1}, {2}")
public static List<Object[]> data() throws Exception {
List<LibraryDesugaringSpecification> specs;
if (ToolHelper.isWindows()) {
// The library configuration is not available on windows. Do not run anything.
specs = ImmutableList.of();
} else {
Jdk11TestLibraryDesugaringSpecification.setUp();
specs = ImmutableList.of(JDK8_JAVA_BASE_EXT, JDK11_PATH_JAVA_BASE_EXT);
}
return buildParameters(
// TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required
// and present only in ART runtimes.
getTestParameters()
.withDexRuntimesStartingFromIncluding(Version.V5_1_1)
.withAllApiLevels()
.withApiLevel(AndroidApiLevel.N)
.build(),
specs,
ImmutableList.of(D8_L8DEBUG, D8_L8SHRINK, D8CF2CF_L8DEBUG));
}
public Jdk11StreamAbstractTests(
TestParameters parameters,
LibraryDesugaringSpecification libraryDesugaringSpecification,
CompilationSpecification compilationSpecification) {
this.parameters = parameters;
this.libraryDesugaringSpecification = libraryDesugaringSpecification;
this.compilationSpecification = compilationSpecification;
}
private static Path JDK_11_STREAM_TEST_CLASSES_DIR;
private static final Path JDK_11_STREAM_TEST_FILES_DIR =
Paths.get("third_party/openjdk/jdk-11-test/java/util/stream/test");
private static Path[] JDK_11_STREAM_TEST_COMPILED_FILES;
private static Path[] getJdk11StreamTestFiles() throws Exception {
Path[] files = getAllFilesWithSuffixInDirectory(JDK_11_STREAM_TEST_FILES_DIR, JAVA_EXTENSION);
assert files.length > 0;
return files;
}
private static final String[] FAILING_RUNNABLE_TESTS = new String[] {
// Disabled, D8 generated code raises AbstractMethodError instead of NPE because of API
// unsupported in the desugared library.
// "org/openjdk/tests/java/util/stream/SpliteratorTest.java",
// Disabled because both the stream close issue and the Random issue (See below).
// "org/openjdk/tests/java/util/stream/LongPrimitiveOpsTests.java",
// Disabled because explicit cast done on a wrapped value.
// "org/openjdk/tests/java/util/SplittableRandomTest.java",
// Disabled due to a desugaring failure due to the extended library used for the test.
// "org/openjdk/tests/java/util/stream/IterateTest.java",
};
// Cannot succeed with JDK 8 desugared library because use J9 features.
// Stream close issue with try with resource desugaring mixed with partial library desugaring.
public static final String[] STREAM_CLOSE_TESTS =
new String[] {"org/openjdk/tests/java/util/stream/StreamCloseTest.java"};
// Cannot succeed with JDK 8 desugared library because use J9 features.
public static final String[] SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7 =
new String[] {
// Require the virtual method isDefault() in class java/lang/reflect/Method.
"org/openjdk/tests/java/util/stream/WhileOpTest.java",
"org/openjdk/tests/java/util/stream/WhileOpStatefulTest.java",
// Require a Random method not present before Android 7 and not desugared.
"org/openjdk/tests/java/util/stream/IntPrimitiveOpsTests.java"
};
// Disabled because time to run > 1 min for each test.
// Can be used for experimentation/testing purposes.
private static String[] LONG_RUNNING_TESTS =
new String[] {
"org/openjdk/tests/java/util/stream/InfiniteStreamWithLimitOpTest.java",
"org/openjdk/tests/java/util/stream/CountLargeTest.java",
"org/openjdk/tests/java/util/stream/RangeTest.java",
"org/openjdk/tests/java/util/stream/CollectorsTest.java",
"org/openjdk/tests/java/util/stream/FlatMapOpTest.java",
"org/openjdk/tests/java/util/stream/StreamSpliteratorTest.java",
"org/openjdk/tests/java/util/stream/StreamLinkTest.java",
"org/openjdk/tests/java/util/stream/StreamBuilderTest.java",
"org/openjdk/tests/java/util/stream/SliceOpTest.java",
"org/openjdk/tests/java/util/stream/ToArrayOpTest.java"
};
private static final String[] SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_ONLY =
new String[] {
// Assertion error
"org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java",
"org/openjdk/tests/java/util/stream/CountTest.java",
// J9 Random problem
"org/openjdk/tests/java/util/stream/DoublePrimitiveOpsTests.java",
};
private static String[] SUCCESSFUL_RUNNABLE_TESTS =
new String[] {
"org/openjdk/tests/java/util/stream/FindFirstOpTest.java",
"org/openjdk/tests/java/util/stream/MapOpTest.java",
"org/openjdk/tests/java/util/stream/DistinctOpTest.java",
"org/openjdk/tests/java/util/MapTest.java",
"org/openjdk/tests/java/util/FillableStringTest.java",
"org/openjdk/tests/java/util/stream/ForEachOpTest.java",
"org/openjdk/tests/java/util/stream/CollectionAndMapModifyStreamTest.java",
"org/openjdk/tests/java/util/stream/GroupByOpTest.java",
"org/openjdk/tests/java/util/stream/PrimitiveAverageOpTest.java",
"org/openjdk/tests/java/util/stream/TeeOpTest.java",
"org/openjdk/tests/java/util/stream/MinMaxTest.java",
"org/openjdk/tests/java/util/stream/ConcatTest.java",
"org/openjdk/tests/java/util/stream/StreamParSeqTest.java",
"org/openjdk/tests/java/util/stream/ReduceByOpTest.java",
"org/openjdk/tests/java/util/stream/ConcatOpTest.java",
"org/openjdk/tests/java/util/stream/IntReduceTest.java",
"org/openjdk/tests/java/util/stream/SortedOpTest.java",
"org/openjdk/tests/java/util/stream/MatchOpTest.java",
"org/openjdk/tests/java/util/stream/IntSliceOpTest.java",
"org/openjdk/tests/java/util/stream/SequentialOpTest.java",
"org/openjdk/tests/java/util/stream/PrimitiveSumTest.java",
"org/openjdk/tests/java/util/stream/ReduceTest.java",
"org/openjdk/tests/java/util/stream/IntUniqOpTest.java",
"org/openjdk/tests/java/util/stream/FindAnyOpTest.java"
};
private boolean streamCloseTestShouldSucceed() {
if (libraryDesugaringSpecification == JDK8_JAVA_BASE_EXT) {
return false;
}
// TODO(b/216047740): Investigate if this runs on Dalvik VMs.
// StreamCloseTest relies on suppressed exceptions which may not work on Dalvik VMs.
return parameters.getDexRuntimeVersion().isNewerThan(Version.V4_4_4);
}
Map<String, String> getSuccessfulTests() {
Map<String, String> runnableTests = getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS);
if (libraryDesugaringSpecification != JDK8_JAVA_BASE_EXT) {
runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_ONLY));
if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)) {
runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
}
}
if (streamCloseTestShouldSucceed()) {
runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
}
return runnableTests;
}
Map<String, String> getFailingTests() {
Map<String, String> runnableTests = getRunnableTests(FAILING_RUNNABLE_TESTS);
if (libraryDesugaringSpecification == JDK8_JAVA_BASE_EXT) {
runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_ONLY));
runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
} else if (!parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)) {
runnableTests.putAll(getRunnableTests(SUCCESSFUL_RUNNABLE_TESTS_ON_JDK11_AND_V7));
}
if (!streamCloseTestShouldSucceed()) {
runnableTests.putAll(getRunnableTests(STREAM_CLOSE_TESTS));
}
return runnableTests;
}
private static Map<String, String> getRunnableTests(String[] tests) {
IdentityHashMap<String, String> pathToName = new IdentityHashMap<>();
int javaExtSize = JAVA_EXTENSION.length();
for (String runnableTest : tests) {
String nameWithoutJavaExt = runnableTest.substring(0, runnableTest.length() - javaExtSize);
pathToName.put(
JDK_11_STREAM_TEST_CLASSES_DIR + "/" + nameWithoutJavaExt + CLASS_EXTENSION,
nameWithoutJavaExt.replace("/", "."));
}
return pathToName;
}
private static String[] missingDesugaredMethods() {
// These methods are from Java 9 and not supported in the current desugared libraries.
return new String[] {
// Stream
"takeWhile(",
"dropWhile(",
"iterate(",
"range(",
"doubles(",
// Collectors
"filtering(",
"flatMapping(",
// isDefault()Z in class Ljava/lang/reflect/Method
"isDefault("
};
}
@BeforeClass
public static void compileJdk11StreamTests() throws Exception {
JDK_11_STREAM_TEST_CLASSES_DIR = getStaticTemp().newFolder("stream").toPath();
List<String> options =
Arrays.asList(
"--add-reads",
"java.base=ALL-UNNAMED",
"--patch-module",
"java.base=" + EXTENSION_PATH);
javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
.addOptions(options)
.addClasspathFiles(
ImmutableList.of(EXTENSION_PATH, Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
.addSourceFiles(getJdk11StreamTestFiles())
.setOutputPath(JDK_11_STREAM_TEST_CLASSES_DIR)
.compile();
JDK_11_STREAM_TEST_COMPILED_FILES =
getAllFilesWithSuffixInDirectory(JDK_11_STREAM_TEST_CLASSES_DIR, CLASS_EXTENSION);
assert JDK_11_STREAM_TEST_COMPILED_FILES.length > 0;
}
Map<String, String> firstHalf(Map<String, String> input) {
return half(input, true);
}
Map<String, String> secondHalf(Map<String, String> input) {
return half(input, false);
}
private Map<String, String> half(Map<String, String> input, boolean first) {
ArrayList<String> keys = new ArrayList<>(input.keySet());
keys.sort(Comparator.naturalOrder());
int length = keys.size();
int middle = length / 2 + length % 2;
List<String> half = first ? keys.subList(0, middle) : keys.subList(middle, length);
HashMap<String, String> newMap = new HashMap<>();
for (String key : half) {
newMap.put(key, input.get(key));
}
return newMap;
}
public void testStream(Map<String, String> successes, Map<String, String> failures)
throws Throwable {
Assume.assumeFalse(
"getAllFilesWithSuffixInDirectory() seems to find different files on Windows",
ToolHelper.isWindows());
Assume.assumeTrue(
"Requires Java base extensions, should add it when not desugaring",
parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel());
DesugaredLibraryTestCompileResult<?> compileResult = compileStreamTestsToDex();
runSuccessfulTests(compileResult, successes);
runFailingTests(compileResult, failures);
}
DesugaredLibraryTestCompileResult<?> compileStreamTestsToDex() throws Exception {
List<Path> filesToCompile =
Arrays.stream(JDK_11_STREAM_TEST_COMPILED_FILES)
.filter(file -> !file.toString().contains("lang/invoke"))
.collect(Collectors.toList());
return testForDesugaredLibrary(
parameters, libraryDesugaringSpecification, compilationSpecification)
.addProgramFiles(filesToCompile)
.applyIf(
libraryDesugaringSpecification != JDK11_PATH_JAVA_BASE_EXT,
b -> b.addProgramFiles(getPathsFiles()))
.addProgramFiles(getSafeVarArgsFile())
.addProgramFiles(testNGSupportProgramFiles())
.addOptionsModification(opt -> opt.testing.trackDesugaredAPIConversions = true)
.disableL8AnnotationRemoval()
.compile()
.withArt6Plus64BitsLib();
}
private void runSuccessfulTests(
DesugaredLibraryTestCompileResult<?> compileResult, Map<String, String> successes)
throws Exception {
String verbosity = "2"; // Increase verbosity for debugging.DesugaredLibraryTestBuilder
for (String path : successes.keySet()) {
assert successes.get(path) != null;
SingleTestRunResult<?> result =
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, successes.get(path));
assertTrue(
"Failure in " + path + "\n" + result,
result
.getStdOut()
.endsWith(StringUtils.lines("Tests result in " + successes.get(path) + ": SUCCESS")));
}
}
private void runFailingTests(
DesugaredLibraryTestCompileResult<?> compileResult, Map<String, String> failures)
throws Exception {
// For failing runnable tests, we just ensure that they do not fail due to desugaring, but
// due to an expected failure (missing API, etc.).
String verbosity = "2"; // Increase verbosity for debugging.
for (String path : failures.keySet()) {
assert failures.get(path) != null;
SingleTestRunResult<?> result =
compileResult.run(
parameters.getRuntime(), "TestNGMainRunner", verbosity, failures.get(path));
String stdout = result.getStdOut();
if (stdout.contains("java.lang.NoSuchMethodError")
&& Arrays.stream(missingDesugaredMethods()).anyMatch(stdout::contains)) {
// TODO(b/134732760): support Java 9 APIs.
} else if (stdout.contains("in class Ljava/util/Random")
&& stdout.contains("java.lang.NoSuchMethodError")) {
// TODO(b/134732760): Random Java 9 Apis, support or do not use them.
} else if (stdout.contains("java.lang.AssertionError")) {
// TODO(b/134732760): Investigate and fix these issues.
} else {
String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
fail(errorMessage);
}
}
}
}