/* * Copyright 2013-present Facebook, Inc. * * 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.facebook.buck.dalvik.firstorder; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.facebook.buck.util.MoreCollectors; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.stream.StreamSupport; import org.junit.Test; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; public class FirstOrderTest { private static final ImmutableList<Class<?>> KNOWN_TYPES = ImmutableList.of( DependencyEnum.class, AnnotationDependency.class, DependencyInterface.class, DependencyBase.class, Dependency.class, DerivedInterface.class, Derived.class); private static final ImmutableList<ClassNode> KNOWN_CLASS_NODES = KNOWN_TYPES .stream() .map(FirstOrderTest::loadClassNode) .collect(MoreCollectors.toImmutableList()); enum DependencyEnum { DEFAULT } @Retention(RetentionPolicy.RUNTIME) @interface AnnotationDependency { DependencyEnum dependencyEnum() default DependencyEnum.DEFAULT; } interface DependencyInterface {} static class DependencyBase {} static class Dependency extends DependencyBase implements DependencyInterface {} interface DerivedInterface extends DependencyInterface {} static class Derived implements DerivedInterface {} @Test public void testHasBase() { class TestClass extends Dependency {} DependencyCheck.checkThat(TestClass.class) .doesNotDependOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .dependsOn(DependencyInterface.class) .dependsOn(DependencyBase.class) .dependsOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testHasField() { class TestClass { @SuppressWarnings("unused") Dependency a; } DependencyCheck.checkThat(TestClass.class) .doesNotDependOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .dependsOn(DependencyInterface.class) .dependsOn(DependencyBase.class) .dependsOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testHasParameter() { class TestClass { @SuppressWarnings("unused") void doSomething(Dependency dependency) {} } DependencyCheck.checkThat(TestClass.class) .doesNotDependOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .dependsOn(DependencyInterface.class) .dependsOn(DependencyBase.class) .dependsOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testHasReturnType() { class TestClass { @SuppressWarnings("unused") Dependency doSomething() { return null; } } DependencyCheck.checkThat(TestClass.class) .doesNotDependOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .dependsOn(DependencyInterface.class) .dependsOn(DependencyBase.class) .dependsOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testHasAnnotation() { class TestClass { @AnnotationDependency() int x; } DependencyCheck.checkThat(TestClass.class) .doesNotDependOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .doesNotDependOn(DependencyInterface.class) .doesNotDependOn(DependencyBase.class) .doesNotDependOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testHasAnnotationWithEnumProperty() { class TestClass { @AnnotationDependency(dependencyEnum = DependencyEnum.DEFAULT) int x; } DependencyCheck.checkThat(TestClass.class) .dependsOn(DependencyEnum.class) .doesNotDependOn(AnnotationDependency.class) .doesNotDependOn(DependencyInterface.class) .doesNotDependOn(DependencyBase.class) .doesNotDependOn(Dependency.class) .dependsOn(TestClass.class); } @Test public void testInterfaceInheritance() { DependencyCheck.checkThat(Derived.class) .dependsOn(Derived.class) .dependsOn(DerivedInterface.class) .dependsOn(DependencyInterface.class); } // Interestingly, observedDependencies includes DependencyInterface in // testInterfaceInheritance, but not in this test. So this verifies more // completely that we are including the transitive closure of implemented // interfaces. @Test public void testInterfaceInheritanceWithField() { class TestClass { @SuppressWarnings("unused") Derived x; } DependencyCheck.checkThat(TestClass.class) .dependsOn(TestClass.class) .dependsOn(Derived.class) .dependsOn(DerivedInterface.class) .dependsOn(DependencyInterface.class); } private static ImmutableSet<String> getFirstOrderDependencies( Iterable<? extends Class<?>> types) { ImmutableSet.Builder<String> builder = ImmutableSet.builder(); FirstOrderHelper.addTypesAndDependencies( FluentIterable.from(types).transform(Type::getType), loadAndMergeClasses(types, KNOWN_CLASS_NODES), builder); return builder.build(); } private static ImmutableList<ClassNode> loadAndMergeClasses( Iterable<? extends Class<?>> classes, Iterable<ClassNode> alreadyLoaded) { ImmutableList.Builder<ClassNode> builder = ImmutableList.builder(); builder.addAll(alreadyLoaded); builder.addAll( StreamSupport.stream(classes.spliterator(), false) .map(FirstOrderTest::loadClassNode) .iterator()); return builder.build(); } private static ClassNode loadClassNode(Class<?> input) { try { ClassReader reader = new ClassReader(input.getName()); ClassNode node = new ClassNode(Opcodes.ASM4); reader.accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); return node; } catch (IOException e) { throw new RuntimeException(e); } } private static class DependencyCheck { private final FluentIterable<String> typeNames; DependencyCheck(Iterable<String> typeNames) { this.typeNames = FluentIterable.from(typeNames); } static DependencyCheck checkThat(Class<?>... types) { return checkThat(ImmutableList.copyOf(types)); } static DependencyCheck checkThat(Iterable<? extends Class<?>> types) { return new DependencyCheck(getFirstOrderDependencies(types)); } DependencyCheck dependsOn(Class<?> type) { String name = Type.getType(type).getInternalName(); assertTrue( "Expected name '" + name + "' not found in list: " + typeNames, typeNames.contains(name)); return this; } DependencyCheck doesNotDependOn(Class<?> type) { String name = Type.getType(type).getInternalName(); assertFalse( "Unexpected name '" + name + "' found in list: " + typeNames, typeNames.contains(name)); return this; } } }