blob: c47353e3fd2d6c4fb84ca3b03ba142101a07e946 [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.apimodel;
import static com.android.tools.r8.apimodel.AndroidApiDatabaseBuilderGenerator.generatedMainDescriptor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.JvmTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class AndroidApiDatabaseBuilderGeneratorTest extends TestBase {
protected final TestParameters parameters;
private static final Path API_VERSIONS_XML =
Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "api-versions", "api-versions.xml");
private static final AndroidApiLevel API_LEVEL = AndroidApiLevel.R;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withSystemRuntime().build();
}
public AndroidApiDatabaseBuilderGeneratorTest(TestParameters parameters) {
this.parameters = parameters;
}
private static Path generateJar() throws Exception {
return generateJar(
AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL));
}
private static Path generateJar(List<ParsedApiClass> apiClasses) throws Exception {
TemporaryFolder temp = new TemporaryFolder();
temp.create();
ZipBuilder builder = ZipBuilder.builder(temp.newFile("out.jar").toPath());
AndroidApiDatabaseBuilderGenerator.generate(
apiClasses,
(descriptor, content) -> {
try {
String binaryName = DescriptorUtils.getBinaryNameFromDescriptor(descriptor) + ".class";
builder.addBytes(binaryName, content);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
});
return builder.build();
}
public static void main(String[] args) throws Exception {
generateJar();
}
@Test
public void testDatabaseGenerationUpToDate() {
assumeTrue("b/190368382", false);
}
@Test
public void testCanParseApiVersionsXml() throws Exception {
// This tests makes a rudimentary check on the number of classes, fields and methods in
// api-versions.xml to ensure that the runtime tests do not vacuously succeed.
List<ParsedApiClass> parsedApiClasses =
AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
IntBox numberOfFields = new IntBox(0);
IntBox numberOfMethods = new IntBox(0);
parsedApiClasses.forEach(
apiClass -> {
apiClass.visitFieldReferences(
((apiLevel, fieldReferences) -> {
fieldReferences.forEach(field -> numberOfFields.increment());
}));
apiClass.visitMethodReferences(
((apiLevel, methodReferences) -> {
methodReferences.forEach(field -> numberOfMethods.increment());
}));
});
// These numbers will change when updating api-versions.xml
assertEquals(4742, parsedApiClasses.size());
assertEquals(25144, numberOfFields.get());
assertEquals(38661, numberOfMethods.get());
}
@Test()
public void testGeneratedOutputForVisitClasses() throws Exception {
runTest(
TestGeneratedMainVisitClasses.class,
(parsedApiClasses, runResult) -> {
String expectedOutput =
StringUtils.lines(
ListUtils.map(
parsedApiClasses, apiClass -> apiClass.getClassReference().getDescriptor()));
runResult.assertSuccessWithOutput(expectedOutput);
});
}
@Test()
public void testBuildClassesContinue() throws Exception {
runTest(
TestBuildClassesContinue.class,
(parsedApiClasses, runResult) -> {
runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, false));
});
}
@Test()
public void testBuildClassesBreak() throws Exception {
runTest(
TestBuildClassesBreak.class,
(parsedApiClasses, runResult) -> {
runResult.assertSuccessWithOutputLines(getExpected(parsedApiClasses, true));
});
}
private void runTest(
Class<?> testClass, BiConsumer<List<ParsedApiClass>, JvmTestRunResult> resultConsumer)
throws Exception {
List<ParsedApiClass> parsedApiClasses =
AndroidApiVersionsXmlParser.getParsedApiClasses(API_VERSIONS_XML.toFile(), API_LEVEL);
testForJvm()
.addProgramClassFileData(
transformer(testClass)
.replaceClassDescriptorInMethodInstructions(
descriptor(AndroidApiDatabaseBuilderTemplate.class), generatedMainDescriptor())
.transform())
.addLibraryFiles(generateJar(parsedApiClasses))
// TODO(b/190368382): This will change when databasebuilder is included in deps.
.addLibraryFiles(ToolHelper.R8_WITHOUT_DEPS_JAR, ToolHelper.DEPS)
.addDefaultRuntimeLibrary(parameters)
.run(parameters.getRuntime(), testClass)
.apply(
result -> {
if (result.getExitCode() != 0) {
System.out.println(result.getStdErr());
}
})
.assertSuccess()
.apply(result -> resultConsumer.accept(parsedApiClasses, result));
}
private List<String> getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
List<String> expected = new ArrayList<>();
parsedApiClasses.forEach(
apiClass -> {
expected.add(apiClass.getClassReference().getDescriptor());
expected.add(apiClass.getApiLevel().getName());
BooleanBox added = new BooleanBox(false);
apiClass.visitFieldReferences(
(apiLevel, fieldReferences) -> {
fieldReferences.forEach(
fieldReference -> {
if (added.isTrue() && abort) {
return;
}
added.set();
expected.add(fieldReference.getFieldType().getDescriptor());
expected.add(fieldReference.getFieldName());
expected.add(apiLevel.getName());
});
});
added.set(false);
apiClass.visitMethodReferences(
(apiLevel, methodReferences) -> {
methodReferences.forEach(
methodReference -> {
if (added.isTrue() && abort) {
return;
}
added.set();
expected.add(methodReference.getMethodDescriptor());
expected.add(methodReference.getMethodName());
expected.add(apiLevel.getName());
});
});
});
return expected;
}
public static class TestGeneratedMainVisitClasses {
public static void main(String[] args) {
AndroidApiDatabaseBuilderTemplate.visitApiClasses(System.out::println);
}
}
public static class TestBuildClassesContinue {
public static void main(String[] args) {
AndroidApiDatabaseBuilderTemplate.visitApiClasses(
descriptor -> {
com.android.tools.r8.androidapi.AndroidApiClass apiClass =
AndroidApiDatabaseBuilderTemplate.buildClass(
Reference.classFromDescriptor(descriptor));
if (apiClass != null) {
System.out.println(descriptor);
System.out.println(apiClass.getApiLevel().getName());
apiClass.visitFields(
(reference, apiLevel) -> {
System.out.println(reference.getFieldType().getDescriptor());
System.out.println(reference.getFieldName());
System.out.println(apiLevel.getName());
return TraversalContinuation.CONTINUE;
});
apiClass.visitMethods(
(reference, apiLevel) -> {
System.out.println(reference.getMethodDescriptor());
System.out.println(reference.getMethodName());
System.out.println(apiLevel.getName());
return TraversalContinuation.CONTINUE;
});
}
});
}
}
public static class TestBuildClassesBreak {
public static void main(String[] args) {
AndroidApiDatabaseBuilderTemplate.visitApiClasses(
descriptor -> {
com.android.tools.r8.androidapi.AndroidApiClass apiClass =
AndroidApiDatabaseBuilderTemplate.buildClass(
Reference.classFromDescriptor(descriptor));
if (apiClass != null) {
System.out.println(descriptor);
System.out.println(apiClass.getApiLevel().getName());
apiClass.visitFields(
(reference, apiLevel) -> {
System.out.println(reference.getFieldType().getDescriptor());
System.out.println(reference.getFieldName());
System.out.println(apiLevel.getName());
return TraversalContinuation.BREAK;
});
apiClass.visitMethods(
(reference, apiLevel) -> {
System.out.println(reference.getMethodDescriptor());
System.out.println(reference.getMethodName());
System.out.println(apiLevel.getName());
return TraversalContinuation.BREAK;
});
}
});
}
}
}