blob: db9f09efc2f2c12dae84a11a118e3f2ac9dd1f6a [file] [log] [blame]
// 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()
+ ")");
}
}