| // Copyright (c) 2021, 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.compilerapi; |
| |
| import static org.hamcrest.CoreMatchers.containsString; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.CollectorsUtils; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestRuntime; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.ProcessResult; |
| import com.android.tools.r8.transformers.ClassFileTransformer; |
| import com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.FileUtils; |
| import com.android.tools.r8.utils.IntBox; |
| import com.android.tools.r8.utils.ZipUtils; |
| import com.android.tools.r8.utils.ZipUtils.ZipBuilder; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import java.io.File; |
| import java.lang.reflect.Method; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.StandardCopyOption; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| |
| /** |
| * Abstract base to define a collection of API tests that should be run against a checked in jar. |
| */ |
| public abstract class BinaryCompatibilityTestCollection<T> { |
| |
| private static final String JUNIT_JAR = "junit-4.13-beta-2.jar"; |
| |
| private static final String HAMCREST = "hamcrest-core-1.3.jar"; |
| |
| /** Jar to run tests against. */ |
| public abstract Path getTargetJar(); |
| |
| /** Jar with tests. */ |
| public abstract Path getCheckedInTestJar(); |
| |
| /** List of classes that make up the checked in test suite. */ |
| public abstract List<Class<? extends T>> getCheckedInTestClasses(); |
| |
| /** List of classes that are yet to become part of the test suite. */ |
| public abstract List<Class<? extends T>> getPendingTestClasses(); |
| |
| /** Additional classes that should always be included together to run tests. */ |
| public abstract List<Class<?>> getAdditionalClassesForTests(); |
| |
| /** Additional classes that should always be included together to run the pending tests. */ |
| public abstract List<Class<?>> getPendingAdditionalClassesForTests(); |
| |
| /** Additional JVM args supplied to any external execution. */ |
| public abstract List<String> getVmArgs(); |
| |
| /** Temporary folder for generating jars and so on. */ |
| public abstract TemporaryFolder getTemp(); |
| |
| public String makeProperty(String key, String value) { |
| return "-D" + key + "=" + value; |
| } |
| |
| private void verifyConsistency() { |
| assertEquals( |
| ImmutableSet.of(), |
| Sets.intersection( |
| ImmutableSet.copyOf(getCheckedInTestClasses()), |
| ImmutableSet.copyOf(getPendingTestClasses()))); |
| } |
| |
| private boolean testIsCheckedInOrPending(Class<?> clazz) { |
| return getCheckedInTestClasses().contains(clazz) || getPendingTestClasses().contains(clazz); |
| } |
| |
| public void runJunitOnCheckedInJar() throws Exception { |
| runJunitOnTestClasses(getCheckedInTestJar(), getCheckedInTestClasses()); |
| } |
| |
| public void runJunitOnTestClass(Class<? extends T> test) throws Exception { |
| List<Class<? extends T>> testClasses = Collections.singletonList(test); |
| runJunitOnTestClasses( |
| generateJarForTestClasses(testClasses, getPendingAdditionalClassesForTests()), testClasses); |
| } |
| |
| private void runJunitOnTestClasses(Path testJar, Collection<Class<? extends T>> tests) |
| throws Exception { |
| verifyConsistency(); |
| IntBox numberOfTestMethods = new IntBox(0); |
| List<Path> classPaths = |
| ImmutableList.of(getJunitDependency(), getHamcrest(), getTargetJar(), testJar); |
| List<String> args = new ArrayList<>(); |
| args.add("org.junit.runner.JUnitCore"); |
| tests.forEach( |
| test -> { |
| assertTrue(testIsCheckedInOrPending(test)); |
| args.add(test.getTypeName()); |
| for (Method method : test.getDeclaredMethods()) { |
| if (method.isAnnotationPresent(Test.class)) { |
| numberOfTestMethods.increment(); |
| } |
| } |
| }); |
| ProcessResult processResult = |
| ToolHelper.runJava( |
| TestRuntime.getSystemRuntime(), |
| ImmutableList.<String>builder().add("-ea").addAll(getVmArgs()).build(), |
| classPaths, |
| args.toArray(new String[0])); |
| assertEquals(processResult.toString(), 0, processResult.exitCode); |
| assertThat(processResult.stdout, containsString("OK (" + numberOfTestMethods.get() + " test")); |
| } |
| |
| private static Path getJunitDependency() { |
| String junitPath = |
| Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)) |
| .filter(cp -> cp.endsWith(JUNIT_JAR)) |
| .collect(CollectorsUtils.toSingle()); |
| return Paths.get(junitPath); |
| } |
| |
| private static Path getHamcrest() { |
| String junitPath = |
| Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)) |
| .filter(cp -> cp.endsWith(HAMCREST)) |
| .collect(CollectorsUtils.toSingle()); |
| return Paths.get(junitPath); |
| } |
| |
| public Path generateJarForCheckedInTestClasses() throws Exception { |
| return generateJarForTestClasses(getCheckedInTestClasses(), Collections.emptyList()); |
| } |
| |
| private Path generateJarForTestClasses( |
| Collection<Class<? extends T>> classes, List<Class<?>> additionalPendingClassesForTest) |
| throws Exception { |
| Path jar = getTemp().newFolder().toPath().resolve("test.jar"); |
| ZipBuilder zipBuilder = ZipBuilder.builder(jar); |
| for (Class<? extends T> test : classes) { |
| zipBuilder.addFilesRelative( |
| ToolHelper.getClassPathForTests(), ToolHelper.getClassFilesForInnerClasses(test)); |
| zipBuilder.addBytes( |
| ZipUtils.zipEntryNameForClass(test), |
| ClassFileTransformer.create(test) |
| .removeInnerClasses( |
| InnerClassPredicate.onName( |
| DescriptorUtils.getBinaryNameFromJavaType(test.getTypeName()))) |
| .transform()); |
| } |
| zipBuilder.addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| getAdditionalClassesForTests().stream() |
| .map(ToolHelper::getClassFileForTestClass) |
| .collect(Collectors.toList())); |
| zipBuilder.addFilesRelative( |
| ToolHelper.getClassPathForTests(), |
| additionalPendingClassesForTest.stream() |
| .map(ToolHelper::getClassFileForTestClass) |
| .collect(Collectors.toList())); |
| return zipBuilder.build(); |
| } |
| |
| public void verifyCheckedInJarIsUpToDate() throws Exception { |
| TemporaryFolder temp = getTemp(); |
| Path checkedInContents = temp.newFolder().toPath(); |
| Path generatedContents = temp.newFolder().toPath(); |
| ZipUtils.unzip(getCheckedInTestJar(), checkedInContents); |
| ZipUtils.unzip(generateJarForCheckedInTestClasses(), generatedContents); |
| try (Stream<Path> existingPaths = Files.walk(checkedInContents); |
| Stream<Path> generatedPaths = Files.walk(generatedContents)) { |
| List<Path> existing = |
| existingPaths.filter(FileUtils::isClassFile).collect(Collectors.toList()); |
| List<Path> generated = |
| generatedPaths.filter(FileUtils::isClassFile).collect(Collectors.toList()); |
| for (Path classFile : generated) { |
| Path otherClassFile = checkedInContents.resolve(generatedContents.relativize(classFile)); |
| assertTrue("Could not find file: " + otherClassFile, Files.exists(otherClassFile)); |
| assertTrue( |
| "Non-equal files: " + otherClassFile, |
| TestBase.filesAreEqual(classFile, otherClassFile)); |
| } |
| assertEquals(existing.size(), generated.size()); |
| assertNotEquals(0, existing.size()); |
| } |
| } |
| |
| public void replaceJarForCheckedInTestClasses() throws Exception { |
| Path checkedInJar = getCheckedInTestJar(); |
| Path tarballDir = checkedInJar.getParent(); |
| Path parentDir = tarballDir.getParent(); |
| if (!Files.exists(Paths.get(tarballDir + ".tar.gz.sha1"))) { |
| throw new RuntimeException("Could not locate the SHA file for " + tarballDir); |
| } |
| Path generatedJar = generateJarForCheckedInTestClasses(); |
| Files.move(generatedJar, checkedInJar, StandardCopyOption.REPLACE_EXISTING); |
| System.out.println( |
| "Updated file in: " |
| + checkedInJar |
| + "\nRemember to upload to cloud storage:" |
| + "\n(cd " |
| + parentDir |
| + " && upload_to_google_storage.py -a --bucket r8-deps " |
| + tarballDir.getFileName() |
| + ")"); |
| } |
| } |