blob: c74febb1840ae327d709839d0c2ea4480e3a229c [file] [log] [blame]
// Copyright 2017 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.desugar;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.android.desugar.DefaultMethodClassFixer.InterfaceComparator.INSTANCE;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Closer;
import com.google.devtools.build.android.desugar.Desugar.ThrowingClassLoader;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import org.junit.After;
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;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
/** Unit Test for {@link DefaultMethodClassFixer} */
@RunWith(JUnit4.class)
public class DefaultMethodClassFixerTest {
private ClassReaderFactory classpathReader;
private ClassReaderFactory bootclassPath;
private ClassLoader classLoader;
private Closer closer;
@Before
public void setup() throws IOException {
closer = Closer.create();
CoreLibraryRewriter rewriter = new CoreLibraryRewriter("");
IndexedInputs indexedInputs =
toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.input"));
IndexedInputs indexedClasspath =
toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.classpath"));
IndexedInputs indexedBootclasspath =
toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.bootclasspath"));
bootclassPath = new ClassReaderFactory(indexedBootclasspath, rewriter);
IndexedInputs indexedClasspathAndInputFiles = indexedClasspath.withParent(indexedInputs);
classpathReader = new ClassReaderFactory(indexedClasspathAndInputFiles, rewriter);
ClassLoader bootclassloader =
new HeaderClassLoader(indexedBootclasspath, rewriter, new ThrowingClassLoader());
classLoader = new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader);
}
@After
public void teardown() throws IOException {
closer.close();
}
private static IndexedInputs toIndexedInputs(Closer closer, String stringPathList)
throws IOException {
final List<Path> pathList = readPathListFromString(stringPathList);
return new IndexedInputs(Desugar.toRegisteredInputFileProvider(closer, pathList));
}
private static List<Path> readPathListFromString(String pathList) {
return Arrays.stream(checkNotNull(pathList).split(File.pathSeparator))
.map(Paths::get)
.collect(ImmutableList.toImmutableList());
}
private byte[] desugar(String classname) {
ClassReader reader = classpathReader.readIfKnown(classname);
return desugar(reader);
}
private byte[] desugar(ClassReader reader) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
DefaultMethodClassFixer fixer =
new DefaultMethodClassFixer(
writer,
classpathReader,
DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING,
bootclassPath,
classLoader);
reader.accept(fixer, 0);
return writer.toByteArray();
}
private byte[] desugar(byte[] classContent) {
ClassReader reader = new ClassReader(classContent);
return desugar(reader);
}
@Test
public void testDesugaringDirectImplementation() {
byte[] desugaredClass =
desugar(
("com.google.devtools.build.android.desugar.testdata.java8."
+ "DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$C")
.replace('.', '/'));
checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(desugaredClass);
byte[] desugaredClassAgain = desugar(desugaredClass);
checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(
desugaredClassAgain);
desugaredClassAgain = desugar(desugaredClassAgain);
checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(
desugar(desugaredClassAgain));
}
private void checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(
byte[] classContent) {
ClassReader reader = new ClassReader(classContent);
reader.accept(
new ClassVisitor(Opcodes.ASM5) {
class ClinitMethod extends MethodNode {
public ClinitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
super(Opcodes.ASM5, access, name, desc, signature, exceptions);
}
}
private ClinitMethod clinit;
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
if ("<clinit>".equals(name)) {
assertThat(clinit).isNull();
clinit = new ClinitMethod(access, name, desc, signature, exceptions);
return clinit;
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
assertThat(clinit).isNotNull();
assertThat(clinit.instructions.size()).isEqualTo(3);
AbstractInsnNode instruction = clinit.instructions.getFirst();
{
assertThat(instruction).isInstanceOf(MethodInsnNode.class);
MethodInsnNode field = (MethodInsnNode) instruction;
assertThat(field.owner)
.isEqualTo(
"com/google/devtools/build/android/desugar/testdata/java8/"
+ "DefaultInterfaceMethodWithStaticInitializer"
+ "$TestInterfaceSetOne$I1$$CC");
assertThat(field.name)
.isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME);
assertThat(field.desc)
.isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC);
}
{
instruction = instruction.getNext();
assertThat(instruction).isInstanceOf(MethodInsnNode.class);
MethodInsnNode field = (MethodInsnNode) instruction;
assertThat(field.owner)
.isEqualTo(
"com/google/devtools/build/android/desugar/testdata/java8/"
+ "DefaultInterfaceMethodWithStaticInitializer"
+ "$TestInterfaceSetOne$I2$$CC");
assertThat(field.name)
.isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME);
assertThat(field.desc)
.isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC);
}
{
instruction = instruction.getNext();
assertThat(instruction).isInstanceOf(InsnNode.class);
assertThat(instruction.getOpcode()).isEqualTo(Opcodes.RETURN);
}
}
},
0);
}
@Test
public void testInterfaceComparator() {
assertThat(INSTANCE.compare(Runnable.class, Runnable.class)).isEqualTo(0);
assertThat(INSTANCE.compare(Runnable.class, MyRunnable1.class)).isEqualTo(1);
assertThat(INSTANCE.compare(MyRunnable2.class, Runnable.class)).isEqualTo(-1);
assertThat(INSTANCE.compare(MyRunnable3.class, Runnable.class)).isEqualTo(-1);
assertThat(INSTANCE.compare(MyRunnable1.class, MyRunnable3.class)).isEqualTo(1);
assertThat(INSTANCE.compare(MyRunnable3.class, MyRunnable2.class)).isEqualTo(-1);
assertThat(INSTANCE.compare(MyRunnable2.class, MyRunnable1.class)).isGreaterThan(0);
assertThat(INSTANCE.compare(Runnable.class, Serializable.class)).isGreaterThan(0);
assertThat(INSTANCE.compare(Serializable.class, Runnable.class)).isLessThan(0);
TreeSet<Class<?>> orderedSet = new TreeSet<>(INSTANCE);
orderedSet.add(Serializable.class);
orderedSet.add(Runnable.class);
orderedSet.add(MyRunnable2.class);
orderedSet.add(Callable.class);
orderedSet.add(Serializable.class);
orderedSet.add(MyRunnable1.class);
orderedSet.add(MyRunnable3.class);
assertThat(orderedSet)
.containsExactly(
MyRunnable3.class, // subtype before supertype(s)
MyRunnable1.class,
MyRunnable2.class,
Serializable.class, // java... comes textually after com.google...
Runnable.class,
Callable.class)
.inOrder();
}
private static interface MyRunnable1 extends Runnable {}
private static interface MyRunnable2 extends Runnable {}
private static interface MyRunnable3 extends MyRunnable1, MyRunnable2 {}
}