blob: 74a6c9a1d943dbf2fc8340d3ee8d0f0fa7dea9b6 [file] [log] [blame]
// Copyright 2020 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.android.r8.desugar;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.android.r8.R8Utils.INTERFACE_COMPANION_SUFFIX;
import static org.objectweb.asm.Opcodes.V1_7;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.android.desugar.proto.DesugarDeps;
import com.google.devtools.build.android.desugar.proto.DesugarDeps.Dependency;
import com.google.devtools.build.android.desugar.proto.DesugarDeps.DesugarDepsInfo;
import com.google.devtools.build.android.desugar.proto.DesugarDeps.InterfaceDetails;
import com.google.devtools.build.android.desugar.proto.DesugarDeps.InterfaceWithCompanion;
import com.google.devtools.build.android.r8.DescriptorUtils;
import com.google.devtools.build.android.r8.Desugar;
import com.google.devtools.build.android.r8.FileUtils;
import com.google.devtools.build.android.r8.desugar.basic.A;
import com.google.devtools.build.android.r8.desugar.basic.B;
import com.google.devtools.build.android.r8.desugar.basic.C;
import com.google.devtools.build.android.r8.desugar.basic.I;
import com.google.devtools.build.android.r8.desugar.basic.J;
import com.google.devtools.build.android.r8.desugar.basic.K;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
/** Basic test of D8 desugar */
@RunWith(JUnit4.class)
public class DesugarBasicTest {
private Path basic;
private Path desugared;
private Path desugaredClasspath;
private Path desugaredWithDependencyMetadata;
private Path doubleDesugaredWithDependencyMetadata;
private Path desugaredClasspathWithDependencyMetadata;
private Path desugaredWithDependencyMetadataMissingInterface;
@Before
public void setup() {
// Jar file with the compiled Java code in the sub-package basic before desugaring.
basic = Paths.get(System.getProperty("DesugarBasicTest.testdata_basic"));
// Jar file with the compiled Java code in the sub-package basic after desugaring.
desugared = Paths.get(System.getProperty("DesugarBasicTest.testdata_basic_desugared"));
// Jar file with the compiled Java code in the sub-package basic after desugaring with all
// interfaces on classpath.
desugaredClasspath =
Paths.get(System.getProperty("DesugarBasicTest.testdata_basic_desugared_classpath"));
// Jar file with the compiled Java code in the sub-package basic after desugaring with
// collected dependency metadata included.
desugaredWithDependencyMetadata =
Paths.get(
System.getProperty(
"DesugarBasicTest.testdata_basic_desugared_with_dependency_metadata"));
// Same as testdata_basic_desugared_with_dependency_metadata, but where the input is instead the
// already desugared code (i.e., testdata_basic_desugared)
doubleDesugaredWithDependencyMetadata =
Paths.get(
System.getProperty(
"DesugarBasicTest.testdata_basic_double_desugared_with_dependency_metadata"));
// Jar file with the compiled Java code in the sub-package basic after desugaring with all
// interfaces on classpath with collected dependency metadata included.
desugaredClasspathWithDependencyMetadata =
Paths.get(
System.getProperty(
"DesugarBasicTest.testdata_basic_desugared_classpath_with_dependency_metadata"));
// Jar file with the compiled Java code in the sub-package basic after desugaring with missing
// interface.
desugaredWithDependencyMetadataMissingInterface =
Paths.get(
System.getProperty(
"DesugarBasicTest.testdata_basic_desugared_with_dependency_metadata_missing_interface"));
}
@Test
public void checkBeforeDesugar() throws Exception {
DesugarInfoCollector desugarInfoCollector = new DesugarInfoCollector();
forAllClasses(basic, desugarInfoCollector);
assertThat(desugarInfoCollector.getLargestMajorClassFileVersion()).isGreaterThan(V1_7);
assertThat(desugarInfoCollector.getNumberOfInvokeDynamic()).isGreaterThan(0);
assertThat(desugarInfoCollector.getNumberOfDefaultMethods()).isGreaterThan(0);
assertThat(desugarInfoCollector.getNumberOfDesugaredLambdas()).isEqualTo(0);
assertThat(desugarInfoCollector.getNumberOfCompanionClasses()).isEqualTo(0);
}
@Test
public void checkAfterDesugar() throws Exception {
for (Path jar : ImmutableList.of(desugared, desugaredWithDependencyMetadata)) {
DesugarInfoCollector desugarInfoCollector = new DesugarInfoCollector();
forAllClasses(jar, desugarInfoCollector);
// TODO(b/153971249): The class file version of desugared class files should be Java 7.
// assertThat(lambdaUse.getMajorCfVersion()).isEqualTo(V1_7);
assertThat(desugarInfoCollector.getNumberOfInvokeDynamic()).isEqualTo(0);
assertThat(desugarInfoCollector.getNumberOfDefaultMethods()).isEqualTo(0);
assertThat(desugarInfoCollector.getNumberOfDesugaredLambdas()).isEqualTo(1);
assertThat(desugarInfoCollector.getNumberOfCompanionClasses()).isEqualTo(3);
}
}
@Test
public void checkAfterDesugarClasspath() throws Exception {
DesugarInfoCollector desugarInfoCollector = new DesugarInfoCollector();
forAllClasses(desugaredClasspath, desugarInfoCollector);
// TODO(b/153971249): The class file version of desugared class files should be Java 7.
// assertThat(lambdaUse.getMajorCfVersion()).isEqualTo(V1_7);
assertThat(desugarInfoCollector.getNumberOfInvokeDynamic()).isEqualTo(0);
assertThat(desugarInfoCollector.getNumberOfDefaultMethods()).isEqualTo(0);
assertThat(desugarInfoCollector.getNumberOfDesugaredLambdas()).isEqualTo(1);
assertThat(desugarInfoCollector.getNumberOfCompanionClasses()).isEqualTo(0);
}
@Test
public void checkMetaDataAfterDoubleDesugaring() throws Exception {
DesugarDepsInfo info = extractDesugarDeps(doubleDesugaredWithDependencyMetadata);
assertThat(info.getInterfaceWithCompanionCount()).isEqualTo(0);
assertThat(info.getAssumePresentCount()).isEqualTo(0);
assertThat(info.getMissingInterfaceCount()).isEqualTo(0);
assertThat(info.getInterfaceWithSupertypesList())
.containsExactly(
InterfaceDetails.newBuilder()
.setOrigin(classToType(J.class))
.addExtendedInterface(classToType(I.class))
.build());
}
@SuppressWarnings("ProtoParseWithRegistry")
private static DesugarDepsInfo extractDesugarDeps(Path jar) throws Exception {
try (ZipFile zip = new ZipFile(jar.toFile())) {
ZipEntry desugarDepsEntry = zip.getEntry(Desugar.DESUGAR_DEPS_FILENAME);
assertThat(desugarDepsEntry).isNotNull();
return DesugarDepsInfo.parseFrom(zip.getInputStream(desugarDepsEntry));
}
}
private static DesugarDeps.Type classToType(Class<?> clazz) {
return DesugarDeps.Type.newBuilder()
.setBinaryName(DescriptorUtils.classToBinaryName(clazz))
.build();
}
private static DesugarDeps.Type classToCompanionType(Class<?> clazz) {
return DesugarDeps.Type.newBuilder()
.setBinaryName(DescriptorUtils.classToBinaryName(clazz) + INTERFACE_COMPANION_SUFFIX)
.build();
}
@Test
public void checkDependencyMetadata() throws Exception {
DesugarDepsInfo info = extractDesugarDeps(desugaredWithDependencyMetadata);
// Check expected metadata content.
assertThat(info.getAssumePresentList())
.containsExactly(
Dependency.newBuilder()
.setOrigin(classToType(A.class))
.setTarget(classToCompanionType(I.class))
.build(),
Dependency.newBuilder()
.setOrigin(classToType(B.class))
.setTarget(classToCompanionType(J.class))
.build(),
Dependency.newBuilder()
.setOrigin(classToType(C.class))
.setTarget(classToCompanionType(K.class))
.build());
assertThat(info.getInterfaceWithCompanionList())
.containsExactly(
InterfaceWithCompanion.newBuilder()
.setOrigin(classToType(I.class))
.setNumDefaultMethods(1)
.build(),
InterfaceWithCompanion.newBuilder()
.setOrigin(classToType(J.class))
.setNumDefaultMethods(1)
.build(),
InterfaceWithCompanion.newBuilder()
.setOrigin(classToType(K.class))
.setNumDefaultMethods(1)
.build());
assertThat(info.getInterfaceWithSupertypesList())
.containsExactly(
InterfaceDetails.newBuilder()
.setOrigin(classToType(J.class))
.addExtendedInterface(classToType(I.class))
.build());
assertThat(info.getMissingInterfaceCount()).isEqualTo(0);
}
@Test
public void checkDependencyMetadataClasspath() throws Exception {
DesugarDepsInfo info = extractDesugarDeps(desugaredClasspathWithDependencyMetadata);
// Check expected metadata content.
assertThat(info.getAssumePresentList())
.containsExactly(
Dependency.newBuilder()
.setOrigin(classToType(A.class))
.setTarget(classToCompanionType(I.class))
.build(),
Dependency.newBuilder()
.setOrigin(classToType(B.class))
.setTarget(classToCompanionType(J.class))
.build(),
Dependency.newBuilder()
.setOrigin(classToType(C.class))
.setTarget(classToCompanionType(K.class))
.build());
assertThat(info.getInterfaceWithCompanionCount()).isEqualTo(0);
assertThat(info.getInterfaceWithSupertypesCount()).isEqualTo(0);
assertThat(info.getMissingInterfaceCount()).isEqualTo(0);
}
@Test
public void checkDependencyMetadataMissingInterface() throws Exception {
DesugarDepsInfo info = extractDesugarDeps(desugaredWithDependencyMetadataMissingInterface);
// Check expected metadata content.
assertThat(info.getAssumePresentList())
.containsExactly(
Dependency.newBuilder()
.setOrigin(classToType(A.class))
.setTarget(classToCompanionType(I.class))
.build(),
Dependency.newBuilder()
.setOrigin(classToType(B.class))
.setTarget(classToCompanionType(J.class))
.build());
assertThat(info.getInterfaceWithCompanionList())
.containsExactly(
InterfaceWithCompanion.newBuilder()
.setOrigin(classToType(I.class))
.setNumDefaultMethods(1)
.build(),
InterfaceWithCompanion.newBuilder()
.setOrigin(classToType(J.class))
.setNumDefaultMethods(1)
.build());
assertThat(info.getInterfaceWithSupertypesList())
.containsExactly(
InterfaceDetails.newBuilder()
.setOrigin(classToType(J.class))
.addExtendedInterface(classToType(I.class))
.build());
assertThat(info.getMissingInterfaceList())
.containsExactly(
Dependency.newBuilder()
.setOrigin(classToType(C.class))
.setTarget(classToType(K.class))
.build());
}
private static void forAllClasses(Path jar, ClassVisitor classVisitor) throws Exception {
forAllClasses(
jar,
classReader ->
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES));
}
private static void forAllClasses(Path jar, Consumer<ClassReader> classReader) throws Exception {
try (JarInputStream jarInputStream =
new JarInputStream(Files.newInputStream(jar, StandardOpenOption.READ))) {
JarEntry entry;
while ((entry = jarInputStream.getNextJarEntry()) != null) {
String entryName = entry.getName();
if (FileUtils.isClassFile(entryName)) {
classReader.accept(new ClassReader(jarInputStream));
}
}
}
}
}