/*
* 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;
}
}
}