diff options
Diffstat (limited to 'javatests')
48 files changed, 5082 insertions, 278 deletions
diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java index 5a4d97e..15b54eb 100644 --- a/javatests/com/google/turbine/binder/BinderErrorTest.java +++ b/javatests/com/google/turbine/binder/BinderErrorTest.java @@ -22,11 +22,19 @@ import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Processing.ProcessorInfo; import com.google.turbine.diag.TurbineError; import com.google.turbine.parse.Parser; import com.google.turbine.tree.Tree.CompUnit; import java.util.Arrays; import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -259,7 +267,7 @@ public class BinderErrorTest { { "<>:2: error: java.lang.Object is not an annotation", // " @Object int x;", - " ^", + " ^", }, }, { @@ -271,7 +279,7 @@ public class BinderErrorTest { { "<>:2: error: java.lang.Deprecated is not @Repeatable", // " @Deprecated @Deprecated int x;", - " ^", + " ^", }, }, { @@ -283,7 +291,7 @@ public class BinderErrorTest { { "<>:2: error: could not resolve NoSuch.NoSuch", // " @NoSuch.NoSuch int x;", - " ^", + " ^", }, }, { @@ -417,6 +425,9 @@ public class BinderErrorTest { "}", }, { + "<>:1: error: cycle in class hierarchy: Cycle", + "class Cycle extends Cycle {", + " ^", "<>:2: error: could not resolve NoSuch", // " NoSuch f;", " ^", @@ -501,7 +512,7 @@ public class BinderErrorTest { " ^", "<>:3: error: could not resolve NoSuchAnno", "@NoSuchAnno", - " ^", + "^", }, }, { @@ -568,7 +579,127 @@ public class BinderErrorTest { "@One.A(b = {@One.NoSuch})", " ^", }, - } + }, + { + { + "public class Test {", // + " @interface Anno {", + " Class<?> value() default Object.class;", + " }", + " @Anno(NoSuch.class) int x;", + " @Anno(NoSuch.class) int y;", + "}", + }, + { + "<>:5: error: could not resolve NoSuch", + " @Anno(NoSuch.class) int x;", + " ^", + "<>:6: error: could not resolve NoSuch", + " @Anno(NoSuch.class) int y;", + " ^", + }, + }, + { + { + "public class Test {", // + " @A @B void f() {}", + "}", + }, + { + "<>:2: error: could not resolve A", + " @A @B void f() {}", + " ^", + "<>:2: error: could not resolve B", + " @A @B void f() {}", + " ^", + }, + }, + { + { + "public class Test {", // + " @A(\"bar\") void f() {}", + "}", + }, + { + "<>:2: error: could not resolve A", // + " @A(\"bar\") void f() {}", + " ^", + }, + }, + { + { + "@NoSuch", + "@interface A {", // + "}", + }, + { + "<>:1: error: could not resolve NoSuch", // + "@NoSuch", + "^", + }, + }, + { + { + "public class Test {", // + " @String @String int x;", + "}", + }, + { + "<>:2: error: java.lang.String is not an annotation", + " @String @String int x;", + " ^", + "<>:2: error: java.lang.String is not an annotation", + " @String @String int x;", + " ^", + }, + }, + { + { + "@interface Anno {", + " int value();", + "}", + "enum E {", + " ONE", + "}", + "@Anno(value = E.ONE)", + "interface Test {}", + }, + { + "<>:7: error: could not evaluate constant expression", // + "@Anno(value = E.ONE)", + " ^", + }, + }, + { + { + "class T extends T {}", + }, + { + "<>:1: error: cycle in class hierarchy: T", "class T extends T {}", " ^", + }, + }, + { + { + "class T implements T {}", + }, + { + "<>:1: error: cycle in class hierarchy: T", + "class T implements T {}", + " ^", + }, + }, + { + { + "class T {", // + " static final String s = \"a\" + + \"b\";", + "}", + }, + { + "<>:2: error: bad operand type String", + " static final String s = \"a\" + + \"b\";", + " ^", + }, + }, }; return Arrays.asList((Object[][]) testCases); } @@ -596,6 +727,41 @@ public class BinderErrorTest { } } + @SupportedAnnotationTypes("*") + static class HelloWorldProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return false; + } + } + + // exercise error reporting with annotation enabled, which should be identical + @Test + public void testWithProcessors() throws Exception { + try { + Binder.bind( + ImmutableList.of(parseLines(source)), + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new HelloWorldProcessor()), + /* loader= */ getClass().getClassLoader(), + /* options= */ ImmutableMap.of(), + SourceVersion.latestSupported()), + TURBINE_BOOTCLASSPATH, + /* moduleVersion=*/ Optional.empty()) + .units(); + fail(Joiner.on('\n').join(source)); + } catch (TurbineError e) { + assertThat(e).hasMessageThat().isEqualTo(lines(expected)); + } + } + private static CompUnit parseLines(String... lines) { return Parser.parse(lines(lines)); } diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java index 4b1e890..e238ee0 100644 --- a/javatests/com/google/turbine/binder/BinderTest.java +++ b/javatests/com/google/turbine/binder/BinderTest.java @@ -36,8 +36,6 @@ import com.google.turbine.tree.Tree; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.jar.JarEntry; @@ -55,22 +53,21 @@ public class BinderTest { @Test public void hello() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "public class A {", - " public class Inner1 extends b.B {", - " }", - " public class Inner2 extends A.Inner1 {", - " }", - "}")); - units.add( - parseLines( - "package b;", // - "import a.A;", - "public class B extends A {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "public class A {", + " public class Inner1 extends b.B {", + " }", + " public class Inner2 extends A.Inner1 {", + " }", + "}"), + parseLines( + "package b;", // + "import a.A;", + "public class B extends A {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -103,20 +100,19 @@ public class BinderTest { @Test public void interfaces() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.i;", // - "public interface I {", - " public class IInner {", - " }", - "}")); - units.add( - parseLines( - "package b;", // - "class B implements com.i.I {", - " class BInner extends IInner {}", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.i;", // + "public interface I {", + " public class IInner {", + " }", + "}"), + parseLines( + "package b;", // + "class B implements com.i.I {", + " class BInner extends IInner {}", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -143,20 +139,19 @@ public class BinderTest { @Test public void imports() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.test;", // - "public class Test {", - " public static class Inner {}", - "}")); - units.add( - parseLines( - "package other;", // - "import com.test.Test.Inner;", - "import no.such.Class;", // imports are resolved lazily on-demand - "public class Foo extends Inner {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.test;", // + "public class Test {", + " public static class Inner {}", + "}"), + parseLines( + "package other;", // + "import com.test.Test.Inner;", + "import no.such.Class;", // imports are resolved lazily on-demand + "public class Foo extends Inner {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -172,21 +167,20 @@ public class BinderTest { @Test public void cycle() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "import b.B;", - "public class A extends B.Inner {", - " class Inner {}", - "}")); - units.add( - parseLines( - "package b;", // - "import a.A;", - "public class B extends A.Inner {", - " class Inner {}", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "import b.B;", + "public class A extends B.Inner {", + " class Inner {}", + "}"), + parseLines( + "package b;", // + "import a.A;", + "public class B extends A.Inner {", + " class Inner {}", + "}")); try { Binder.bind( @@ -202,12 +196,12 @@ public class BinderTest { @Test public void annotationDeclaration() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package com.test;", // - "public @interface Annotation {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package com.test;", // + "public @interface Annotation {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -230,13 +224,13 @@ public class BinderTest { @Test public void helloBytecode() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "import java.util.Map.Entry;", - "public class A implements Entry {", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "import java.util.Map.Entry;", + "public class A implements Entry {", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -268,15 +262,15 @@ public class BinderTest { jos.write(lib.get("B")); } - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "import java.lang.annotation.Target;", - "import java.lang.annotation.ElementType;", - "public class C implements B {", - " @Target(ElementType.TYPE_USE)", - " @interface A {};", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "import java.lang.annotation.Target;", + "import java.lang.annotation.ElementType;", + "public class C implements B {", + " @Target(ElementType.TYPE_USE)", + " @interface A {};", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( @@ -294,13 +288,13 @@ public class BinderTest { // (Error reporting is deferred to javac.) @Test public void invalidConst() throws Exception { - List<Tree.CompUnit> units = new ArrayList<>(); - units.add( - parseLines( - "package a;", // - "public class A {", - " public static final boolean b = true == 42;", - "}")); + ImmutableList<Tree.CompUnit> units = + ImmutableList.of( + parseLines( + "package a;", // + "public class A {", + " public static final boolean b = true == 42;", + "}")); ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound = Binder.bind( diff --git a/javatests/com/google/turbine/binder/ClassPathBinderTest.java b/javatests/com/google/turbine/binder/ClassPathBinderTest.java index 3f41706..c11d814 100644 --- a/javatests/com/google/turbine/binder/ClassPathBinderTest.java +++ b/javatests/com/google/turbine/binder/ClassPathBinderTest.java @@ -44,7 +44,10 @@ import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type.ClassTy; import java.io.IOError; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -161,4 +164,18 @@ public class ClassPathBinderTest { assertThat(e).hasMessageThat().contains("NOT_A_JAR"); } } + + @Test + public void resources() throws Exception { + Path path = temporaryFolder.newFile("tmp.jar").toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) { + jos.putNextEntry(new JarEntry("foo/bar/hello.txt")); + jos.write("hello".getBytes(UTF_8)); + jos.putNextEntry(new JarEntry("foo/bar/Baz.class")); + jos.write("goodbye".getBytes(UTF_8)); + } + ClassPath classPath = ClassPathBinder.bindClasspath(ImmutableList.of(path)); + assertThat(new String(classPath.resource("foo/bar/hello.txt").get(), UTF_8)).isEqualTo("hello"); + assertThat(classPath.resource("foo/bar/Baz.class")).isNull(); + } } diff --git a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java index 2a0de48..3e841a5 100644 --- a/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java +++ b/javatests/com/google/turbine/binder/bytecode/BytecodeBoundClassTest.java @@ -22,14 +22,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; import static java.util.Objects.requireNonNull; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; import com.google.common.io.ByteStreams; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; import com.google.turbine.binder.env.CompoundEnv; import com.google.turbine.binder.env.Env; -import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.type.Type; import com.google.turbine.type.Type.ClassTy; @@ -37,7 +37,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.io.UncheckedIOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -84,6 +86,10 @@ public class BytecodeBoundClassTest { <X, Y extends X, Z extends Throwable> X foo(@Deprecated X bar, Y baz) throws IOException, Z { return null; } + + void baz() throws IOException { + throw new IOException(); + } } @Test @@ -97,6 +103,12 @@ public class BytecodeBoundClassTest { assertThat(m.parameters().get(0).annotations()).hasSize(1); assertThat(m.parameters().get(0).name()).isEqualTo("bar"); assertThat(m.exceptions()).hasSize(2); + + MethodInfo b = + getBytecodeBoundClass(HasMethod.class).methods().stream() + .filter(x -> x.name().equals("baz")) + .collect(onlyElement()); + assertThat(b.exceptions()).hasSize(1); } @interface VoidAnno { @@ -116,6 +128,53 @@ public class BytecodeBoundClassTest { .isEqualTo(Type.TyKind.ARRAY_TY); } + static class HasField { + @Deprecated List<String> foo; + } + + @Test + public void fieldTypes() { + FieldInfo f = + getBytecodeBoundClass(HasField.class).fields().stream() + .filter(x -> x.name().equals("foo")) + .collect(onlyElement()); + + assertThat(Iterables.getLast(((ClassTy) f.type()).classes()).targs()).hasSize(1); + assertThat(f.annotations()).hasSize(1); + } + + interface Y { + Object f(); + } + + interface X extends Y { + String f(); + } + + @Test + public void covariantBridges() { + assertThat(getBytecodeBoundClass(X.class, Y.class).methods()).hasSize(1); + } + + interface A<T> { + void f(T t); + } + + interface B<T extends Number> extends A<T> { + @Override + void f(T t); + } + + interface C<T extends Integer> extends B<T> { + @Override + void f(T t); + } + + @Test + public void genericBridges() { + assertThat(getBytecodeBoundClass(C.class, B.class, A.class).methods()).hasSize(1); + } + private static byte[] toByteArrayOrDie(InputStream is) { try { return ByteStreams.toByteArray(is); @@ -135,15 +194,29 @@ public class BytecodeBoundClassTest { "test.jar"); } - private BytecodeBoundClass getBytecodeBoundClass(Class<?> clazz) { - Env<ClassSymbol, BytecodeBoundClass> env = TURBINE_BOOTCLASSPATH.env(); - env = - CompoundEnv.of(env) + private BytecodeBoundClass getBytecodeBoundClass(Class<?> clazz, Class<?>... classpath) { + Map<ClassSymbol, BytecodeBoundClass> map = new HashMap<>(); + Env<ClassSymbol, BytecodeBoundClass> env = + CompoundEnv.of(TURBINE_BOOTCLASSPATH.env()) .append( - new SimpleEnv<>( - ImmutableMap.of( - new ClassSymbol(BytecodeBoundClass.class.getName().replace('.', '/')), - getBytecodeBoundClass(env, BytecodeBoundClassTest.class)))); + new Env<ClassSymbol, BytecodeBoundClass>() { + @Override + public BytecodeBoundClass get(ClassSymbol sym) { + return map.get(sym); + } + }); + addClass(clazz, map, env); + addClass(BytecodeBoundClassTest.class, map, env); + for (Class<?> c : classpath) { + addClass(c, map, env); + } return getBytecodeBoundClass(env, clazz); } + + private void addClass( + Class<?> clazz, + Map<ClassSymbol, BytecodeBoundClass> map, + Env<ClassSymbol, BytecodeBoundClass> env) { + map.put(new ClassSymbol(clazz.getName().replace('.', '/')), getBytecodeBoundClass(env, clazz)); + } } diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java index dda29ac..fb64541 100644 --- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java +++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.ModuleVisitor; import org.objectweb.asm.Opcodes; @@ -152,7 +153,8 @@ public class ClassReaderTest { "<X:Ljava/lang/Object;>Ljava/lang/Object;", "java/lang/Object", null); - cw.visitField(Opcodes.ACC_PUBLIC, "x", "I", null, null); + FieldVisitor fv = cw.visitField(Opcodes.ACC_PUBLIC, "x", "I", null, null); + fv.visitAnnotation("Ljava/lang/Deprecated;", true); cw.visitField( Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC, "y", @@ -173,7 +175,9 @@ public class ClassReaderTest { assertThat(x.descriptor()).isEqualTo("I"); assertThat(x.signature()).isNull(); assertThat(x.value()).isNull(); - assertThat(x.annotations()).isEmpty(); + assertThat(x.annotations()).hasSize(1); + ClassFile.AnnotationInfo annotation = Iterables.getOnlyElement(x.annotations()); + assertThat(annotation.typeName()).isEqualTo("Ljava/lang/Deprecated;"); ClassFile.FieldInfo y = classFile.fields().get(1); assertThat(y.access()) @@ -182,12 +186,13 @@ public class ClassReaderTest { assertThat(y.descriptor()).isEqualTo("I"); assertThat(y.value().constantTypeKind()).isEqualTo(TurbineConstantTypeKind.INT); assertThat(((Const.IntValue) y.value()).value()).isEqualTo(42); + assertThat(y.annotations()).isEmpty(); ClassFile.FieldInfo z = classFile.fields().get(2); assertThat(z.name()).isEqualTo("z"); assertThat(z.descriptor()).isEqualTo("Ljava/util/List;"); - // don't bother reading signatures for fields; we only care about constants - assertThat(z.signature()).isNull(); + assertThat(z.signature()).isEqualTo("Ljava/util/List<TX;>;"); + assertThat(z.annotations()).isEmpty(); } @Test diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java index e544c15..71cf356 100644 --- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java +++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java @@ -17,6 +17,7 @@ package com.google.turbine.bytecode; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; @@ -84,7 +85,7 @@ public class ClassWriterTest { /* classes= */ null, fileManager.getJavaFileObjects(path)); - assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue(); + assertWithMessage(collector.getDiagnostics().toString()).that(task.call()).isTrue(); byte[] original = Files.readAllBytes(out.resolve("test/Test.class")); byte[] actual = ClassWriter.writeClass(ClassReader.read(null, original)); diff --git a/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java b/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java index a0f0774..042bb80 100644 --- a/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java +++ b/javatests/com/google/turbine/bytecode/sig/SigRegressionTest.java @@ -102,5 +102,6 @@ public class SigRegressionTest { input = "LA<[-[Z>.I;"; sig = new SigParser(input).parseClassSig(); + assertThat(SigWriter.classSig(sig)).isEqualTo(input); } } diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java index 4cb8adf..c5b68ff 100644 --- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java +++ b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java @@ -36,7 +36,6 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -143,7 +142,7 @@ public abstract class AbstractTransitiveTest { // Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations. Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar"); - List<String> sources = + ImmutableList<String> sources = new SourceBuilder() .addSourceLines( "c/C.java", @@ -152,18 +151,17 @@ public abstract class AbstractTransitiveTest { " @Anno(x = 2) static final Inner i; // a.A$Inner ", " static final int X = CONST; // a.A#CONST", "}") - .build().stream() + .build() + .stream() .map(Path::toString) .collect(toImmutableList()); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(sources) - .addClassPathEntries( - ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) - .setOutput(libc.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(sources) + .setClassPath( + ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList())) + .setOutput(libc.toString()) + .build()); assertThat(readJar(libc).keySet()) .containsExactly( diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java index b1da209..bc663cd 100644 --- a/javatests/com/google/turbine/deps/DependenciesTest.java +++ b/javatests/com/google/turbine/deps/DependenciesTest.java @@ -22,7 +22,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Streams; import com.google.turbine.binder.Binder; import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPathBinder; @@ -40,7 +39,6 @@ import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -88,7 +86,7 @@ public class DependenciesTest { static class DepsBuilder { List<Path> classpath; - List<CompUnit> units = new ArrayList<>(); + ImmutableList.Builder<CompUnit> units = ImmutableList.builder(); DepsBuilder setClasspath(Path... classpath) { this.classpath = ImmutableList.copyOf(classpath); @@ -103,7 +101,7 @@ public class DependenciesTest { DepsProto.Dependencies run() throws IOException { BindingResult bound = Binder.bind( - units, + units.build(), ClassPathBinder.bindClasspath(classpath), TestClassPaths.TURBINE_BOOTCLASSPATH, /* moduleVersion=*/ Optional.empty()); @@ -116,7 +114,7 @@ public class DependenciesTest { } private Map<Path, DepsProto.Dependency.Kind> depsMap(DepsProto.Dependencies deps) { - return Streams.stream(deps.getDependencyList()) + return deps.getDependencyList().stream() .collect(Collectors.toMap(d -> Paths.get(d.getPath()), DepsProto.Dependency::getKind)); } @@ -333,6 +331,96 @@ public class DependenciesTest { } } + @Test + public void annotations_recursive() throws Exception { + Path libA = libA(); + Path libB = libB(); + + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "@A(B.class)", + "class Test {", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + @Test + public void annotations_field() throws Exception { + Path libA = libA(); + Path libB = libB(); + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "class Test {", + " @A(B.class)", + " int x;", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + @Test + public void annotations_method() throws Exception { + Path libA = libA(); + Path libB = libB(); + DepsProto.Dependencies deps = + new DepsBuilder() + .setClasspath(libA, libB) + .addSourceLines( + "Test.java", // + "import a.A;", + "import b.B;", + "class Test {", + " @A(B.class)", + " void f() {}", + "}") + .run(); + assertThat(depsMap(deps)) + .containsExactly( + libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); + } + + private Path libB() throws Exception { + return new LibraryBuilder() + .addSourceLines( + "b/B.java", + "package b;", + "import java.lang.annotation.Retention;", + "import static java.lang.annotation.RetentionPolicy.RUNTIME;", + "@Retention(RUNTIME)", + "public @interface B {", + "}") + .compileToJar("libb.jar"); + } + + private Path libA() throws Exception { + return new LibraryBuilder() + .addSourceLines( + "a/A.java", + "package a;", + "import java.lang.annotation.Retention;", + "import static java.lang.annotation.RetentionPolicy.RUNTIME;", + "@Retention(RUNTIME)", + "public @interface A {", + " Class<?> value() default Object.class;", + "}") + .compileToJar("liba.jar"); + } + void writeDeps(Path path, ImmutableMap<String, DepsProto.Dependency.Kind> deps) throws IOException { DepsProto.Dependencies.Builder builder = diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java index f8c5b50..2c9f807 100644 --- a/javatests/com/google/turbine/deps/TransitiveTest.java +++ b/javatests/com/google/turbine/deps/TransitiveTest.java @@ -17,7 +17,6 @@ package com.google.turbine.deps; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import com.google.common.collect.ImmutableList; @@ -34,15 +33,12 @@ public class TransitiveTest extends AbstractTransitiveTest { protected Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath) throws IOException { Path out = temporaryFolder.newFolder().toPath().resolve("out.jar"); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(sources.stream().map(Path::toString).collect(toImmutableList())) - .addClassPathEntries( - classpath.stream().map(Path::toString).collect(toImmutableList())) - .setOutput(out.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(sources.stream().map(Path::toString).collect(toImmutableList())) + .setClassPath(classpath.stream().map(Path::toString).collect(toImmutableList())) + .setOutput(out.toString()) + .build()); return out; } } diff --git a/javatests/com/google/turbine/lower/IntegrationTestSupport.java b/javatests/com/google/turbine/lower/IntegrationTestSupport.java index 680b073..a03473d 100644 --- a/javatests/com/google/turbine/lower/IntegrationTestSupport.java +++ b/javatests/com/google/turbine/lower/IntegrationTestSupport.java @@ -16,11 +16,13 @@ package com.google.turbine.lower; -import static com.google.common.truth.Truth.assertThat; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.turbine.testing.TestClassPaths.TURBINE_BOOTCLASSPATH; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; +import static org.junit.Assert.fail; import com.google.common.base.Joiner; import com.google.common.base.Splitter; @@ -29,12 +31,13 @@ import com.google.common.io.MoreFiles; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; import com.google.turbine.binder.ClassPath; import com.google.turbine.binder.ClassPathBinder; import com.google.turbine.diag.SourceFile; import com.google.turbine.parse.Parser; import com.google.turbine.testing.AsmUtils; -import com.google.turbine.tree.Tree; +import com.google.turbine.tree.Tree.CompUnit; import com.sun.source.util.JavacTask; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.file.JavacFileManager; @@ -100,8 +103,11 @@ public class IntegrationTestSupport { public static Map<String, byte[]> canonicalize(Map<String, byte[]> in) { List<ClassNode> classes = toClassNodes(in); - // drop anonymous classes - classes = classes.stream().filter(n -> !isAnonymous(n)).collect(toCollection(ArrayList::new)); + // drop local and anonymous classes + classes = + classes.stream() + .filter(n -> !isAnonymous(n) && !isLocal(n)) + .collect(toCollection(ArrayList::new)); // collect all inner classes attributes Map<String, InnerClassNode> infos = new HashMap<>(); @@ -123,6 +129,10 @@ public class IntegrationTestSupport { return toByteCode(classes); } + private static boolean isLocal(ClassNode n) { + return n.outerMethod != null; + } + private static boolean isAnonymous(ClassNode n) { // JVMS 4.7.6: if C is anonymous, the value of the inner_name_index item must be zero return n.innerClasses.stream().anyMatch(i -> i.name.equals(n.name) && i.innerName == null); @@ -436,15 +446,41 @@ public class IntegrationTestSupport { ClassPath bootClassPath, Optional<String> moduleVersion) throws IOException { - List<Tree.CompUnit> units = + BindingResult bound = turbineAnalysis(input, classpath, bootClassPath, moduleVersion); + return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes(); + } + + public static BindingResult turbineAnalysis( + Map<String, String> input, + ImmutableList<Path> classpath, + ClassPath bootClassPath, + Optional<String> moduleVersion) + throws IOException { + ImmutableList<CompUnit> units = input.entrySet().stream() .map(e -> new SourceFile(e.getKey(), e.getValue())) .map(Parser::parse) - .collect(toList()); + .collect(toImmutableList()); - Binder.BindingResult bound = - Binder.bind(units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion); - return Lower.lowerAll(bound.units(), bound.modules(), bound.classPathEnv()).bytes(); + return Binder.bind( + units, ClassPathBinder.bindClasspath(classpath), bootClassPath, moduleVersion); + } + + public static JavacTask runJavacAnalysis( + Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) + throws Exception { + return runJavacAnalysis(sources, classpath, options, new DiagnosticCollector<>()); + } + + public static JavacTask runJavacAnalysis( + Map<String, String> sources, + Collection<Path> classpath, + ImmutableList<String> options, + DiagnosticCollector<JavaFileObject> collector) + throws Exception { + FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + Path out = fs.getPath("out"); + return setupJavac(sources, classpath, options, collector, fs, out); } public static Map<String, byte[]> runJavac( @@ -457,10 +493,46 @@ public class IntegrationTestSupport { Map<String, String> sources, Collection<Path> classpath, ImmutableList<String> options) throws Exception { + DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); + Path out = fs.getPath("out"); + JavacTask task = setupJavac(sources, classpath, options, collector, fs, out); + + if (!task.call()) { + fail(collector.getDiagnostics().stream().map(d -> d.toString()).collect(joining("\n"))); + } + + List<Path> classes = new ArrayList<>(); + Files.walkFileTree( + out, + new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) + throws IOException { + if (path.getFileName().toString().endsWith(".class")) { + classes.add(path); + } + return FileVisitResult.CONTINUE; + } + }); + Map<String, byte[]> result = new LinkedHashMap<>(); + for (Path path : classes) { + String r = out.relativize(path).toString(); + result.put(r.substring(0, r.length() - ".class".length()), Files.readAllBytes(path)); + } + return result; + } + + private static JavacTask setupJavac( + Map<String, String> sources, + Collection<Path> classpath, + ImmutableList<String> options, + DiagnosticCollector<JavaFileObject> collector, + FileSystem fs, + Path out) + throws IOException { Path srcs = fs.getPath("srcs"); - Path out = fs.getPath("out"); Files.createDirectories(out); @@ -475,7 +547,6 @@ public class IntegrationTestSupport { } JavacTool compiler = JavacTool.create(); - DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); JavacFileManager fileManager = new JavacFileManager(new Context(), true, UTF_8); fileManager.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, ImmutableList.of(out)); fileManager.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath); @@ -487,36 +558,13 @@ public class IntegrationTestSupport { StandardLocation.locationFor("MODULE_SOURCE_PATH"), ImmutableList.of(srcs)); } - JavacTask task = - compiler.getTask( - new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), - fileManager, - collector, - options, - ImmutableList.of(), - fileManager.getJavaFileObjectsFromPaths(inputs)); - - assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue(); - - List<Path> classes = new ArrayList<>(); - Files.walkFileTree( - out, - new SimpleFileVisitor<Path>() { - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) - throws IOException { - if (path.getFileName().toString().endsWith(".class")) { - classes.add(path); - } - return FileVisitResult.CONTINUE; - } - }); - Map<String, byte[]> result = new LinkedHashMap<>(); - for (Path path : classes) { - String r = out.relativize(path).toString(); - result.put(r.substring(0, r.length() - ".class".length()), Files.readAllBytes(path)); - } - return result; + return compiler.getTask( + new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, UTF_8)), true), + fileManager, + collector, + options, + ImmutableList.of(), + fileManager.getJavaFileObjectsFromPaths(inputs)); } /** Normalizes and stringifies a collection of class files. */ @@ -535,17 +583,17 @@ public class IntegrationTestSupport { return sb.toString(); } - static class TestInput { + public static class TestInput { - final Map<String, String> sources; - final Map<String, String> classes; + public final Map<String, String> sources; + public final Map<String, String> classes; public TestInput(Map<String, String> sources, Map<String, String> classes) { this.sources = sources; this.classes = classes; } - static TestInput parse(String text) { + public static TestInput parse(String text) { Map<String, String> sources = new LinkedHashMap<>(); Map<String, String> classes = new LinkedHashMap<>(); String className = null; diff --git a/javatests/com/google/turbine/lower/LowerIntegrationTest.java b/javatests/com/google/turbine/lower/LowerIntegrationTest.java index f7e9c18..85c3450 100644 --- a/javatests/com/google/turbine/lower/LowerIntegrationTest.java +++ b/javatests/com/google/turbine/lower/LowerIntegrationTest.java @@ -308,6 +308,9 @@ public class LowerIntegrationTest { "shadow_inherited.test", "static_final_boxed.test", "anno_void.test", + "tyanno_varargs.test", + "tyanno_inner.test", + "local.test", }; List<Object[]> tests = ImmutableList.copyOf(testCases).stream().map(x -> new Object[] {x}).collect(toList()); diff --git a/javatests/com/google/turbine/lower/LowerSignatureTest.java b/javatests/com/google/turbine/lower/LowerSignatureTest.java index 5ccbf01..08bc46d 100644 --- a/javatests/com/google/turbine/lower/LowerSignatureTest.java +++ b/javatests/com/google/turbine/lower/LowerSignatureTest.java @@ -83,7 +83,7 @@ public class LowerSignatureTest { assertThat(SigWriter.type(new LowerSignature().signature(type))) .isEqualTo("Ltest/Outer<Ljava/lang/Object;>.Inner<Ljava/lang/Object;>;"); // Type#toString is only for debugging - assertThat(type.toString()).isEqualTo("test/Outer<java/lang/Object>.Inner<java/lang/Object>"); + assertThat(type.toString()).isEqualTo("test.Outer<java.lang.Object>.Inner<java.lang.Object>"); } @Test diff --git a/javatests/com/google/turbine/lower/LowerTest.java b/javatests/com/google/turbine/lower/LowerTest.java index 0de55c3..8151e81 100644 --- a/javatests/com/google/turbine/lower/LowerTest.java +++ b/javatests/com/google/turbine/lower/LowerTest.java @@ -33,6 +33,7 @@ import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.FieldSymbol; import com.google.turbine.binder.sym.MethodSymbol; +import com.google.turbine.binder.sym.ParamSymbol; import com.google.turbine.binder.sym.TyVarSymbol; import com.google.turbine.bytecode.ByteReader; import com.google.turbine.bytecode.ConstantPoolReader; @@ -104,12 +105,13 @@ public class LowerTest { new ClassSymbol("test/Test$Inner"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of())); int access = TurbineFlag.ACC_SUPER | TurbineFlag.ACC_PUBLIC; ImmutableList<SourceTypeBoundClass.MethodInfo> methods = ImmutableList.of( new SourceTypeBoundClass.MethodInfo( - new MethodSymbol(new ClassSymbol("test/Test"), "f"), + new MethodSymbol(-1, new ClassSymbol("test/Test"), "f"), ImmutableMap.of(), PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), ImmutableList.of(), @@ -120,9 +122,9 @@ public class LowerTest { ImmutableList.of(), null), new SourceTypeBoundClass.MethodInfo( - new MethodSymbol(new ClassSymbol("test/Test"), "g"), + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), ImmutableMap.of( - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "V"), + new TyVarSymbol(new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "V"), new SourceTypeBoundClass.TyVarInfo( IntersectionTy.create( ImmutableList.of( @@ -132,8 +134,9 @@ public class LowerTest { new ClassSymbol("java/lang/Runnable"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of()), - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), + new TyVarSymbol(new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "E"), new SourceTypeBoundClass.TyVarInfo( IntersectionTy.create( ImmutableList.of( @@ -143,17 +146,20 @@ public class LowerTest { new ClassSymbol("java/lang/Error"), ImmutableList.of(), ImmutableList.of()))))), + /* lowerBound= */ null, ImmutableList.of())), Type.VOID, ImmutableList.of( new SourceTypeBoundClass.ParamInfo( + new ParamSymbol( + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "foo"), PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()), - "foo", ImmutableList.of(), 0)), ImmutableList.of( TyVar.create( - new TyVarSymbol(new MethodSymbol(new ClassSymbol("test/Test"), "g"), "E"), + new TyVarSymbol( + new MethodSymbol(-1, new ClassSymbol("test/Test"), "g"), "E"), ImmutableList.of())), TurbineFlag.ACC_PUBLIC, null, @@ -350,7 +356,7 @@ public class LowerTest { int typeRef, TypePath typePath, String desc, boolean visible) { path[0] = typePath; return null; - }; + } }; } }, @@ -604,9 +610,10 @@ public class LowerTest { assertThat(error) .hasMessageThat() .contains( - "Test.java:3: error: could not locate class file for A\n" - + " I i;\n" - + " ^"); + lines( + "Test.java:3: error: could not locate class file for A", + " I i;", + " ^")); } } @@ -640,6 +647,6 @@ public class LowerTest { } static String lines(String... lines) { - return Joiner.on("\n").join(lines); + return Joiner.on(System.lineSeparator()).join(lines); } } diff --git a/javatests/com/google/turbine/lower/testdata/local.test b/javatests/com/google/turbine/lower/testdata/local.test new file mode 100644 index 0000000..fd6d4c5 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/local.test @@ -0,0 +1,8 @@ +=== T.java === +class T { + Object f() { + class Local { + } + return new Local(); + } +}
\ No newline at end of file diff --git a/javatests/com/google/turbine/lower/testdata/tyanno_inner.test b/javatests/com/google/turbine/lower/testdata/tyanno_inner.test new file mode 100644 index 0000000..d42b3a3 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/tyanno_inner.test @@ -0,0 +1,16 @@ +%%% A.java %%% +import static java.lang.annotation.ElementType.TYPE_PARAMETER; + +import java.lang.annotation.Target; + +@interface A { + + @Target(TYPE_PARAMETER) + @interface I { + } +} + +=== T.java === +abstract class T<@A.I X> { + +} diff --git a/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test b/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test new file mode 100644 index 0000000..1fbc1e1 --- /dev/null +++ b/javatests/com/google/turbine/lower/testdata/tyanno_varargs.test @@ -0,0 +1,12 @@ +=== T.java === +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; + +import java.lang.annotation.Target; + +abstract class T { + @Target({PARAMETER, TYPE_USE}) + @interface A {} + + void f(boolean a, @A String b, Object @A... xs) {} +} diff --git a/javatests/com/google/turbine/main/MainTest.java b/javatests/com/google/turbine/main/MainTest.java index 65f3167..5d47632 100644 --- a/javatests/com/google/turbine/main/MainTest.java +++ b/javatests/com/google/turbine/main/MainTest.java @@ -17,17 +17,29 @@ package com.google.turbine.main; import static com.google.common.base.StandardSystemProperty.JAVA_CLASS_VERSION; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteStreams; import com.google.common.io.MoreFiles; +import com.google.protobuf.ExtensionRegistry; import com.google.turbine.diag.TurbineError; import com.google.turbine.options.TurbineOptions; +import com.google.turbine.proto.ManifestProto; +import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; @@ -35,16 +47,27 @@ import java.time.ZoneId; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; +import java.util.stream.Stream; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; @RunWith(JUnit4.class) public class MainTest { @@ -84,13 +107,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(ImmutableList.of(src.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("test/package-info.class"); @@ -106,13 +127,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .setSourceJars(ImmutableList.of(srcjar.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSourceJars(ImmutableList.of(srcjar.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("test/package-info.class"); @@ -150,15 +169,13 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - TurbineOptions.builder() - .setRelease("9") - .addSources(ImmutableList.of(src.toString())) - .setSourceJars(ImmutableList.of(srcjar.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + TurbineOptions.builder() + .setRelease("9") + .setSources(ImmutableList.of(src.toString())) + .setSourceJars(ImmutableList.of(srcjar.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()) @@ -171,26 +188,48 @@ public class MainTest { MoreFiles.asCharSink(src, UTF_8).write("class Foo {}"); Path output = temporaryFolder.newFile("output.jar").toPath(); + Path gensrcOutput = temporaryFolder.newFile("gensrcOutput.jar").toPath(); - boolean ok = - Main.compile( - optionsWithBootclasspath() - .addSources(ImmutableList.of(src.toString())) - .setTargetLabel("//foo:foo") - .setInjectingRuleKind("foo_library") - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setGensrcOutput(gensrcOutput.toString()) + .build()); try (JarFile jarFile = new JarFile(output.toFile())) { + try (Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsAtLeast("META-INF/", "META-INF/MANIFEST.MF"); + } Manifest manifest = jarFile.getManifest(); Attributes attributes = manifest.getMainAttributes(); - assertThat(attributes.getValue("Target-Label")).isEqualTo("//foo:foo"); - assertThat(attributes.getValue("Injecting-Rule-Kind")).isEqualTo("foo_library"); + ImmutableMap<String, ?> entries = + attributes.entrySet().stream() + .collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue)); + assertThat(entries) + .containsExactly( + "Created-By", "bazel", + "Manifest-Version", "1.0", + "Target-Label", "//foo:foo", + "Injecting-Rule-Kind", "foo_library"); assertThat(jarFile.getEntry(JarFile.MANIFEST_NAME).getLastModifiedTime().toInstant()) .isEqualTo( LocalDateTime.of(2010, 1, 1, 0, 0, 0).atZone(ZoneId.systemDefault()).toInstant()); } + try (JarFile jarFile = new JarFile(gensrcOutput.toFile())) { + Manifest manifest = jarFile.getManifest(); + Attributes attributes = manifest.getMainAttributes(); + ImmutableMap<String, ?> entries = + attributes.entrySet().stream() + .collect(toImmutableMap(e -> e.getKey().toString(), Map.Entry::getValue)); + assertThat(entries) + .containsExactly( + "Created-By", "bazel", + "Manifest-Version", "1.0"); + } } @Test @@ -201,13 +240,11 @@ public class MainTest { Path output = temporaryFolder.newFile("output.jar").toPath(); - boolean ok = - Main.compile( - TurbineOptions.builder() - .addSources(ImmutableList.of(src.toString())) - .setOutput(output.toString()) - .build()); - assertThat(ok).isTrue(); + Main.compile( + TurbineOptions.builder() + .setSources(ImmutableList.of(src.toString())) + .setOutput(output.toString()) + .build()); Map<String, byte[]> data = readJar(output); assertThat(data.keySet()).containsExactly("java/lang/Object.class"); @@ -223,7 +260,7 @@ public class MainTest { try { Main.compile( TurbineOptions.builder() - .addSources(ImmutableList.of(src.toString())) + .setSources(ImmutableList.of(src.toString())) .setOutput(output.toString()) .build()); fail(); @@ -238,10 +275,200 @@ public class MainTest { MoreFiles.asCharSink(src, UTF_8).write("public class Test {}"); try { - Main.compile(optionsWithBootclasspath().addSources(ImmutableList.of(src.toString())).build()); + Main.compile(optionsWithBootclasspath().setSources(ImmutableList.of(src.toString())).build()); fail(); } catch (UsageException expected) { - assertThat(expected).hasMessageThat().contains("--output is required"); + assertThat(expected) + .hasMessageThat() + .contains("at least one of --output, --gensrc_output, or --resource_output is required"); + } + } + + @Test + public void noSources() throws IOException { + // Compilations with no sources (or source jars) are accepted, and create empty for requested + // outputs. This is helpful for the Bazel integration, which allows java_library rules to be + // declared without sources. + File gensrc = temporaryFolder.newFile("gensrc.jar"); + Main.compile(optionsWithBootclasspath().setGensrcOutput(gensrc.toString()).build()); + try (JarFile jarFile = new JarFile(gensrc); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsExactly("META-INF/", "META-INF/MANIFEST.MF"); + } + } + + @SupportedAnnotationTypes("*") + public static class SourceGeneratingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (Writer writer = processingEnv.getFiler().createSourceFile("g.Gen").openWriter()) { + writer.write("package g; class Gen {}"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + } + + @Test + public void testManifestProto() throws IOException { + Path src = temporaryFolder.newFile("Foo.java").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("package f; @Deprecated class Foo {}"); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path gensrcOutput = temporaryFolder.newFile("gensrcOutput.jar").toPath(); + Path manifestProtoOutput = temporaryFolder.newFile("manifest.proto").toPath(); + + Main.compile( + optionsWithBootclasspath() + .setSources(ImmutableList.of(src.toString())) + .setTargetLabel("//foo:foo") + .setInjectingRuleKind("foo_library") + .setOutput(output.toString()) + .setGensrcOutput(gensrcOutput.toString()) + .setOutputManifest(manifestProtoOutput.toString()) + .setProcessors(ImmutableList.of(SourceGeneratingProcessor.class.getName())) + .build()); + + assertThat(readManifestProto(manifestProtoOutput)) + .isEqualTo( + ManifestProto.Manifest.newBuilder() + .addCompilationUnit( + ManifestProto.CompilationUnit.newBuilder() + .setPkg("f") + .addTopLevel("Foo") + .setPath(src.toString()) + .setGeneratedByAnnotationProcessor(false) + .build()) + .addCompilationUnit( + ManifestProto.CompilationUnit.newBuilder() + .setPkg("g") + .addTopLevel("Gen") + .setPath("g/Gen.java") + .setGeneratedByAnnotationProcessor(true) + .build()) + .build()); + } + + private static ManifestProto.Manifest readManifestProto(Path manifestProtoOutput) + throws IOException { + ManifestProto.Manifest.Builder manifest = ManifestProto.Manifest.newBuilder(); + try (InputStream is = new BufferedInputStream(Files.newInputStream(manifestProtoOutput))) { + manifest.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); + } + return manifest.build(); + } + + @SupportedAnnotationTypes("*") + public static class CrashyProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + throw new AssertionError(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + return false; + } + } + + @Test + public void noSourcesProcessing() throws IOException { + // Compilations with no sources shouldn't initialize annotation processors. + File gensrc = temporaryFolder.newFile("gensrc.jar"); + Main.compile( + optionsWithBootclasspath() + .setProcessors(ImmutableList.of(CrashyProcessor.class.getName())) + .setGensrcOutput(gensrc.toString()) + .build()); + try (JarFile jarFile = new JarFile(gensrc); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)) + .containsExactly("META-INF/", "META-INF/MANIFEST.MF"); + } + } + + @SupportedAnnotationTypes("*") + public static class ClassGeneratingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (OutputStream outputStream = + processingEnv.getFiler().createClassFile("g.Gen").openOutputStream()) { + outputStream.write(dump()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + + public static byte[] dump() { + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit( + Opcodes.V1_8, + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + "g/Gen", + null, + "java/lang/Object", + null); + { + MethodVisitor methodVisitor = + classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); + methodVisitor.visitInsn(Opcodes.RETURN); + methodVisitor.visitMaxs(1, 1); + methodVisitor.visitEnd(); + } + classWriter.visitEnd(); + return classWriter.toByteArray(); + } + } + + @Test + public void classGeneration() throws IOException { + Path src = temporaryFolder.newFile("package-info.jar").toPath(); + MoreFiles.asCharSink(src, UTF_8).write("@Deprecated package test;"); + File resources = temporaryFolder.newFile("resources.jar"); + Main.compile( + optionsWithBootclasspath() + .setProcessors(ImmutableList.of(ClassGeneratingProcessor.class.getName())) + .setSources(ImmutableList.of(src.toString())) + .setResourceOutput(resources.toString()) + .build()); + try (JarFile jarFile = new JarFile(resources); + Stream<JarEntry> entries = jarFile.stream()) { + assertThat(entries.map(JarEntry::getName)).containsExactly("g/Gen.class"); } } } diff --git a/javatests/com/google/turbine/main/ReducedClasspathTest.java b/javatests/com/google/turbine/main/ReducedClasspathTest.java new file mode 100644 index 0000000..d74c640 --- /dev/null +++ b/javatests/com/google/turbine/main/ReducedClasspathTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.main; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; +import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ExtensionRegistry; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.main.Main.Result; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; +import com.google.turbine.proto.DepsProto; +import com.google.turbine.proto.DepsProto.Dependency.Kind; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ReducedClasspathTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private Path liba; + private Path libb; + private Path libc; + private Path libcJdeps; + + @Before + public void setup() throws Exception { + Map<String, byte[]> compiled = + IntegrationTestSupport.runJavac( + TestInput.parse( + String.join( + "\n", + ImmutableList.of( + "=== a/A.java ===", + "package a;", + "public class A {", + " public static class I {}", + "}", + "=== b/B.java ===", + "package b;", + "import a.A;", + "public class B extends A {}", + "=== c/C.java ===", + "package c;", + "import b.B;", + "public class C extends B {}"))) + .sources, + /* classpath= */ ImmutableList.of()); + + liba = createLibrary(compiled, "liba.jar", "a/A", "a/A$I"); + libb = createLibrary(compiled, "libb.jar", "b/B"); + libc = createLibrary(compiled, "libc.jar", "c/C"); + + libcJdeps = temporaryFolder.newFile("libc.jdeps").toPath(); + try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(libcJdeps))) { + DepsProto.Dependencies.newBuilder() + .addDependency( + DepsProto.Dependency.newBuilder() + .setKind(Kind.EXPLICIT) + .setPath(libb.toString()) + .build()) + .build() + .writeTo(os); + } + } + + private Path createLibrary(Map<String, byte[]> compiled, String jarPath, String... classNames) + throws IOException { + Path lib = temporaryFolder.newFile(jarPath).toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { + for (String className : classNames) { + jos.putNextEntry(new JarEntry(className + ".class")); + jos.write(compiled.get(className)); + } + } + return lib; + } + + @Test + public void succeedsWithoutFallingBack() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath( + ImmutableList.of( + // ensure that the compilation succeeds without falling back by adding + // a jar to the transitive classpath that doesn't exist, which would cause + // the compilation to fail if it fell back + temporaryFolder.newFile("no.such.jar").toString(), + liba.toString(), + libb.toString(), + libc.toString())) + .setDirectJars(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + assertThat(result.transitiveClasspathFallback()).isFalse(); + } + + @Test + public void succeedsAfterFallingBack() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath(ImmutableList.of(liba.toString(), libb.toString(), libc.toString())) + .setDirectJars(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + assertThat(result.transitiveClasspathFallback()).isTrue(); + assertThat(result.reducedClasspathLength()).isEqualTo(2); + assertThat(result.transitiveClasspathLength()).isEqualTo(3); + } + + @Test + public void bazelFallback() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + Path jdeps = temporaryFolder.newFile("output.jdeps").toPath(); + + Result result = + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setTargetLabel("//java/com/google/foo") + .setOutputDeps(jdeps.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.BAZEL_REDUCED) + .setClassPath(ImmutableList.of(libc.toString())) + .setReducedClasspathLength(1) + .setFullClasspathLength(3) + .build()); + assertThat(result.transitiveClasspathFallback()).isTrue(); + assertThat(result.reducedClasspathLength()).isEqualTo(1); + assertThat(result.transitiveClasspathLength()).isEqualTo(3); + DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); + try (InputStream is = new BufferedInputStream(Files.newInputStream(jdeps))) { + deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); + } + assertThat(deps.build()) + .isEqualTo( + DepsProto.Dependencies.newBuilder() + .setRequiresReducedClasspathFallback(true) + .setRuleLabel("//java/com/google/foo") + .build()); + } + + @Test + public void noFallbackWithoutDirectJarsAndJdeps() throws Exception { + Path src = temporaryFolder.newFile("Test.java").toPath(); + Files.write( + src, + ImmutableList.of( + "import c.C;", // + "class Test extends C {", + " I i;", + "}"), + UTF_8); + + Path output = temporaryFolder.newFile("output.jar").toPath(); + + try { + Main.compile( + optionsWithBootclasspath() + .setOutput(output.toString()) + .setSources(ImmutableList.of(src.toString())) + .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) + .setClassPath(ImmutableList.of(libc.toString())) + .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) + .build()); + fail(); + } catch (TurbineError e) { + assertThat(e).hasMessageThat().contains("could not resolve I"); + } + } + + static String lines(String... lines) { + return Joiner.on(System.lineSeparator()).join(lines); + } +} diff --git a/javatests/com/google/turbine/model/ConstTest.java b/javatests/com/google/turbine/model/ConstTest.java index a64d0bf..984fd5a 100644 --- a/javatests/com/google/turbine/model/ConstTest.java +++ b/javatests/com/google/turbine/model/ConstTest.java @@ -16,12 +16,19 @@ package com.google.turbine.model; +import static com.google.common.truth.Truth.assertThat; + import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.testing.EqualsTester; -import com.google.turbine.binder.bound.AnnotationValue; +import com.google.turbine.binder.bound.EnumConstantValue; +import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.model.Const.ArrayInitValue; +import com.google.turbine.model.Const.IntValue; +import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type.ClassTy; import com.google.turbine.type.Type.PrimTy; import org.junit.Test; @@ -62,15 +69,31 @@ public class ConstTest { new Const.ArrayInitValue( ImmutableList.of(new Const.IntValue(3), new Const.IntValue(4)))) .addEqualityGroup( - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(3))), - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(3)))) + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(3)))), + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(3))))) .addEqualityGroup( - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(4))), - new AnnotationValue( - new ClassSymbol("test/Anno"), ImmutableMap.of("value", new Const.IntValue(4)))) + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(4)))), + new TurbineAnnotationValue( + new AnnoInfo( + null, + new ClassSymbol("test/Anno"), + null, + ImmutableMap.of("value", new Const.IntValue(4))))) .addEqualityGroup( new TurbineClassValue(ClassTy.asNonParametricClassTy(new ClassSymbol("test/Clazz"))), new TurbineClassValue(ClassTy.asNonParametricClassTy(new ClassSymbol("test/Clazz")))) @@ -82,4 +105,37 @@ public class ConstTest { new TurbineClassValue(PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()))) .testEquals(); } + + @Test + public void toStringTest() { + assertThat(new Const.CharValue('\t').toString()).isEqualTo("\'\\t\'"); + assertThat(new EnumConstantValue(new FieldSymbol(new ClassSymbol("Foo"), "CONST")).toString()) + .isEqualTo("CONST"); + assertThat(makeAnno(ImmutableMap.of())).isEqualTo("@p.Anno"); + assertThat(makeAnno(ImmutableMap.of("value", new IntValue(1)))).isEqualTo("@p.Anno(1)"); + assertThat(makeAnno(ImmutableMap.of("x", new IntValue(1)))).isEqualTo("@p.Anno(x=1)"); + assertThat( + makeAnno( + ImmutableMap.of("value", new ArrayInitValue(ImmutableList.of(new IntValue(1)))))) + .isEqualTo("@p.Anno({1})"); + assertThat( + makeAnno( + ImmutableMap.of( + "value", + new ArrayInitValue(ImmutableList.of(new IntValue(1), new IntValue(2)))))) + .isEqualTo("@p.Anno({1, 2})"); + assertThat( + makeAnno(ImmutableMap.of("xs", new ArrayInitValue(ImmutableList.of(new IntValue(1)))))) + .isEqualTo("@p.Anno(xs={1})"); + assertThat(makeAnno(ImmutableMap.of("x", new IntValue(1), "y", new IntValue(2)))) + .isEqualTo("@p.Anno(x=1, y=2)"); + assertThat(new Const.StringValue("\"").toString()).isEqualTo("\"\\\"\""); + assertThat(new Const.ByteValue((byte) 42).toString()).isEqualTo("(byte)0x2a"); + assertThat(new Const.ShortValue((short) 42).toString()).isEqualTo("42"); + } + + private static String makeAnno(ImmutableMap<String, Const> value) { + return new TurbineAnnotationValue(new AnnoInfo(null, new ClassSymbol("p/Anno"), null, value)) + .toString(); + } } diff --git a/javatests/com/google/turbine/options/TurbineOptionsTest.java b/javatests/com/google/turbine/options/TurbineOptionsTest.java index a5872d9..d4b468b 100644 --- a/javatests/com/google/turbine/options/TurbineOptionsTest.java +++ b/javatests/com/google/turbine/options/TurbineOptionsTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; +import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -95,7 +96,7 @@ public class TurbineOptionsTest { assertThat(options.outputDeps()).hasValue("out.jdeps"); assertThat(options.targetLabel()).hasValue("//java/com/google/test"); assertThat(options.injectingRuleKind()).hasValue("foo_library"); - assertThat(options.shouldReduceClassPath()).isTrue(); + assertThat(options.reducedClasspathMode()).isEqualTo(ReducedClasspathMode.NONE); } @Test @@ -261,13 +262,22 @@ public class TurbineOptionsTest { @Test public void javacopts() throws Exception { String[] lines = { - "--javacopts", "--release", "9", "--", "--sources", "Test.java", + "--javacopts", + "--release", + "8", + "--", + "--sources", + "Test.java", + "--javacopts", + "--release", + "9", + "--", }; TurbineOptions options = TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); - assertThat(options.javacOpts()).containsExactly("--release", "9").inOrder(); + assertThat(options.javacOpts()).containsExactly("--release", "8", "--release", "9").inOrder(); assertThat(options.sources()).containsExactly("Test.java"); } @@ -315,20 +325,14 @@ public class TurbineOptionsTest { } @Test - public void shouldReduceClasspath() throws Exception { - { - TurbineOptions options = - TurbineOptionsParser.parse( - Iterables.concat(BASE_ARGS, ImmutableList.of("--reduce_classpath"))); - assertThat(options.shouldReduceClassPath()).isTrue(); - } - - { - TurbineOptions options = - TurbineOptionsParser.parse( - Iterables.concat(BASE_ARGS, ImmutableList.of("--noreduce_classpath"))); - assertThat(options.shouldReduceClassPath()).isFalse(); - } + public void miscOutputs() throws Exception { + TurbineOptions options = + TurbineOptionsParser.parse( + Iterables.concat( + BASE_ARGS, + ImmutableList.of("--gensrc_output", "gensrc.jar", "--profile", "turbine.prof"))); + assertThat(options.gensrcOutput()).hasValue("gensrc.jar"); + assertThat(options.profile()).hasValue("turbine.prof"); } @Test @@ -350,4 +354,23 @@ public class TurbineOptionsTest { } catch (IllegalArgumentException expected) { } } + + @Test + public void builtinProcessors() throws Exception { + String[] lines = {"--builtin_processors", "BuiltinProcessor"}; + TurbineOptions options = + TurbineOptionsParser.parse(Iterables.concat(BASE_ARGS, Arrays.asList(lines))); + assertThat(options.builtinProcessors()).containsExactly("BuiltinProcessor"); + } + + @Test + public void reducedClasspathMode() throws Exception { + for (ReducedClasspathMode mode : ReducedClasspathMode.values()) { + TurbineOptions options = + TurbineOptionsParser.parse( + Iterables.concat( + BASE_ARGS, ImmutableList.of("--reduce_classpath_mode", mode.name()))); + assertThat(options.reducedClasspathMode()).isEqualTo(mode); + } + } } diff --git a/javatests/com/google/turbine/parse/CommentParserTest.java b/javatests/com/google/turbine/parse/CommentParserTest.java new file mode 100644 index 0000000..a2f84d5 --- /dev/null +++ b/javatests/com/google/turbine/parse/CommentParserTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Google Inc. 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.turbine.parse; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.turbine.tree.Tree; +import com.google.turbine.tree.Tree.MethDecl; +import com.google.turbine.tree.Tree.TyDecl; +import com.google.turbine.tree.Tree.VarDecl; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommentParserTest { + + @Test + public void comments() { + Tree.CompUnit unit = + Parser.parse( + Joiner.on('\n') + .join( + "package p;", + "/** hello world */", + "class Test {", + " /**", + " * This is", + " * class A", + " */", + " class A {", + " /** This is a method */", + " void f() {}", + " /** This is a field */", + " int g;", + " }", + " /* This is not javadoc */", + " class B {}", + " /**", + " * This is", + " * class C", + " */", + " class C {}", + "}\n")); + TyDecl decl = getOnlyElement(unit.decls()); + assertThat(decl.javadoc()).isEqualTo(" hello world "); + assertThat( + decl.members().stream() + .map(Tree.TyDecl.class::cast) + .filter(c -> c.javadoc() != null) + .collect(toImmutableMap(c -> c.name().value(), c -> c.javadoc()))) + .containsExactly( + "A", "\n * This is\n * class A\n ", + "C", "\n * This is\n * class C\n "); + TyDecl a = (TyDecl) decl.members().get(0); + MethDecl f = (MethDecl) a.members().get(0); + assertThat(f.javadoc()).isEqualTo(" This is a method "); + VarDecl g = (VarDecl) a.members().get(1); + assertThat(g.javadoc()).isEqualTo(" This is a field "); + } +} diff --git a/javatests/com/google/turbine/parse/JavacLexer.java b/javatests/com/google/turbine/parse/JavacLexer.java index af82dbe..d8939f1 100644 --- a/javatests/com/google/turbine/parse/JavacLexer.java +++ b/javatests/com/google/turbine/parse/JavacLexer.java @@ -280,8 +280,7 @@ public class JavacLexer { case CHARLITERAL: return String.format( "CHAR_LITERAL(%s)", SourceCodeEscapers.javaCharEscaper().escape(token.stringVal())); - default: - return token.kind.toString(); } + return token.kind.toString(); } } diff --git a/javatests/com/google/turbine/parse/LexerTest.java b/javatests/com/google/turbine/parse/LexerTest.java index 4867a22..8530d52 100644 --- a/javatests/com/google/turbine/parse/LexerTest.java +++ b/javatests/com/google/turbine/parse/LexerTest.java @@ -40,12 +40,6 @@ public class LexerTest { } @Test - public void unterminated() { - assertThat(lex("/* foo")).containsExactly("EOF"); - assertThat(lex("\" foo")).containsExactly("EOF"); - } - - @Test public void boolLiteral() { lexerComparisonTest("0b0101__01010"); assertThat(lex("1 + 0b1000100101")) diff --git a/javatests/com/google/turbine/parse/ParseErrorTest.java b/javatests/com/google/turbine/parse/ParseErrorTest.java index 49fb273..6a9ad11 100644 --- a/javatests/com/google/turbine/parse/ParseErrorTest.java +++ b/javatests/com/google/turbine/parse/ParseErrorTest.java @@ -228,6 +228,57 @@ public class ParseErrorTest { } } + @Test + public void abruptMultivariableDeclaration() { + String input = "class T { int x,; }"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: expected token <identifier>", // + "class T { int x,; }", + " ^")); + } + } + + @Test + public void invalidAnnotation() { + String input = "@Foo(x = @E [] x) class T {}"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: invalid annotation argument", // + "@Foo(x = @E [] x) class T {}", + " ^")); + } + } + + @Test + public void unclosedComment() { + String input = "/** *\u001a/ class Test {}"; + try { + Parser.parse(input); + fail("expected parsing to fail"); + } catch (TurbineError e) { + assertThat(e) + .hasMessageThat() + .isEqualTo( + lines( + "<>:1: error: unclosed comment", // + "/** *\u001a/ class Test {}", + "^")); + } + } + private static String lines(String... lines) { return Joiner.on(System.lineSeparator()).join(lines); } diff --git a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java index 2637091..e3f7b63 100644 --- a/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java +++ b/javatests/com/google/turbine/parse/UnicodeEscapePreprocessorTest.java @@ -16,10 +16,13 @@ package com.google.turbine.parse; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.diag.TurbineError.ErrorKind; import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -57,15 +60,15 @@ public class UnicodeEscapePreprocessorTest { try { readAll("\\u00"); fail(); - } catch (AssertionError e) { - assertThat(e).hasMessage("unexpected end of input"); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); } try { readAll("\\u"); fail(); - } catch (AssertionError e) { - assertThat(e).hasMessage("unexpected end of input"); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.UNEXPECTED_EOF); } } @@ -74,6 +77,16 @@ public class UnicodeEscapePreprocessorTest { assertThat(readAll("\\u005C\\\\u005C")).containsExactly('\\', '\\', '\\'); } + @Test + public void invalidEscape() { + try { + readAll("\\uUUUU"); + fail(); + } catch (TurbineError e) { + assertThat(getOnlyElement(e.diagnostics()).kind()).isEqualTo(ErrorKind.INVALID_UNICODE); + } + } + private List<Character> readAll(String input) { UnicodeEscapePreprocessor reader = new UnicodeEscapePreprocessor(new SourceFile(null, input)); List<Character> result = new ArrayList<>(); diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java new file mode 100644 index 0000000..6ea6e72 --- /dev/null +++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesBiPredicateTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.Truth.assertWithMessage; + +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +/** + * A combo test for {@link TurbineTypes} that compares the behaviour of bipredicates like {@link + * Types#isSubtype(TypeMirror, TypeMirror)} with javac's implementation. + */ +abstract class AbstractTurbineTypesBiPredicateTest extends AbstractTurbineTypesTest { + + final String testDescription; + final TypesBiFunctionInput javacInput; + final TypesBiFunctionInput turbineInput; + + public AbstractTurbineTypesBiPredicateTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + this.testDescription = testDescription; + this.javacInput = javacInput; + this.turbineInput = turbineInput; + } + + protected void test(String symbol, TypeBiPredicate predicate) { + assertWithMessage("%s = %s", javacInput.format(symbol), turbineInput.format(symbol)) + .that(turbineInput.apply(predicate)) + .isEqualTo(javacInput.apply(predicate)); + } +} diff --git a/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java new file mode 100644 index 0000000..e6a59bf --- /dev/null +++ b/javatests/com/google/turbine/processing/AbstractTurbineTypesTest.java @@ -0,0 +1,527 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Streams; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import com.sun.source.util.JavacTask; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.file.JavacFileManager; +import com.sun.tools.javac.util.Context; +import java.net.URI; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.ElementScanner8; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; + +class AbstractTurbineTypesTest { + + protected static class TypeParameters { + protected final Types javacTypes; + protected final List<List<TypeMirror>> aGroups; + protected final Types turbineTypes; + protected final List<List<TypeMirror>> bGroups; + + private TypeParameters( + Types javacTypes, + List<List<TypeMirror>> aGroups, + Types turbineTypes, + List<List<TypeMirror>> bGroups) { + this.javacTypes = javacTypes; + this.aGroups = aGroups; + this.turbineTypes = turbineTypes; + this.bGroups = bGroups; + } + } + + protected interface TypeBiPredicate { + boolean apply(Types types, TypeMirror a, TypeMirror b); + } + + static class TypesBiFunctionInput { + final Types types; + final TypeMirror lhs; + final TypeMirror rhs; + + TypesBiFunctionInput(Types types, TypeMirror lhs, TypeMirror rhs) { + this.types = types; + this.lhs = lhs; + this.rhs = rhs; + } + + boolean apply(TypeBiPredicate predicate) { + return predicate.apply(types, lhs, rhs); + } + + String format(String symbol) { + return String.format("`%s` %s `%s`", lhs, symbol, rhs); + } + } + + protected static Iterable<Object[]> binaryParameters() throws Exception { + TypeParameters typeParameters = typeParameters(); + List<Object[]> params = new ArrayList<>(); + for (int i = 0; i < typeParameters.aGroups.size(); i++) { + List<TypeMirror> ax = typeParameters.aGroups.get(i); + List<TypeMirror> bx = typeParameters.bGroups.get(i); + Streams.zip( + Lists.cartesianProduct(ax, ax).stream(), + Lists.cartesianProduct(bx, bx).stream(), + (a, b) -> + new Object[] { + a.get(0) + " " + a.get(1), + new TypesBiFunctionInput(typeParameters.javacTypes, a.get(0), a.get(1)), + new TypesBiFunctionInput(typeParameters.turbineTypes, b.get(0), b.get(1)), + }) + .forEachOrdered(params::add); + } + return params; + } + + protected static Iterable<Object[]> unaryParameters() throws Exception { + TypeParameters typeParameters = typeParameters(); + List<Object[]> params = new ArrayList<>(); + for (int i = 0; i < typeParameters.aGroups.size(); i++) { + Streams.zip( + typeParameters.aGroups.get(i).stream(), + typeParameters.bGroups.get(i).stream(), + (a, b) -> + new Object[] { + a.toString(), typeParameters.javacTypes, a, typeParameters.turbineTypes, b, + }) + .forEachOrdered(params::add); + } + return params; + } + + protected static TypeParameters typeParameters() throws Exception { + String[][] types = { + // generics + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "Set", + "ArrayList", + "Collection", + "List<Object>", + "List<Number>", + "List<Integer>", + "ArrayList<Object>", + "ArrayList<Number>", + "ArrayList<Integer>", + }, + // wildcards + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "List<?>", + "List<Object>", + "List<Number>", + "List<Integer>", + "List<? extends Object>", + "List<? extends Number>", + "List<? extends Integer>", + "List<? super Object>", + "List<? super Number>", + "List<? super Integer>", + }, + // arrays + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "Object[]", + "Number[]", + "List<Integer>[]", + "List<? extends Integer>[]", + "long[]", + "int[]", + "int[][]", + "Long[]", + "Integer[]", + "Integer[][]", + }, + // primitives + { + "Object", + "String", + "Cloneable", + "Serializable", + "List", + "int", + "char", + "byte", + "short", + "boolean", + "long", + "float", + "double", + "Integer", + "Character", + "Byte", + "Short", + "Boolean", + "Long", + "Float", + "Double", + }, + }; + List<String> files = new ArrayList<>(); + AtomicInteger idx = new AtomicInteger(); + for (String[] group : types) { + StringBuilder sb = new StringBuilder(); + Joiner.on('\n') + .appendTo( + sb, + "package p;", + "import java.util.*;", + "import java.io.*;", + String.format("abstract class Test%s {", idx.getAndIncrement()), + Streams.mapWithIndex( + Arrays.stream(group), (x, i) -> String.format(" %s f%d;\n", x, i)) + .collect(joining("\n")), + " abstract <T extends Serializable & List<T>> T f();", + " abstract <V extends List<V>> V g();", + " abstract <W extends ArrayList> W h();", + " abstract <X extends Serializable> X i();", + "}"); + String content = sb.toString(); + files.add(content); + } + // type hierarchies + files.add( + Joiner.on('\n') + .join( + "import java.util.*;", + "class Hierarchy {", // + " static class A<T> {", + " class I {}", + " }", + " static class D<T> extends A<T[]> {}", + " static class E<T> extends A<T> {", + " class J extends I {}", + " }", + " static class F<T> extends A {}", + " static class G<T> extends A<List<T>> {}", + " A rawA;", + " A<Object[]> a1;", + " A<Number[]> a2;", + " A<Integer[]> a3;", + " A<? super Object> a4;", + " A<? super Number> a5;", + " A<? super Integer> a6;", + " A<? extends Object> a7;", + " A<? extends Number> a8;", + " A<? extends Integer> a9;", + " A<List<Integer>> a10;", + " D<Object> d1;", + " D<Number> d2;", + " D<Integer> d3;", + " A<Object>.I i1;", + " A<Number>.I i2;", + " A<Integer>.I i3;", + " E<Object>.J j1;", + " E<Number>.J j2;", + " E<Integer>.J j3;", + " F<Integer> f1;", + " F<Number> f2;", + " F<Object> f3;", + " G<Integer> g1;", + " G<Number> g2;", + "}")); + // methods + files.add( + Joiner.on('\n') + .join( + "import java.io.*;", + "class Methods {", + " void f() {}", + " void g() {}", + " void f(int x) {}", + " void f(int x, int y) {}", + " abstract static class I {", + " abstract int f();", + " abstract void g() throws IOException;", + " abstract <T> void h();", + " abstract <T extends String> T i(T s);", + " }", + " abstract static class J {", + " abstract long f();", + " abstract void g();", + " abstract <T> void h();", + " abstract <T extends Number> T i(T s);", + " }", + " class K {", + " void f(K this, int x) {}", + " void g(K this) {}", + " <T extends Enum<T> & Serializable> void h(T t) {}", + " }", + " class L {", + " void f(int x) {}", + " void g() {}", + " <E extends Enum<E> & Serializable> void h(E t) {}", + " }", + "}", + "class M<T extends Enum<T> & Serializable> {", + " void h(T t) {}", + "}")); + + Context context = new Context(); + JavaFileManager fileManager = new JavacFileManager(context, true, UTF_8); + idx.set(0); + ImmutableList<SimpleJavaFileObject> compilationUnits = + files.stream() + .map( + x -> + new SimpleJavaFileObject( + URI.create("file://test" + idx.getAndIncrement() + ".java"), Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return x; + } + }) + .collect(toImmutableList()); + JavacTask task = + JavacTool.create() + .getTask( + /* out= */ null, + fileManager, + /* diagnosticListener= */ null, + /* options= */ ImmutableList.of(), + /* classes= */ ImmutableList.of(), + compilationUnits); + + Types javacTypes = task.getTypes(); + ImmutableMap<String, Element> javacElements = + Streams.stream(task.analyze()) + .collect(toImmutableMap(e -> e.getSimpleName().toString(), x -> x)); + + ImmutableList<CompUnit> units = files.stream().map(Parser::parse).collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + Types turbineTypes = new TurbineTypes(factory); + ImmutableMap<String, Element> turbineElements = + bound.units().keySet().stream() + .filter(x -> !x.binaryName().contains("$")) // only top level classes + .collect(toImmutableMap(x -> x.simpleName(), factory::element)); + + assertThat(javacElements.keySet()).containsExactlyElementsIn(turbineElements.keySet()); + + List<List<TypeMirror>> aGroups = new ArrayList<>(); + List<List<TypeMirror>> bGroups = new ArrayList<>(); + for (String name : javacElements.keySet()) { + + List<TypeMirror> aGroup = new ArrayList<>(); + List<TypeMirror> bGroup = new ArrayList<>(); + + ListMultimap<String, TypeMirror> javacInputs = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + javacElements + .get(name) + .getEnclosedElements() + .forEach(e -> getTypes(javacTypes, e, javacInputs)); + + ListMultimap<String, TypeMirror> turbineInputs = + MultimapBuilder.linkedHashKeys().arrayListValues().build(); + turbineElements + .get(name) + .getEnclosedElements() + .forEach(e -> getTypes(turbineTypes, e, turbineInputs)); + + assertThat(turbineInputs.keySet()).containsExactlyElementsIn(javacInputs.keySet()); + + for (String key : javacInputs.keySet()) { + List<TypeMirror> a = javacInputs.get(key); + List<TypeMirror> b = turbineInputs.get(key); + assertWithMessage(key) + .that(b.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList())) + .containsExactlyElementsIn( + a.stream().map(x -> x.getKind() + " " + x).collect(toImmutableList())) + .inOrder(); + aGroup.addAll(a); + bGroup.addAll(b); + } + aGroups.add(aGroup); + bGroups.add(bGroup); + } + return new TypeParameters(javacTypes, aGroups, turbineTypes, bGroups); + } + + /** + * Discover all types contained in the given element, keyed by their immediate enclosing element. + */ + private static void getTypes( + Types typeUtils, Element element, Multimap<String, TypeMirror> types) { + element.accept( + new ElementScanner8<Void, Void>() { + + /** + * Returns an element name qualified by all enclosing elements, to allow comparison + * between javac and turbine's implementation to group related types. + */ + String key(Element e) { + Deque<String> flat = new ArrayDeque<>(); + while (e != null) { + flat.addFirst(e.getSimpleName().toString()); + if (e.getKind() == ElementKind.PACKAGE) { + break; + } + e = e.getEnclosingElement(); + } + return Joiner.on('.').join(flat); + } + + void addType(Element e, TypeMirror t) { + if (t != null) { + types.put(key(e), t); + t.accept( + new SimpleTypeVisitor8<Void, Void>() { + @Override + public Void visitDeclared(DeclaredType t, Void aVoid) { + for (TypeMirror a : t.getTypeArguments()) { + a.accept(this, null); + } + return null; + } + + @Override + public Void visitWildcard(WildcardType t, Void aVoid) { + types.put(key(e), t); + return null; + } + + @Override + public Void visitTypeVariable(TypeVariable t, Void aVoid) { + if (t.getUpperBound() != null) { + types.put(key(e), t.getUpperBound()); + } + return null; + } + }, + null); + } + } + + void addType(Element e, List<? extends TypeMirror> types) { + for (TypeMirror type : types) { + addType(e, type); + } + } + + @Override + public Void visitVariable(VariableElement e, Void unused) { + if (e.getSimpleName().toString().contains("this$")) { + // enclosing instance parameters + return null; + } + addType(e, e.asType()); + return super.visitVariable(e, null); + } + + @Override + public Void visitType(TypeElement e, Void unused) { + addType(e, e.asType()); + return super.visitType(e, null); + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + scan(e.getTypeParameters(), null); + scan(e.getParameters(), null); + addType(e, e.asType()); + addType(e, e.getReturnType()); + TypeMirror receiverType = e.getReceiverType(); + if (receiverType == null) { + // work around a javac bug in JDK < 14, see: + // https://bugs.openjdk.java.net/browse/JDK-8222369 + receiverType = typeUtils.getNoType(TypeKind.NONE); + } + addType(e, receiverType); + addType(e, e.getThrownTypes()); + return null; + } + + @Override + public Void visitTypeParameter(TypeParameterElement e, Void unused) { + addType(e, e.asType()); + addType(e, e.getBounds()); + return null; + } + }, + null); + } +} diff --git a/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java new file mode 100644 index 0000000..ed5af6a --- /dev/null +++ b/javatests/com/google/turbine/processing/ProcessingIntegrationTest.java @@ -0,0 +1,346 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.Processing; +import com.google.turbine.binder.Processing.ProcessorInfo; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.io.Writer; +import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProcessingIntegrationTest { + + @SupportedAnnotationTypes("*") + public static class CrashingProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + throw new RuntimeException("crash!"); + } + } + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test extends NoSuch {", + "}")); + + @Test + public void crash() throws IOException { + ImmutableList<Tree.CompUnit> units = + SOURCES.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new CrashingProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + fail(); + } catch (TurbineError e) { + assertThat(e.diagnostics()).hasSize(2); + assertThat(e.diagnostics().get(0).message()).contains("could not resolve NoSuch"); + assertThat(e.diagnostics().get(1).message()).contains("crash!"); + } + } + + @SupportedAnnotationTypes("*") + public static class WarningProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "proc warning"); + try { + JavaFileObject file = processingEnv.getFiler().createSourceFile("Gen.java"); + try (Writer writer = file.openWriter()) { + writer.write("class Gen {}"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + if (roundEnv.processingOver()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "proc error"); + } + return false; + } + } + + @Test + public void warnings() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new WarningProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + fail(); + } catch (TurbineError e) { + ImmutableList<String> diags = + e.diagnostics().stream().map(d -> d.message()).collect(toImmutableList()); + assertThat(diags).hasSize(2); + assertThat(diags.get(0)).contains("proc warning"); + assertThat(diags.get(1)).contains("proc error"); + } + } + + @SupportedAnnotationTypes("*") + public static class ResourceProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try { + try (Writer writer = processingEnv.getFiler().createSourceFile("Gen").openWriter()) { + writer.write("class Gen {}"); + } + try (Writer writer = + processingEnv + .getFiler() + .createResource(StandardLocation.SOURCE_OUTPUT, "", "source.txt") + .openWriter()) { + writer.write("hello source output"); + } + try (Writer writer = + processingEnv + .getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "class.txt") + .openWriter()) { + writer.write("hello class output"); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + } + + @Test + public void resources() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "@Deprecated", + "class Test {", + "}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new ResourceProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + assertThat(bound.generatedSources().keySet()).containsExactly("Gen.java", "source.txt"); + assertThat(bound.generatedClasses().keySet()).containsExactly("class.txt"); + + assertThat(bound.generatedSources().get("source.txt").source()) + .isEqualTo("hello source output"); + assertThat(new String(bound.generatedClasses().get("class.txt"), UTF_8)) + .isEqualTo("hello class output"); + } + + @Test + public void getAllAnnotations() throws IOException { + ImmutableList<Tree.CompUnit> units = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== A.java ===", // + "import java.lang.annotation.Inherited;", + "@Inherited", + "@interface A {}", + "=== B.java ===", // + "@interface B {}", + "=== One.java ===", // + "@A @B class One {}", + "=== Two.java ===", // + "class Two extends One {}")) + .sources + .entrySet() + .stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + ProcessorInfo.create( + ImmutableList.of(new ElementsAnnotatedWithProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + assertThat( + Splitter.on(System.lineSeparator()) + .omitEmptyStrings() + .split( + new String( + bound.generatedClasses().entrySet().stream() + .filter(s -> s.getKey().equals("output.txt")) + .collect(onlyElement()) + .getValue(), + UTF_8))) + .containsExactly("A: One, Two", "B: One"); + } + + @SupportedAnnotationTypes("*") + private static class ElementsAnnotatedWithProcessor extends AbstractProcessor { + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + private boolean first = true; + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + if (first) { + try (PrintWriter writer = + new PrintWriter( + processingEnv + .getFiler() + .createResource(StandardLocation.CLASS_OUTPUT, "", "output.txt") + .openWriter(), + /* autoFlush= */ true)) { + printAnnotatedElements(roundEnv, writer, "A"); + printAnnotatedElements(roundEnv, writer, "B"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + first = false; + } + return false; + } + + private void printAnnotatedElements( + RoundEnvironment roundEnv, PrintWriter writer, String annotation) { + writer.println( + annotation + + ": " + + roundEnv + .getElementsAnnotatedWith( + processingEnv.getElementUtils().getTypeElement(annotation)) + .stream() + .map(e -> e.getSimpleName().toString()) + .collect(joining(", "))); + } + } +} diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java new file mode 100644 index 0000000..a049860 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineAnnotationMirrorTest.java @@ -0,0 +1,249 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multimaps; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.testing.TestClassPaths; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.AbstractAnnotationValueVisitor8; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineAnnotationMirrorTest { + + private AnnotationMirror getAnnotation( + List<? extends AnnotationMirror> annotationMirrors, String name) { + return annotationMirrors.stream() + .filter(x -> x.getAnnotationType().asElement().getSimpleName().contentEquals(name)) + .findFirst() + .get(); + } + + private ImmutableMap<String, Object> values(AnnotationMirror a) { + return values(a.getElementValues()); + } + + /** + * Returns a map from the name of annotation elements to their values, see also {@link + * #getValue(AnnotationValue)}. + */ + private ImmutableMap<String, Object> values( + Map<? extends ExecutableElement, ? extends AnnotationValue> values) { + return values.entrySet().stream() + .collect( + toImmutableMap( + e -> e.getKey().getSimpleName().toString(), e -> getValue(e.getValue()))); + } + + /** + * Returns the given annotation value as an Object (for primitives), or a list (for arrays), or + * strings (for compound annotations, enums, and class literals). + */ + static Object getValue(AnnotationValue value) { + return value.accept( + new AbstractAnnotationValueVisitor8<Object, Void>() { + @Override + public Object visitBoolean(boolean b, Void unused) { + return b; + } + + @Override + public Object visitByte(byte b, Void unused) { + return b; + } + + @Override + public Object visitChar(char c, Void unused) { + return c; + } + + @Override + public Object visitDouble(double d, Void unused) { + return d; + } + + @Override + public Object visitFloat(float f, Void unused) { + return f; + } + + @Override + public Object visitInt(int i, Void unused) { + return i; + } + + @Override + public Object visitLong(long i, Void unused) { + return i; + } + + @Override + public Object visitShort(short s, Void unused) { + return s; + } + + @Override + public Object visitString(String s, Void unused) { + return s; + } + + @Override + public Object visitType(TypeMirror t, Void unused) { + return value.toString(); + } + + @Override + public Object visitEnumConstant(VariableElement c, Void unused) { + return value.toString(); + } + + @Override + public Object visitAnnotation(AnnotationMirror a, Void unused) { + return value.toString(); + } + + @Override + public Object visitArray(List<? extends AnnotationValue> vals, Void unused) { + return vals.stream().map(v -> v.accept(this, null)).collect(toImmutableList()); + } + }, + null); + } + + private static Stream<String> typeAnnotationNames(Element e) { + return e.asType().getAnnotationMirrors().stream() + .map(anno -> anno.getAnnotationType().asElement().getSimpleName().toString()); + } + + @Test + public void test() throws Exception { + TestInput input = + TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "import java.lang.annotation.ElementType;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "import java.lang.annotation.Target;", + "import java.util.Map;", + "import java.util.Map.Entry;", + "@Retention(RetentionPolicy.RUNTIME)", + "@interface A {", + " int x() default 0;", + " int y() default 1;", + " int[] z() default {};", + "}", + "@interface B {", + " Class<?> c() default String.class;", + " ElementType e() default ElementType.TYPE_USE;", + " A f() default @A;", + "}", + "@Retention(RetentionPolicy.RUNTIME)", + "@Target(ElementType.TYPE_USE)", + "@interface T {}", + "@Target(ElementType.TYPE_USE)", + "@interface V {}", + "", + "@A(y = 42, z = {43})", + "@B", + "class Test {", + " class I {}", + " @T Test. @V I f;", + " Map. @T Entry g;", + " @T Entry h;", + "}", + "")); + + Binder.BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + input.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + TurbineElements turbineElements = new TurbineElements(factory, turbineTypes); + + TurbineTypeElement te = factory.typeElement(new ClassSymbol("Test")); + + AnnotationMirror a = getAnnotation(te.getAnnotationMirrors(), "A"); + ((TypeElement) a.getAnnotationType().asElement()).getQualifiedName().contentEquals("A"); + assertThat(values(a)).containsExactly("y", 42, "z", ImmutableList.of(43)); + assertThat(values(turbineElements.getElementValuesWithDefaults(a))) + .containsExactly( + "x", 0, + "y", 42, + "z", ImmutableList.of(43)); + + AnnotationMirror b = getAnnotation(te.getAnnotationMirrors(), "B"); + assertThat(values(turbineElements.getElementValuesWithDefaults(b))) + .containsExactly( + "c", "java.lang.String.class", + "e", "java.lang.annotation.ElementType.TYPE_USE", + "f", "@A"); + + ListMultimap<String, String> fieldTypeAnnotations = + te.getEnclosedElements().stream() + .filter(e -> e.getKind().equals(ElementKind.FIELD)) + .collect( + Multimaps.flatteningToMultimap( + e -> e.getSimpleName().toString(), + e -> typeAnnotationNames(e), + MultimapBuilder.linkedHashKeys().arrayListValues()::build)); + assertThat(fieldTypeAnnotations) + .containsExactly( + "f", "V", + "g", "T", + "h", "T"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java new file mode 100644 index 0000000..d339700 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineAnnotationProxyTest.java @@ -0,0 +1,219 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Ints; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.parse.Parser; +import com.google.turbine.processing.TurbineElement.TurbineTypeElement; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineAnnotationProxyTest { + + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Retention(RetentionPolicy.RUNTIME) + public @interface A { + B b() default @B(-1); + + ElementType e() default ElementType.PACKAGE; + + int[] xs() default {}; + + Class<?> c() default String.class; + + Class<?>[] cx() default {}; + } + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + public @interface B { + int value(); + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface C {} + + @Retention(RetentionPolicy.RUNTIME) + public @interface RS { + R[] value() default {}; + } + + @Repeatable(RS.class) + @Retention(RetentionPolicy.RUNTIME) + public @interface R { + int value() default 1; + } + + @A + static class I {} + + @Test + public void test() throws IOException { + + Path lib = temporaryFolder.newFile("lib.jar").toPath(); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { + addClass(jos, TurbineAnnotationProxyTest.class); + addClass(jos, A.class); + addClass(jos, B.class); + addClass(jos, C.class); + addClass(jos, R.class); + } + + TestInput input = + TestInput.parse( + Joiner.on('\n') + .join( + "=== Super.java ===", + "import " + B.class.getCanonicalName() + ";", + "import " + C.class.getCanonicalName() + ";", + "@B(42)", + "@C", + "class Super {}", + "=== Test.java ===", + "import " + A.class.getCanonicalName() + ";", + "import " + R.class.getCanonicalName() + ";", + "@A(xs = {1,2,3}, cx = {Integer.class, Long.class})", + "@R(1)", + "@R(2)", + "@R(3)", + "class Test extends Super {}", + "")); + + ImmutableList<CompUnit> units = + input.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + + Binder.BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of(lib)), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypeElement te = factory.typeElement(new ClassSymbol("Test")); + + A a = te.getAnnotation(A.class); + B b = te.getAnnotation(B.class); + assertThat(te.getAnnotation(C.class)).isNull(); + + assertThat(a.b().value()).isEqualTo(-1); + assertThat(a.e()).isEqualTo(ElementType.PACKAGE); + try { + a.c(); + fail(); + } catch (MirroredTypeException e) { + assertThat(e.getTypeMirror().getKind()).isEqualTo(TypeKind.DECLARED); + assertThat(getQualifiedName(e.getTypeMirror())).contains("java.lang.String"); + } + try { + a.cx(); + fail(); + } catch (MirroredTypesException e) { + assertThat( + e.getTypeMirrors().stream().map(m -> getQualifiedName(m)).collect(toImmutableList())) + .containsExactly("java.lang.Integer", "java.lang.Long"); + } + assertThat(Ints.asList(a.xs())).containsExactly(1, 2, 3).inOrder(); + assertThat(a.annotationType()).isEqualTo(A.class); + + assertThat(b.value()).isEqualTo(42); + + RS container = te.getAnnotation(RS.class); + assertThat(container.value()).hasLength(3); + R[] rs = te.getAnnotationsByType(R.class); + assertThat(rs).hasLength(3); + assertThat(Arrays.toString(rs)) + .isEqualTo( + String.format( + "[@%s(1), @%s(2), @%s(3)]", + R.class.getCanonicalName(), + R.class.getCanonicalName(), + R.class.getCanonicalName())); + + new EqualsTester() + .addEqualityGroup(a, te.getAnnotation(A.class)) + .addEqualityGroup(b, te.getAnnotation(B.class)) + .addEqualityGroup(rs[0]) + .addEqualityGroup(rs[1]) + .addEqualityGroup(rs[2]) + .addEqualityGroup(container) + .addEqualityGroup(I.class.getAnnotation(A.class)) + .addEqualityGroup("unrelated") + .testEquals(); + } + + private static void addClass(JarOutputStream jos, Class<?> clazz) throws IOException { + String entryPath = clazz.getName().replace('.', '/') + ".class"; + jos.putNextEntry(new JarEntry(entryPath)); + try (InputStream is = clazz.getClassLoader().getResourceAsStream(entryPath)) { + ByteStreams.copy(is, jos); + } + } + + private static String getQualifiedName(TypeMirror typeMirror) { + return ((TypeElement) ((DeclaredType) typeMirror).asElement()).getQualifiedName().toString(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementTest.java b/javatests/com/google/turbine/processing/TurbineElementTest.java new file mode 100644 index 0000000..0b3448f --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementTest.java @@ -0,0 +1,234 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.MoreCollectors; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.FieldSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type.ClassTy; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.NestingKind; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.TypeParameterElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineElementTest { + + private final ModelFactory factory = + new ModelFactory( + TestClassPaths.TURBINE_BOOTCLASSPATH.env(), + ClassLoader.getSystemClassLoader(), + TestClassPaths.TURBINE_BOOTCLASSPATH.index()); + + @Test + public void typeElement() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/Map$Entry")); + TypeElement m = (TypeElement) e.getEnclosingElement(); + TypeMirror t = e.asType(); + + assertThat(e.getSimpleName().toString()).isEqualTo("Entry"); + assertThat(e.getQualifiedName().toString()).isEqualTo("java.util.Map.Entry"); + assertThat(e.toString()).isEqualTo("java.util.Map.Entry"); + assertThat(e.asType().toString()).isEqualTo("java.util.Map.Entry<K,V>"); + assertThat(e.getKind()).isEqualTo(ElementKind.INTERFACE); + assertThat(e.getNestingKind()).isEqualTo(NestingKind.MEMBER); + assertThat(e.getModifiers()) + .containsExactly(Modifier.PUBLIC, Modifier.ABSTRACT, Modifier.STATIC); + + assertThat(m.getSimpleName().toString()).isEqualTo("Map"); + assertThat(m.getSuperclass().getKind()).isEqualTo(TypeKind.NONE); + assertThat(m.getQualifiedName().toString()).isEqualTo("java.util.Map"); + assertThat(m.toString()).isEqualTo("java.util.Map"); + assertThat(m.asType().toString()).isEqualTo("java.util.Map<K,V>"); + assertThat(m.getNestingKind()).isEqualTo(NestingKind.TOP_LEVEL); + assertThat(m.getSuperclass().getKind()).isEqualTo(TypeKind.NONE); + assertThat(m.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + + assertThat(t.getKind()).isEqualTo(TypeKind.DECLARED); + } + + @Test + public void superClass() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat( + ((TypeElement) ((DeclaredType) e.getSuperclass()).asElement()) + .getQualifiedName() + .toString()) + .isEqualTo("java.util.AbstractMap"); + + e = factory.typeElement(new ClassSymbol("java/lang/annotation/ElementType")); + assertThat( + ((TypeElement) ((DeclaredType) e.getSuperclass()).asElement()) + .getQualifiedName() + .toString()) + .isEqualTo("java.lang.Enum"); + } + + @Test + public void interfaces() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat( + e.getInterfaces().stream() + .map( + i -> + ((TypeElement) ((DeclaredType) i).asElement()) + .getQualifiedName() + .toString()) + .collect(toImmutableList())) + .contains("java.util.Map"); + } + + @Test + public void typeParameters() { + TypeElement e = factory.typeElement(new ClassSymbol("java/util/HashMap")); + assertThat(e.getTypeParameters().stream().map(Object::toString).collect(toImmutableList())) + .containsExactly("K", "V"); + for (TypeParameterElement t : e.getTypeParameters()) { + assertThat(t.getGenericElement()).isEqualTo(e); + assertThat(t.getEnclosingElement()).isEqualTo(e); + assertThat(t.getBounds()).containsExactly(factory.asTypeMirror(ClassTy.OBJECT)); + } + } + + @Test + public void enclosed() { + assertThat( + factory.typeElement(new ClassSymbol("java/lang/Integer")).getEnclosedElements().stream() + .map(e -> e.getKind() + " " + e) + .collect(toImmutableList())) + .containsAtLeast("METHOD parseInt(java.lang.String)", "FIELD MAX_VALUE"); + } + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/List")), + factory.typeElement(new ClassSymbol("java/util/List"))) + .addEqualityGroup(factory.typeElement(new ClassSymbol("java/util/ArrayList"))) + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/Map")).getTypeParameters().get(0), + factory.typeElement(new ClassSymbol("java/util/Map")).getTypeParameters().get(0)) + .addEqualityGroup( + factory.typeElement(new ClassSymbol("java/util/ArrayList")).getTypeParameters().get(0)) + .addEqualityGroup( + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "elementData")), + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "elementData"))) + .addEqualityGroup( + factory.fieldElement( + new FieldSymbol(new ClassSymbol("java/util/ArrayList"), "serialVersionUID"))) + .addEqualityGroup( + ((ExecutableElement) + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter( + e -> + e.getKind().equals(ElementKind.METHOD) + && e.getSimpleName().contentEquals("add")) + .skip(1) + .findFirst() + .get()) + .getParameters() + .get(0)) + .addEqualityGroup( + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .skip(1) + .findFirst() + .get()) + .addEqualityGroup( + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .findFirst() + .get(), + factory + .typeElement(new ClassSymbol("java/util/ArrayList")) + .getEnclosedElements() + .stream() + .filter(e -> e.getKind().equals(ElementKind.METHOD)) + .findFirst() + .get()) + .addEqualityGroup( + factory.packageElement(new PackageSymbol("java/util")), + factory.typeElement(new ClassSymbol("java/util/ArrayList")).getEnclosingElement()) + .addEqualityGroup(factory.packageElement(new PackageSymbol("java/lang"))) + .testEquals(); + } + + @Test + public void noElement() { + PackageElement p = factory.packageElement(new PackageSymbol("java/lang")); + assertThat(p.getEnclosingElement()).isNull(); + } + + @Test + public void objectSuper() { + assertThat(factory.typeElement(new ClassSymbol("java/lang/Object")).getSuperclass().getKind()) + .isEqualTo(TypeKind.NONE); + } + + @Test + public void typeKind() { + assertThat(factory.typeElement(new ClassSymbol("java/lang/annotation/Target")).getKind()) + .isEqualTo(ElementKind.ANNOTATION_TYPE); + assertThat(factory.typeElement(new ClassSymbol("java/lang/annotation/ElementType")).getKind()) + .isEqualTo(ElementKind.ENUM); + } + + @Test + public void parameter() { + ExecutableElement equals = + (ExecutableElement) + factory.typeElement(new ClassSymbol("java/lang/Object")).getEnclosedElements().stream() + .filter(e -> e.getSimpleName().contentEquals("equals")) + .collect(MoreCollectors.onlyElement()); + VariableElement parameter = getOnlyElement(equals.getParameters()); + assertThat(parameter.getKind()).isEqualTo(ElementKind.PARAMETER); + assertThat(parameter.asType().toString()).isEqualTo("java.lang.Object"); + assertThat(parameter.getModifiers()).isEmpty(); + assertThat(parameter.getEnclosedElements()).isEmpty(); + assertThat(parameter.getSimpleName().toString()).isNotEmpty(); + assertThat(parameter.getConstantValue()).isNull(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java b/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java new file mode 100644 index 0000000..11dedbf --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementsGetAllMembersTest.java @@ -0,0 +1,293 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.requireNonNull; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.lower.IntegrationTestSupport.TestInput; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree.CompUnit; +import com.sun.source.util.JavacTask; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.util.Elements; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineElementsGetAllMembersTest { + + @Parameters + public static Iterable<Object[]> parameters() { + // An array of test inputs. Each element is an array of lines of sources to compile. + String[][] inputs = { + { + "=== Test.java ===", // + "class Test {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Integer f();", + "}", + "=== Test.java ===", // + "class Test implements A, B {", + " Integer f() {", + " return 42;", + " }", + "}", + }, + { + "=== I.java ===", + "abstract class I {", + " abstract Integer f();", + "}", + "=== J.java ===", + "interface J extends I {", + " default Integer f() {", + " return 42;", + " }", + "}", + "=== Test.java ===", // + "class Test extends I implements J {", + "}", + }, + { + "=== I.java ===", + "interface I {", + " Integer f();", + "}", + "=== J.java ===", + "interface J extends I {", + " default Integer f() {", + " return 42;", + " }", + "}", + "=== Test.java ===", // + "class Test implements J, I {", + "}", + }, + { + "=== p/A.java ===", + "package p;", + "public class A {", + " public boolean f() {", + " return true;", + " }", + "}", + "=== p/B.java ===", + "package p;", + "public interface B {", + " public boolean f();", + "}", + "=== Test.java ===", // + "import p.*;", + "class Test extends A implements B {", + "}", + }, + { + "=== p/A.java ===", + "package p;", + "public class A {", + " public boolean f() {", + " return true;", + " }", + "}", + "=== p/B.java ===", + "package p;", + "public interface B {", + " public boolean f();", + "}", + "=== Middle.java ===", // + "import p.*;", + "public abstract class Middle extends A implements B {", + "}", + "=== Test.java ===", // + "class Test extends Middle {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Number f();", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== A.java ===", + "interface A {", + " Integer f();", + "}", + "=== B.java ===", + "interface B {", + " Integer f();", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== I.java ===", + "interface I {", + " int x;", + "}", + "=== J.java ===", + "interface J {", + " int x;", + "}", + "=== B.java ===", + "class B {", + " int x;", + "}", + "=== C.java ===", + "class C extends B {", + " static int x;", + "}", + "=== Test.java ===", + "class Test extends C implements I, J {", + " int x;", + "}", + }, + { + "=== one/A.java ===", + "public class A {", + " int a;", + "}", + "=== two/B.java ===", + "public class B extends A {", + " int b;", + " private int c;", + " protected int d;", + "}", + "=== Test.java ===", + "public class Test extends B {", + " int x;", + "}", + }, + { + "=== A.java ===", + "interface A {", + " class I {}", + "}", + "=== B.java ===", + "interface B {", + " class J {}", + "}", + "=== Test.java ===", // + "abstract class Test implements A, B {", + "}", + }, + { + "=== A.java ===", + "import java.util.List;", + "interface A<T> {", + " List<? extends T> f();", + "}", + "=== Test.java ===", + "import java.util.List;", + "class Test<T extends Number> implements A<T> {", + " public List<? extends T> f() {", + " return null;", + " }", + "}", + }, + }; + return Arrays.stream(inputs) + .map(input -> TestInput.parse(Joiner.on('\n').join(input))) + .map(x -> new Object[] {x}) + .collect(toImmutableList()); + } + + private final TestInput input; + + public TurbineElementsGetAllMembersTest(TestInput input) { + this.input = input; + } + + // Compile the test inputs with javac and turbine, and assert that getAllMembers returns the + // same elements under each implementation. + @Test + public void test() throws Exception { + JavacTask javacTask = + IntegrationTestSupport.runJavacAnalysis( + input.sources, ImmutableList.of(), ImmutableList.of()); + Elements javacElements = javacTask.getElements(); + List<? extends Element> javacMembers = + javacElements.getAllMembers(requireNonNull(javacElements.getTypeElement("Test"))); + + ImmutableList<CompUnit> units = + input.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + + Binder.BindingResult bound = + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + TurbineElements turbineElements = new TurbineElements(factory, turbineTypes); + List<? extends Element> turbineMembers = + turbineElements.getAllMembers(factory.typeElement(new ClassSymbol("Test"))); + + assertThat(formatElements(turbineMembers)) + .containsExactlyElementsIn(formatElements(javacMembers)); + } + + private static ImmutableList<String> formatElements(Collection<? extends Element> elements) { + return elements.stream() + .map(e -> String.format("%s %s.%s %s", e.getKind(), e.getEnclosingElement(), e, e.asType())) + .collect(toImmutableList()); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineElementsTest.java b/javatests/com/google/turbine/processing/TurbineElementsTest.java new file mode 100644 index 0000000..770e6f6 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineElementsTest.java @@ -0,0 +1,353 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.testing.TestClassPaths; +import com.sun.source.util.JavacTask; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineElementsTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "@Deprecated", + "@A class Test extends One {}", + "=== One.java ===", + "/** javadoc", + " * for", + " * one", + " */", + "@B class One extends Two {", + " /** method javadoc */", + " void f() {}", + " /** field javadoc */", + " int x;", + "}", + "=== Two.java ===", + "/** javadoc", + " for", + " two with extra *", + " */", + "@C(1) class Two extends Three {}", + "=== Three.java ===", + "@C(2) class Three extends Four {}", + "=== Four.java ===", + "@D class Four {}", + "=== Annotations.java ===", + "import java.lang.annotation.Inherited;", + "@interface A {}", + "@interface B {}", + "@Inherited", + "@interface C {", + " int value() default 42;", + "}", + "@Inherited", + "@interface D {}", + "=== com/pkg/P.java ===", + "package com.pkg;", + "@interface P {}", + "=== com/pkg/package-info.java ===", + "@P", + "package com.pkg;", + "=== Const.java ===", + "class Const {", + " static final int X = 1867;", + "}", + "=== com/pkg/empty/package-info.java ===", + "@P", + "package com.pkg.empty;", + "import com.pkg.P;", + "=== com/pkg/A.java ===", + "package com.pkg;", + "class A {", + " class I {}", + "}", + "=== com/pkg/B.java ===", + "package com.pkg;", + "class B {}")); + + Elements javacElements; + ModelFactory factory; + TurbineElements turbineElements; + + @Before + public void setup() throws Exception { + JavacTask task = + IntegrationTestSupport.runJavacAnalysis( + SOURCES.sources, ImmutableList.of(), ImmutableList.of()); + task.analyze(); + javacElements = task.getElements(); + + BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + SOURCES.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + factory = new ModelFactory(env, TurbineElementsTest.class.getClassLoader(), bound.tli()); + TurbineTypes turbineTypes = new TurbineTypes(factory); + turbineElements = new TurbineElements(factory, turbineTypes); + } + + @Test + public void constants() { + for (Object value : + Arrays.asList( + Short.valueOf((short) 1), + Short.MIN_VALUE, + Short.MAX_VALUE, + Byte.valueOf((byte) 1), + Byte.MIN_VALUE, + Byte.MAX_VALUE, + Integer.valueOf(1), + Integer.MIN_VALUE, + Integer.MAX_VALUE, + Long.valueOf(1), + Long.MIN_VALUE, + Long.MAX_VALUE, + Float.valueOf(1), + Float.NaN, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + Float.MAX_VALUE, + Float.MIN_VALUE, + Double.valueOf(1), + Double.NaN, + Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.MAX_VALUE, + Double.MIN_VALUE, + 'a', + '\n', + "hello", + "\"hello\n\"")) { + assertThat(turbineElements.getConstantExpression(value)) + .isEqualTo(javacElements.getConstantExpression(value)); + } + } + + @Test + public void getName() { + Name n = turbineElements.getName("hello"); + assertThat(n.contentEquals("hello")).isTrue(); + assertThat(n.contentEquals("goodbye")).isFalse(); + + assertThat(n.toString()).isEqualTo("hello"); + assertThat(n.toString()) + .isEqualTo(new String(new char[] {'h', 'e', 'l', 'l', 'o'})); // defeat interning + + assertThat(n.length()).isEqualTo(5); + + new EqualsTester() + .addEqualityGroup(turbineElements.getName("hello"), turbineElements.getName("hello")) + .addEqualityGroup(turbineElements.getName("goodbye")) + .testEquals(); + } + + @Test + public void getAllAnnotationMirrors() { + assertThat( + toStrings( + turbineElements.getAllAnnotationMirrors( + factory.typeElement(new ClassSymbol("Test"))))) + .containsExactly("@java.lang.Deprecated", "@A", "@C(1)", "@D"); + } + + @Test + public void getTypeElement() { + for (String name : Arrays.asList("java.util.Map", "java.util.Map.Entry")) { + assertThat(turbineElements.getTypeElement(name).getQualifiedName().toString()) + .isEqualTo(name); + } + assertThat(turbineElements.getTypeElement("NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.Object.NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.NoSuch")).isNull(); + assertThat(turbineElements.getTypeElement("java.lang.Integer.MAX_VALUE")).isNull(); + } + + private static ImmutableList<String> toStrings(List<?> inputs) { + return inputs.stream().map(String::valueOf).collect(toImmutableList()); + } + + @Test + public void isDeprecated() { + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("java.lang.Object"))) + .isFalse(); + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("One"))).isFalse(); + assertThat(turbineElements.isDeprecated(turbineElements.getTypeElement("Test"))).isTrue(); + } + + @Test + public void getBinaryName() { + assertThat( + turbineElements + .getBinaryName(turbineElements.getTypeElement("java.util.Map.Entry")) + .toString()) + .isEqualTo("java.util.Map$Entry"); + } + + @Test + public void methodDefaultTest() { + assertThat( + ((ExecutableElement) + getOnlyElement(turbineElements.getTypeElement("C").getEnclosedElements())) + .getDefaultValue() + .getValue()) + .isEqualTo(42); + } + + @Test + public void constantFieldTest() { + assertThat( + ((VariableElement) + turbineElements.getTypeElement("Const").getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.FIELD)) + .collect(onlyElement())) + .getConstantValue()) + .isEqualTo(1867); + } + + @Test + public void packageElement() { + assertThat( + toStrings( + turbineElements.getAllAnnotationMirrors( + turbineElements.getPackageElement("com.pkg")))) + .containsExactly("@com.pkg.P"); + assertThat( + turbineElements.getAllAnnotationMirrors(turbineElements.getPackageElement("java.lang"))) + .isEmpty(); + assertThat(turbineElements.getPackageElement("com.google.no.such.pkg")).isNull(); + } + + @Test + public void packageMembers() { + assertThat( + turbineElements.getPackageElement("com.pkg").getEnclosedElements().stream() + .map(e -> ((TypeElement) e).getQualifiedName().toString()) + .collect(toImmutableList())) + .containsExactly("com.pkg.P", "com.pkg.A", "com.pkg.B"); + assertThat(turbineElements.getPackageElement("com.pkg.empty").getEnclosedElements()).isEmpty(); + } + + @Test + public void noElement() { + Element e = factory.noElement("com.google.Foo"); + assertThat(e.getKind()).isEqualTo(ElementKind.CLASS); + assertThat(e.getSimpleName().toString()).isEqualTo("Foo"); + assertThat(e.getEnclosingElement().toString()).isEqualTo("com.google"); + assertThat(e.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + + e = factory.noElement("Foo"); + assertThat(e.getSimpleName().toString()).isEqualTo("Foo"); + assertThat(e.getEnclosingElement().toString()).isEmpty(); + assertThat(e.getEnclosingElement().getKind()).isEqualTo(ElementKind.PACKAGE); + } + + @Test + public void javadoc() { + TypeElement e = turbineElements.getTypeElement("One"); + assertThat(turbineElements.getDocComment(e)) + .isEqualTo( + " javadoc\n" // + + " for\n" + + " one\n" + + ""); + + assertThat( + turbineElements.getDocComment( + e.getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.FIELD)) + .collect(onlyElement()))) + .isEqualTo(" field javadoc "); + + assertThat( + turbineElements.getDocComment( + e.getEnclosedElements().stream() + .filter(x -> x.getKind().equals(ElementKind.METHOD)) + .collect(onlyElement()))) + .isEqualTo(" method javadoc "); + + e = turbineElements.getTypeElement("Two"); + assertThat(turbineElements.getDocComment(e)) + .isEqualTo( + " javadoc\n" // + + "for\n" + + "two with extra *\n" + + ""); + } + + @Test + public void syntheticParameters() { + assertThat( + ((ExecutableElement) + getOnlyElement( + turbineElements.getTypeElement("com.pkg.A.I").getEnclosedElements())) + .getParameters()) + .isEmpty(); + } + + @Test + public void printElements() { + StringWriter w = new StringWriter(); + turbineElements.printElements( + w, + turbineElements.getTypeElement("com.pkg.A"), + turbineElements.getTypeElement("com.pkg.A.I")); + assertThat(w.toString()).isEqualTo(lines("com.pkg.A", "com.pkg.A.I")); + } + + private String lines(String... lines) { + return Joiner.on(System.lineSeparator()).join(lines) + System.lineSeparator(); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineFilerTest.java b/javatests/com/google/turbine/processing/TurbineFilerTest.java new file mode 100644 index 0000000..40b78ea --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineFilerTest.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; + +import com.google.common.base.Function; +import com.google.common.base.Supplier; +import com.google.common.io.ByteStreams; +import com.google.common.io.CharStreams; +import com.google.turbine.diag.SourceFile; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.FilerException; +import javax.lang.model.element.Element; +import javax.tools.FileObject; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineFilerTest { + + private final Set<String> seen = new HashSet<>(); + private TurbineFiler filer; + + @Before + public void setup() { + Function<String, Supplier<byte[]>> classpath = + new Function<String, Supplier<byte[]>>() { + @Nullable + @Override + public Supplier<byte[]> apply(String input) { + return null; + } + }; + this.filer = new TurbineFiler(seen, classpath, TurbineFilerTest.class.getClassLoader()); + } + + @Test + public void hello() throws IOException { + JavaFileObject sourceFile = filer.createSourceFile("com.foo.Bar", (Element[]) null); + try (OutputStream os = sourceFile.openOutputStream()) { + os.write("hello".getBytes(UTF_8)); + } + assertThat(sourceFile.getLastModified()).isEqualTo(0); + + JavaFileObject classFile = filer.createClassFile("com.foo.Baz", (Element[]) null); + try (OutputStream os = classFile.openOutputStream()) { + os.write("goodbye".getBytes(UTF_8)); + } + assertThat(classFile.getLastModified()).isEqualTo(0); + + Collection<SourceFile> roundSources = filer.finishRound(); + assertThat(roundSources).hasSize(1); + assertThat(filer.generatedSources()).hasSize(1); + assertThat(filer.generatedClasses()).hasSize(1); + + SourceFile source = getOnlyElement(roundSources); + assertThat(source.path()).isEqualTo("com/foo/Bar.java"); + assertThat(source.source()).isEqualTo("hello"); + + Map.Entry<String, byte[]> clazz = getOnlyElement(filer.generatedClasses().entrySet()); + assertThat(clazz.getKey()).isEqualTo("com/foo/Baz.class"); + assertThat(new String(clazz.getValue(), UTF_8)).isEqualTo("goodbye"); + } + + @Test + public void existing() throws IOException { + seen.add("com/foo/Bar.java"); + seen.add("com/foo/Baz.class"); + + try { + filer.createSourceFile("com.foo.Bar", (Element[]) null); + fail(); + } catch (FilerException expected) { + } + filer.createSourceFile("com.foo.Baz", (Element[]) null); + + filer.createClassFile("com.foo.Bar", (Element[]) null); + try { + filer.createClassFile("com.foo.Baz", (Element[]) null); + fail(); + } catch (FilerException expected) { + } + } + + @Test + public void get() throws IOException { + for (StandardLocation location : + Arrays.asList( + StandardLocation.CLASS_OUTPUT, + StandardLocation.SOURCE_OUTPUT, + StandardLocation.ANNOTATION_PROCESSOR_PATH, + StandardLocation.CLASS_PATH)) { + try { + filer.getResource(location, "", "NoSuch"); + fail(); + } catch (FileNotFoundException expected) { + } + } + } + + @Test + public void sourceOutput() throws IOException { + JavaFileObject classFile = filer.createSourceFile("com.foo.Bar", (Element[]) null); + try (Writer writer = classFile.openWriter()) { + writer.write("hello"); + } + filer.finishRound(); + + FileObject output = filer.getResource(StandardLocation.SOURCE_OUTPUT, "com.foo", "Bar.java"); + assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8)) + .isEqualTo("hello"); + assertThat(output.getCharContent(false).toString()).isEqualTo("hello"); + assertThat(CharStreams.toString(output.openReader(true))).isEqualTo("hello"); + } + + @Test + public void classOutput() throws IOException { + JavaFileObject classFile = filer.createClassFile("com.foo.Baz", (Element[]) null); + try (OutputStream os = classFile.openOutputStream()) { + os.write("goodbye".getBytes(UTF_8)); + } + filer.finishRound(); + + FileObject output = filer.getResource(StandardLocation.CLASS_OUTPUT, "com.foo", "Baz.class"); + assertThat(new String(ByteStreams.toByteArray(output.openInputStream()), UTF_8)) + .isEqualTo("goodbye"); + assertThat(output.getCharContent(false).toString()).isEqualTo("goodbye"); + assertThat(CharStreams.toString(output.openReader(true))).isEqualTo("goodbye"); + } + + @Test + public void classpathResources() throws IOException { + FileObject resource = + filer.getResource(StandardLocation.ANNOTATION_PROCESSOR_PATH, "META-INF", "MANIFEST.MF"); + + assertThat(new String(ByteStreams.toByteArray(resource.openInputStream()), UTF_8)) + .contains("Manifest-Version:"); + assertThat(CharStreams.toString(resource.openReader(true))).contains("Manifest-Version:"); + assertThat(resource.getCharContent(false).toString()).contains("Manifest-Version:"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineMessagerTest.java b/javatests/com/google/turbine/processing/TurbineMessagerTest.java new file mode 100644 index 0000000..c1e6401 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineMessagerTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.truth.Truth.assertThat; +import static java.util.Comparator.comparing; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.turbine.binder.Binder; +import com.google.turbine.binder.ClassPathBinder; +import com.google.turbine.binder.Processing; +import com.google.turbine.diag.SourceFile; +import com.google.turbine.diag.TurbineDiagnostic; +import com.google.turbine.diag.TurbineError; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.parse.Parser; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.tree.Tree; +import com.sun.source.util.JavacTask; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementScanner8; +import javax.lang.model.util.SimpleAnnotationValueVisitor8; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineMessagerTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", + "@A class Test {", + " @A @B void f() {}", + " @B int x;", + "}", + "=== One.java ===", + "class One<U, V> {", + " @A void f(@B int x, @C int y) {}", + " <X, Y> void g(X x) {}", + "}", + "=== Two.java ===", + "class Two {", + " @D(value = 1) int x1;", + " @D(1) int x2;", + " @E(1) int x3;", + " @E({1, 2}) int x4;", + " @E(value = {1, 2}, y = 3) int x5;", + " @E(y = 0, value = {1, 2}) int x6;", + "}", + "=== Annotations.java ===", + "@interface A {}", + "@interface B {}", + "@interface C {}", + "@interface D {", + " int value() default 0;", + "}", + "@interface E {", + " int[] value() default {};", + " int y() default 0;", + "}")); + + /** + * Tests {@link TurbineMessager} by logging a message at each {@link Element}, {@link + * AnnotationMirror}, and {@link AnnotationValue}. + */ + @SupportedAnnotationTypes("*") + public static class DiagnosticTesterProcessor extends AbstractProcessor { + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + for (Element e : roundEnv.getRootElements()) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.valueOf(e)); + e.accept( + new ElementScanner8<Void, Void>() { + @Override + public Void scan(Element e, Void unused) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, String.valueOf(e), e); + for (AnnotationMirror a : e.getAnnotationMirrors()) { + processingEnv + .getMessager() + .printMessage(Diagnostic.Kind.ERROR, String.format("%s %s", e, a), e, a); + for (AnnotationValue av : a.getElementValues().values()) { + processAnnotation(e, a, av); + } + } + return null; + } + + private void processAnnotation(Element e, AnnotationMirror a, AnnotationValue av) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, String.format("%s %s %s", e, a, av), e, a, av); + av.accept( + new SimpleAnnotationValueVisitor8<Void, Void>() { + @Override + public Void visitAnnotation(AnnotationMirror a, Void unused) { + visitAnnotationValues(a.getElementValues().values()); + return null; + } + + @Override + public Void visitArray(List<? extends AnnotationValue> vals, Void unused) { + visitAnnotationValues(vals); + return null; + } + + private void visitAnnotationValues( + Collection<? extends AnnotationValue> values) { + for (AnnotationValue av : values) { + processAnnotation(e, a, av); + } + } + }, + null); + } + + @Override + public Void visitExecutable(ExecutableElement e, Void unused) { + scan(e.getTypeParameters(), null); + return super.visitExecutable(e, unused); + } + + @Override + public Void visitType(TypeElement e, Void unused) { + scan(e.getTypeParameters(), null); + return super.visitType(e, unused); + } + }, + null); + } + return false; + } + } + + @Test + public void test() throws Exception { + + // Processes the test sources with the DiagnosticTesterProcessor under both javac and turbine, + // and asserts that the diagnostics have the same source path, line, and column under each. + + DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); + JavacTask task = + IntegrationTestSupport.runJavacAnalysis( + SOURCES.sources, ImmutableList.of(), ImmutableList.of(), collector); + task.setProcessors(ImmutableList.of(new DiagnosticTesterProcessor())); + task.call(); + ImmutableList<String> javacDiagnostics = + collector.getDiagnostics().stream() + // sort the diagnostics for nicer test failure messages + .sorted( + comparing(TurbineMessagerTest::shortPath) + .thenComparing(Diagnostic::getLineNumber) + .thenComparing(Diagnostic::getColumnNumber)) + .map(TurbineMessagerTest::formatDiagnostic) + .collect(toImmutableList()); + + ImmutableList<String> turbineDiagnostics; + ImmutableList<Tree.CompUnit> units = + SOURCES.sources.entrySet().stream() + .map(e -> new SourceFile(e.getKey(), e.getValue())) + .map(Parser::parse) + .collect(toImmutableList()); + try { + Binder.bind( + units, + ClassPathBinder.bindClasspath(ImmutableList.of()), + Processing.ProcessorInfo.create( + ImmutableList.of(new DiagnosticTesterProcessor()), + getClass().getClassLoader(), + ImmutableMap.of(), + SourceVersion.latestSupported()), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + throw new AssertionError(); + } catch (TurbineError e) { + turbineDiagnostics = + e.diagnostics().stream() + .sorted( + comparing(TurbineDiagnostic::path) + .thenComparing(TurbineDiagnostic::line) + .thenComparing(TurbineDiagnostic::column)) + .map(TurbineMessagerTest::formatDiagnostic) + .collect(toImmutableList()); + } + + assertThat(turbineDiagnostics).containsExactlyElementsIn(javacDiagnostics).inOrder(); + } + + private static String formatDiagnostic(TurbineDiagnostic d) { + return String.format("%s:%s:%s %s", d.path(), d.line(), d.column(), d.message()); + } + + private static String formatDiagnostic(Diagnostic<? extends JavaFileObject> d) { + return String.format( + "%s:%s:%s %s", + shortPath(d), d.getLineNumber(), d.getColumnNumber(), d.getMessage(Locale.ENGLISH)); + } + + private static String shortPath(Diagnostic<? extends JavaFileObject> d) { + return d.getSource() != null + ? Paths.get(d.getSource().getName()).getFileName().toString() + : "<>"; + } +} diff --git a/javatests/com/google/turbine/processing/TurbineNameTest.java b/javatests/com/google/turbine/processing/TurbineNameTest.java new file mode 100644 index 0000000..f67fc82 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineNameTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineNameTest { + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + new TurbineName("hello"), new TurbineName("hello"), new TurbineName("hello")) + .addEqualityGroup(new TurbineName("is")) + .addEqualityGroup(new TurbineName("there")) + .addEqualityGroup(new TurbineName("anybody")) + .addEqualityGroup(new TurbineName("in")) + .testEquals(); + } + + @Test + public void asd() { + assertThat(new TurbineName("hello").contentEquals("hello")).isTrue(); + assertThat(new TurbineName("hello").contentEquals("goodbye")).isFalse(); + + assertThat(new TurbineName("hello").length()).isEqualTo(5); + + assertThat(new TurbineName("hello").charAt(0)).isEqualTo('h'); + + assertThat(new TurbineName("hello").subSequence(1, 4).toString()).isEqualTo("ell"); + + assertThat(new TurbineName("hello").toString()).isEqualTo("hello"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java b/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java new file mode 100644 index 0000000..bf08f89 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypeMirrorTest.java @@ -0,0 +1,281 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.collect.MoreCollectors.onlyElement; +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.binder.sym.PackageSymbol; +import com.google.turbine.binder.sym.TyVarSymbol; +import com.google.turbine.model.TurbineConstantTypeKind; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type; +import com.google.turbine.type.Type.PrimTy; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineTypeMirrorTest { + + private final ModelFactory factory = + new ModelFactory( + TestClassPaths.TURBINE_BOOTCLASSPATH.env(), + ClassLoader.getSystemClassLoader(), + TestClassPaths.TURBINE_BOOTCLASSPATH.index()); + + @Test + public void primitiveTypes() { + for (TypeKind kind : TypeKind.values()) { + if (!kind.isPrimitive()) { + continue; + } + TurbineConstantTypeKind turbineKind = TurbineConstantTypeKind.valueOf(kind.name()); + TypeMirror type = factory.asTypeMirror(PrimTy.create(turbineKind, ImmutableList.of())); + assertThat(type.getKind()).isEqualTo(kind); + } + } + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of(), + ImmutableList.of()), + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.STRING), + ImmutableList.of()))))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.OBJECT), + ImmutableList.of()))))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry")))) + .addEqualityGroup( + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of())), + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror(PrimTy.create(TurbineConstantTypeKind.INT, ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.WildLowerBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.WildUpperBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of()))) + .addEqualityGroup(factory.asTypeMirror(Type.WildUnboundedTy.create(ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.ArrayTy.create( + PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()), + ImmutableList.of()))) + .addEqualityGroup(factory.packageType(new PackageSymbol("java/lang"))) + .addEqualityGroup( + factory.asTypeMirror( + Type.TyVar.create( + new TyVarSymbol(new ClassSymbol("java/util/List"), "V"), ImmutableList.of()))) + .addEqualityGroup( + factory.asTypeMirror( + Type.IntersectionTy.create( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/Cloneable")))))) + .addEqualityGroup(factory.noType()) + .testEquals(); + } + + @Test + public void roundTrip() { + DeclaredType te = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/List"), + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/String"))), + ImmutableList.of())))); + assertThat(te.asElement().asType()).isNotEqualTo(te); + assertThat(te.asElement().asType()) + .isEqualTo( + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/List"), + ImmutableList.of( + Type.TyVar.create( + new TyVarSymbol(new ClassSymbol("java/util/List"), "E"), + ImmutableList.of())), + ImmutableList.of()))))); + } + + @Test + public void wildTy() { + WildcardType lower = + (WildcardType) + factory.asTypeMirror( + Type.WildLowerBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Integer")), + ImmutableList.of())); + WildcardType upper = + (WildcardType) + factory.asTypeMirror( + Type.WildUpperBoundedTy.create( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Long")), + ImmutableList.of())); + WildcardType unbound = + (WildcardType) factory.asTypeMirror(Type.WildUnboundedTy.create(ImmutableList.of())); + + assertThat(lower.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(lower.getExtendsBound()).isNull(); + assertThat(lower.getSuperBound().getKind()).isEqualTo(TypeKind.DECLARED); + + assertThat(upper.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(upper.getExtendsBound().getKind()).isEqualTo(TypeKind.DECLARED); + assertThat(upper.getSuperBound()).isNull(); + + assertThat(unbound.getKind()).isEqualTo(TypeKind.WILDCARD); + assertThat(unbound.getExtendsBound()).isNull(); + assertThat(unbound.getSuperBound()).isNull(); + } + + @Test + public void intersection() { + IntersectionType t = + (IntersectionType) + factory.asTypeMirror( + Type.IntersectionTy.create( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy( + new ClassSymbol("java/lang/Cloneable"))))); + + assertThat(t.getKind()).isEqualTo(TypeKind.INTERSECTION); + assertThat(t.getBounds()) + .containsExactlyElementsIn( + factory.asTypeMirrors( + ImmutableList.of( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Object")), + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/io/Serializable")), + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/lang/Cloneable"))))); + } + + @Test + public void tyVar() { + TypeVariable t = + (TypeVariable) + Iterables.getOnlyElement( + factory + .typeElement(new ClassSymbol("java/util/Collections")) + .getEnclosedElements() + .stream() + .filter(e -> e.getSimpleName().contentEquals("sort")) + .filter(ExecutableElement.class::isInstance) + .map(ExecutableElement.class::cast) + .filter(e -> e.getParameters().size() == 1) + .findFirst() + .get() + .getTypeParameters()) + .asType(); + assertThat(t.getKind()).isEqualTo(TypeKind.TYPEVAR); + assertThat(t.getLowerBound().getKind()).isEqualTo(TypeKind.NONE); + assertThat(t.getUpperBound().toString()).isEqualTo("java.lang.Comparable<? super T>"); + } + + @Test + public void arrayType() { + ArrayType t = + (ArrayType) + factory.asTypeMirror( + Type.ArrayTy.create( + PrimTy.create(TurbineConstantTypeKind.LONG, ImmutableList.of()), + ImmutableList.of())); + assertThat(t.getKind()).isEqualTo(TypeKind.ARRAY); + assertThat(t.getComponentType().getKind()).isEqualTo(TypeKind.LONG); + } + + @Test + public void declared() { + DeclaredType a = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.create( + ImmutableList.of( + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of(), + ImmutableList.of()), + Type.ClassTy.SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(Type.ClassTy.STRING, Type.ClassTy.STRING), + ImmutableList.of())))); + DeclaredType b = + (DeclaredType) + factory.asTypeMirror( + Type.ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry"))); + + assertThat(a.getEnclosingType().getKind()).isEqualTo(TypeKind.NONE); + assertThat(b.getEnclosingType().getKind()).isEqualTo(TypeKind.NONE); + } + + @Test + public void method() { + ExecutableType type = + (ExecutableType) + ((TypeElement) factory.typeElement(new ClassSymbol("java/util/Collections"))) + .getEnclosedElements().stream() + .filter(e -> e.getSimpleName().contentEquals("replaceAll")) + .collect(onlyElement()) + .asType(); + assertThat(type.getTypeVariables()).hasSize(1); + assertThat(type.toString()).isEqualTo("<T>(java.util.List<T>,T,T)boolean"); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java b/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java new file mode 100644 index 0000000..e9e411f --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesContainsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesContainsTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesContainsTest( + String name, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(name, javacInput, turbineInput); + } + + @Test + public void contains() { + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("<=", Types::contains); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java new file mode 100644 index 0000000..0f9e6a6 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesFactoryTest.java @@ -0,0 +1,181 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.turbine.binder.Binder.BindingResult; +import com.google.turbine.binder.bound.TypeBoundClass; +import com.google.turbine.binder.env.CompoundEnv; +import com.google.turbine.binder.env.Env; +import com.google.turbine.binder.env.SimpleEnv; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.lower.IntegrationTestSupport; +import com.google.turbine.testing.TestClassPaths; +import com.google.turbine.type.Type.ArrayTy; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.WildLowerBoundedTy; +import com.google.turbine.type.Type.WildUnboundedTy; +import com.google.turbine.type.Type.WildUpperBoundedTy; +import java.util.Optional; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TurbineTypesFactoryTest { + + private static final IntegrationTestSupport.TestInput SOURCES = + IntegrationTestSupport.TestInput.parse( + Joiner.on('\n') + .join( + "=== Test.java ===", // + "class Test {", + " class I {}", + "}")); + + ModelFactory factory; + TurbineElements turbineElements; + TurbineTypes turbineTypes; + + @Before + public void setup() throws Exception { + + BindingResult bound = + IntegrationTestSupport.turbineAnalysis( + SOURCES.sources, + ImmutableList.of(), + TestClassPaths.TURBINE_BOOTCLASSPATH, + Optional.empty()); + Env<ClassSymbol, TypeBoundClass> env = + CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv()) + .append(new SimpleEnv<>(bound.units())); + factory = new ModelFactory(env, getClass().getClassLoader(), bound.tli()); + turbineTypes = new TurbineTypes(factory); + turbineElements = new TurbineElements(factory, turbineTypes); + } + + @Test + public void primitiveTypes() { + for (TypeKind kind : TypeKind.values()) { + if (kind.isPrimitive()) { + PrimitiveType type = turbineTypes.getPrimitiveType(kind); + assertThat(type.getKind()).isEqualTo(kind); + } else { + try { + turbineTypes.getPrimitiveType(kind); + fail(); + } catch (IllegalArgumentException expected) { + } + } + } + } + + @Test + public void arrayType() { + assertThat( + turbineTypes.isSameType( + turbineTypes.getArrayType( + turbineTypes.erasure(turbineElements.getTypeElement("java.util.Map").asType())), + factory.asTypeMirror( + ArrayTy.create( + ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map")), + ImmutableList.of())))) + .isTrue(); + } + + @Test + public void wildcardType() { + // wildcard types don't compare equal with isSameType, so compare their string representations + assertThat(turbineTypes.getWildcardType(null, null).toString()) + .isEqualTo(factory.asTypeMirror(WildUnboundedTy.create(ImmutableList.of())).toString()); + assertThat( + turbineTypes + .getWildcardType(turbineElements.getTypeElement("java.lang.String").asType(), null) + .toString()) + .isEqualTo( + factory + .asTypeMirror(WildUpperBoundedTy.create(ClassTy.STRING, ImmutableList.of())) + .toString()); + assertThat( + turbineTypes + .getWildcardType(null, turbineElements.getTypeElement("java.lang.String").asType()) + .toString()) + .isEqualTo( + factory + .asTypeMirror(WildLowerBoundedTy.create(ClassTy.STRING, ImmutableList.of())) + .toString()); + } + + @Test + public void declaredType() { + assertThat( + turbineTypes.isSameType( + turbineTypes.getDeclaredType( + turbineElements.getTypeElement("java.util.Map"), + turbineElements.getTypeElement("java.lang.String").asType(), + turbineElements.getTypeElement("java.lang.Integer").asType()), + factory.asTypeMirror( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("java/util/Map"), + ImmutableList.of( + ClassTy.STRING, + ClassTy.asNonParametricClassTy(ClassSymbol.INTEGER)), + ImmutableList.of())))))) + .isTrue(); + assertThat( + turbineTypes.isSameType( + turbineTypes.getDeclaredType( + turbineTypes.getDeclaredType(turbineElements.getTypeElement("Test")), + turbineElements.getTypeElement("Test.I")), + factory.asTypeMirror( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("Test"), ImmutableList.of(), ImmutableList.of()), + SimpleClassTy.create( + new ClassSymbol("Test$I"), + ImmutableList.of(), + ImmutableList.of())))))) + .isTrue(); + } + + @Test + public void noType() { + assertThat(turbineTypes.getNoType(TypeKind.VOID).getKind()).isEqualTo(TypeKind.VOID); + assertThat(turbineTypes.getNoType(TypeKind.NONE).getKind()).isEqualTo(TypeKind.NONE); + try { + turbineTypes.getNoType(TypeKind.DECLARED); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void nullType() { + assertThat(turbineTypes.getNullType().getKind()).isEqualTo(TypeKind.NULL); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java new file mode 100644 index 0000000..75b93b0 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsAssignableTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsAssignableTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsAssignableTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isAssignable() { + // see JDK-8039198 + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("isAssignable", Types::isAssignable); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java new file mode 100644 index 0000000..ef45991 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSameTypeTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSameTypeTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSameTypeTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSameType() { + test("isSameType", Types::isSameType); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java new file mode 100644 index 0000000..5f315da --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSubsignatureTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSubsignatureTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSubsignatureTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSubsignature() { + assume().that(javacInput.lhs.getKind()).isEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isEqualTo(TypeKind.EXECUTABLE); + + test( + "isSubsignature", + (types, x, y) -> types.isSubsignature((ExecutableType) x, (ExecutableType) y)); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java b/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java new file mode 100644 index 0000000..8370126 --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesIsSubtypeTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.TruthJUnit.assume; + +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesIsSubtypeTest extends AbstractTurbineTypesBiPredicateTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return binaryParameters(); + } + + public TurbineTypesIsSubtypeTest( + String testDescription, TypesBiFunctionInput javacInput, TypesBiFunctionInput turbineInput) { + super(testDescription, javacInput, turbineInput); + } + + @Test + public void isSubtype() { + // see JDK-8039198 + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.WILDCARD); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.NONE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.NONE); + + // crashes javac + assume().that(javacInput.lhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + assume().that(javacInput.rhs.getKind()).isNotEqualTo(TypeKind.EXECUTABLE); + + test("<:", Types::isSubtype); + } +} diff --git a/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java new file mode 100644 index 0000000..eb5ee6c --- /dev/null +++ b/javatests/com/google/turbine/processing/TurbineTypesUnaryTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.processing; + +import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.truth.TruthJUnit.assume; +import static org.junit.Assert.fail; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TurbineTypesUnaryTest extends AbstractTurbineTypesTest { + + @Parameters(name = "{index}: {0}") + public static Iterable<Object[]> parameters() throws Exception { + return unaryParameters(); + } + + final String testDescription; + final Types javacTypes; + final TypeMirror javacA; + final Types turbineTypes; + final TypeMirror turbineA; + + public TurbineTypesUnaryTest( + String testDescription, + Types javacTypes, + TypeMirror javacA, + Types turbineTypes, + TypeMirror turbineA) { + this.testDescription = testDescription; + this.javacTypes = javacTypes; + this.javacA = javacA; + this.turbineTypes = turbineTypes; + this.turbineA = turbineA; + } + + @Test + public void unboxedType() { + IllegalArgumentException thrown = null; + String expectedType = null; + try { + expectedType = javacTypes.unboxedType(javacA).toString(); + } catch (IllegalArgumentException e) { + thrown = e; + } + if (thrown != null) { + try { + turbineTypes.unboxedType(turbineA).toString(); + fail(String.format("expected unboxedType(`%s`) to throw", turbineA)); + } catch (IllegalArgumentException expected) { + // expected + } + } else { + String actual = turbineTypes.unboxedType(turbineA).toString(); + assertWithMessage("unboxedClass(`%s`) = unboxedClass(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expectedType); + } + } + + @Test + public void boxedClass() { + assume().that(javacA).isInstanceOf(PrimitiveType.class); + assume().that(turbineA).isInstanceOf(PrimitiveType.class); + + String expected = javacTypes.boxedClass((PrimitiveType) javacA).toString(); + String actual = turbineTypes.boxedClass((PrimitiveType) turbineA).toString(); + assertWithMessage("boxedClass(`%s`) = boxedClass(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + @Test + public void erasure() { + String expected = javacTypes.erasure(javacA).toString(); + String actual = turbineTypes.erasure(turbineA).toString(); + assertWithMessage("erasure(`%s`) = erasure(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + private static final ImmutableSet<TypeKind> UNSUPPORTED_BY_DIRECT_SUPERTYPES = + ImmutableSet.of(TypeKind.EXECUTABLE, TypeKind.PACKAGE); + + @Test + public void directSupertypes() { + assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).doesNotContain(javacA.getKind()); + + String expected = Joiner.on(", ").join(javacTypes.directSupertypes(javacA)); + String actual = Joiner.on(", ").join(turbineTypes.directSupertypes(turbineA)); + assertWithMessage("directSupertypes(`%s`) = directSupertypes(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } + + @Test + public void directSupertypesThrows() { + assume().that(UNSUPPORTED_BY_DIRECT_SUPERTYPES).contains(javacA.getKind()); + + try { + javacTypes.directSupertypes(turbineA); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + turbineTypes.directSupertypes(turbineA); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void asElement() { + // TODO(cushon): this looks like a javac bug + assume().that(javacA.getKind()).isNotEqualTo(TypeKind.INTERSECTION); + + String expected = String.valueOf(javacTypes.asElement(javacA)); + String actual = String.valueOf(turbineTypes.asElement(turbineA)); + assertWithMessage("asElement(`%s`) = asElement(`%s`)", javacA, turbineA) + .that(actual) + .isEqualTo(expected); + } +} diff --git a/javatests/com/google/turbine/testing/TestClassPaths.java b/javatests/com/google/turbine/testing/TestClassPaths.java index bf38913..93be916 100644 --- a/javatests/com/google/turbine/testing/TestClassPaths.java +++ b/javatests/com/google/turbine/testing/TestClassPaths.java @@ -67,7 +67,7 @@ public class TestClassPaths { public static TurbineOptions.Builder optionsWithBootclasspath() { TurbineOptions.Builder options = TurbineOptions.builder(); if (!BOOTCLASSPATH.isEmpty()) { - options.addBootClassPathEntries( + options.setBootClassPath( BOOTCLASSPATH.stream().map(Path::toString).collect(toImmutableList())); } else { options.setRelease("8"); diff --git a/javatests/com/google/turbine/type/TypeTest.java b/javatests/com/google/turbine/type/TypeTest.java new file mode 100644 index 0000000..be3eb9c --- /dev/null +++ b/javatests/com/google/turbine/type/TypeTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Google Inc. 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.turbine.type; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; +import com.google.turbine.binder.sym.ClassSymbol; +import com.google.turbine.tree.Tree.Ident; +import com.google.turbine.type.Type.ClassTy; +import com.google.turbine.type.Type.ClassTy.SimpleClassTy; +import com.google.turbine.type.Type.ErrorTy; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class TypeTest { + + @Test + public void equals() { + new EqualsTester() + .addEqualityGroup( + ClassTy.create( + ImmutableList.of( + SimpleClassTy.create( + new ClassSymbol("java/util/Map"), ImmutableList.of(), ImmutableList.of()), + SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(ClassTy.STRING, ClassTy.STRING), + ImmutableList.of())))) + .addEqualityGroup( + SimpleClassTy.create( + new ClassSymbol("java/util/Map$Entry"), + ImmutableList.of(ClassTy.STRING, ClassTy.OBJECT), + ImmutableList.of())) + .addEqualityGroup(ClassTy.asNonParametricClassTy(new ClassSymbol("java/util/Map$Entry"))) + .testEquals(); + } + + private static final int NO_POSITION = -1; + + @Test + public void error() { + assertThat( + ErrorTy.create( + ImmutableList.of( + new Ident(NO_POSITION, "com"), + new Ident(NO_POSITION, "foo"), + new Ident(NO_POSITION, "Bar"))) + .name()) + .isEqualTo("com.foo.Bar"); + } +} diff --git a/javatests/com/google/turbine/zip/ZipTest.java b/javatests/com/google/turbine/zip/ZipTest.java index 67dcfe7..bfc9cdf 100644 --- a/javatests/com/google/turbine/zip/ZipTest.java +++ b/javatests/com/google/turbine/zip/ZipTest.java @@ -165,7 +165,7 @@ public class ZipTest { actual(path); fail(); } catch (ZipException e) { - assertThat(e).hasMessage("zip file comment length was 33, expected 17"); + assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17"); } } } |