/* * Copyright 2014-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.jvm.java.abi; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; import com.facebook.buck.event.DefaultBuckEventBus; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.java.JarDumper; import com.facebook.buck.jvm.java.testutil.compiler.CompilerTreeApiParameterized; import com.facebook.buck.jvm.java.testutil.compiler.TestCompiler; import com.facebook.buck.model.BuildId; import com.facebook.buck.timing.FakeClock; import com.facebook.buck.util.sha1.Sha1HashCode; import com.facebook.buck.zip.Unzip; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; import org.hamcrest.Matchers; 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.Parameterized; import org.objectweb.asm.ClassReader; // The stub source is easier to read as long lines, so... // CHECKSTYLE.OFF: LineLengthCheck @RunWith(CompilerTreeApiParameterized.class) public class StubJarTest { // Test a stub generated by stripping a full jar private static final String MODE_JAR_BASED = "JAR_BASED"; // Test a stub generated from source private static final String MODE_SOURCE_BASED = "SOURCE_BASED"; // Test a stub generated from source, with dependencies missing private static final String MODE_SOURCE_BASED_MISSING_DEPS = "SOURCE_BASED_MISSING_DEPS"; @Parameterized.Parameter public String testingMode; @Parameterized.Parameters(name = "{0}") public static Object[] getParameters() { return new Object[] {MODE_JAR_BASED, MODE_SOURCE_BASED, MODE_SOURCE_BASED_MISSING_DEPS}; } private static final ImmutableSortedSet<Path> EMPTY_CLASSPATH = ImmutableSortedSet.of(); @Rule public TemporaryFolder temp = new TemporaryFolder(); private Tester tester = new Tester(); private ProjectFilesystem filesystem; @Before public void createTempFilesystem() throws InterruptedException, IOException { File out = temp.newFolder(); filesystem = new ProjectFilesystem(out.toPath()); } @Test public void emptyClass() throws IOException { tester .setSourceFile("A.java", "package com.example.buck; public class A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar() .addStubJarToClasspath() .setSourceFile("B.java", "package com.example.buck; public class B extends A {}") .testCanCompile(); } @Test public void emptyClassWithAnnotation() throws IOException { tester .setSourceFile("A.java", "package com.example.buck; @Deprecated public class A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// DEPRECATED", "// access flags 0x20021", "public class com/example/buck/A {", "", "", " @Ljava/lang/Deprecated;()", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void classWithTwoMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public String toString() { return null; }", " public void eatCake() {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public toString()Ljava/lang/String;", "", " // access flags 0x1", " public eatCake()V", "}") .createAndCheckStubJar(); } @Test public void preservesThrowsClauses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public void throwSomeStuff() throws Exception, Throwable {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public throwSomeStuff()V throws java/lang/Exception java/lang/Throwable ", "}") .createAndCheckStubJar(); } @Test public void preservesThrowsClausesWithTypeVars() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.io.IOException;", "public class A {", " public <E extends IOException> void throwSomeStuff() throws E {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " // signature <E:Ljava/io/IOException;>()V^TE;", " // declaration: void throwSomeStuff<E extends java.io.IOException>() throws E", " public throwSomeStuff()V throws java/io/IOException ", "}") .createAndCheckStubJar(); } @Test public void genericClassSignaturesShouldBePreserved() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A<T> {", " public T get(String key) { return null; }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;", "// declaration: com/example/buck/A<T>", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " // signature (Ljava/lang/String;)TT;", " // declaration: T get(java.lang.String)", " public get(Ljava/lang/String;)Ljava/lang/Object;", "}") .createAndCheckStubJar(); } @Test public void elementsInStubCorrectlyInOrder() throws IOException { // Fields and methods should stub in order // Inner classes should stub in reverse tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " boolean first;", " float second;", " public void foo() { }", " public class B { }", " public class C { }", " public void bar() { }", " public class D { }", " int between;", " public class E {", " public void hello() { }", " public void test() { }", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "}") .addExpectedStub( "com/example/buck/A$C", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$C {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$C com/example/buck/A C", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "}") .addExpectedStub( "com/example/buck/A$D", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$D {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$D com/example/buck/A D", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "}") .addExpectedStub( "com/example/buck/A$E", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$E {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$E com/example/buck/A E", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "", " // access flags 0x1", " public hello()V", "", " // access flags 0x1", " public test()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$E com/example/buck/A E", " // access flags 0x1", " public INNERCLASS com/example/buck/A$D com/example/buck/A D", " // access flags 0x1", " public INNERCLASS com/example/buck/A$C com/example/buck/A C", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x0", " Z first", "", " // access flags 0x0", " F second", "", " // access flags 0x0", " I between", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public foo()V", "", " // access flags 0x1", " public bar()V", "}") .createAndCheckStubJar(); } @Test public void genericInterfaceSignaturesShouldBePreserved() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public interface A<T> {", " T get(String key);", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x601", "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;", "// declaration: com/example/buck/A<T>", "public abstract interface com/example/buck/A {", "", "", " // access flags 0x401", " // signature (Ljava/lang/String;)TT;", " // declaration: T get(java.lang.String)", " public abstract get(Ljava/lang/String;)Ljava/lang/Object;", "}") .createAndCheckStubJar(); } @Test public void shouldIgnorePrivateMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " private void privateMethod() {}", " void packageMethod() {}", " protected void protectedMethod() {}", " public void publicMethod() {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x0", " packageMethod()V", "", " // access flags 0x4", " protected protectedMethod()V", "", " // access flags 0x1", " public publicMethod()V", "}") .createAndCheckStubJar(); } @Test public void shouldGenerateConstructorForClassWithSinglePrivateConstructor() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " private A() { }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x2", " private <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldGenerateConstructorForClassWithPrivateConstructorsOnly() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " private A() { }", " private A(int test) { }", " private A(String test) { }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x2", " private <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldGeneratePrivateInnerClassDefaultConstructor() throws IOException { tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public class Inner {", " private Inner() { }", " }", "}") .addExpectedStub( "com/example/buck/Outer$Inner", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/Outer$Inner {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", "", " // access flags 0x2", " private <init>(Lcom/example/buck/Outer;)V", "}") .addExpectedStub( "com/example/buck/Outer", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/Outer {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldGeneratePrivateNestedClassDefaultConstructor() throws IOException { notYetImplementedForSource(); tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public class Inner {", " public class Nested {", " private Nested() { }", " }", " }", "}") .addExpectedStub( "com/example/buck/Outer$Inner$Nested", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/Outer$Inner$Nested {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner Nested", "", " // access flags 0x2", " private <init>(Lcom/example/buck/Outer$Inner;)V", "}") .addExpectedStub( "com/example/buck/Outer$Inner", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/Outer$Inner {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner$Nested com/example/buck/Outer$Inner Nested", "", " // access flags 0x1", " public <init>(Lcom/example/buck/Outer;)V", "}") .addExpectedStub( "com/example/buck/Outer", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/Outer {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldPreserveAField() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " protected String protectedField;", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x4", " protected Ljava/lang/String; protectedField", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldIgnorePrivateFields() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " private String privateField;", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldPreserveGenericTypesOnFields() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A<T> {", " public T theField;", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;", "// declaration: com/example/buck/A<T>", "public class com/example/buck/A {", "", "", " // access flags 0x1", " // signature TT;", " // declaration: T", " public Ljava/lang/Object; theField", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldPreserveGenericTypesOnMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A<T> {", " public T get(String key) { return null; }", " public <X extends Comparable<T>> X compareWith(T other) { return null; }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;", "// declaration: com/example/buck/A<T>", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " // signature (Ljava/lang/String;)TT;", " // declaration: T get(java.lang.String)", " public get(Ljava/lang/String;)Ljava/lang/Object;", "", " // access flags 0x1", " // signature <X::Ljava/lang/Comparable<TT;>;>(TT;)TX;", " // declaration: X compareWith<X extends java.lang.Comparable<T>>(T)", " public compareWith(Ljava/lang/Object;)Ljava/lang/Comparable;", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsOnMethods() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " @Foo", " public void cheese(String key) {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public cheese(Ljava/lang/String;)V", " @Lcom/example/buck/Foo;()", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsOnFields() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " @Foo", " public String name;", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public Ljava/lang/String; name", " @Lcom/example/buck/Foo;()", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsOnParameters() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public void peynir(@Foo String very, int tasty) {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public peynir(Ljava/lang/String;I)V", " @Lcom/example/buck/Foo;() // parameter 0", "}") .createAndCheckStubJar(); } @Test public void preservesTypeAnnotationsInClasses() throws IOException { // TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated // type gets put on the Tree object but doesn't make it to the corresponding Element. We can // work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges // that I don't want to deal with right now. notYetImplementedForSource(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "public class A<@Foo.TypeAnnotation T> { }") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature <T:Ljava/lang/Object;>Ljava/lang/Object;", "// declaration: com/example/buck/A<T>", "public class com/example/buck/A {", "", "", " @Lcom/example/buck/Foo$TypeAnnotation;() : CLASS_TYPE_PARAMETER 0, null // invisible", " // access flags 0x2609", " public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void preservesTypeAnnotationsInMethods() throws IOException { // TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated // type gets put on the Tree object but doesn't make it to the corresponding Element. We can // work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges // that I don't want to deal with right now. notYetImplementedForSource(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " <@Foo.TypeAnnotation T> void foo(@Foo.TypeAnnotation String s) { }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x2609", " public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x0", " // signature <T:Ljava/lang/Object;>(Ljava/lang/String;)V", " // declaration: void foo<T>(java.lang.String)", " foo(Ljava/lang/String;)V", " @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_TYPE_PARAMETER 0, null // invisible", " @Lcom/example/buck/Foo$TypeAnnotation;() : METHOD_FORMAL_PARAMETER 0, null // invisible", "}") .createAndCheckStubJar(); } @Test public void preservesTypeAnnotationsInFields() throws IOException { // TODO(jkeljo): It looks like annotated types are not accessible via Elements. The annotated // type gets put on the Tree object but doesn't make it to the corresponding Element. We can // work around this using Trees.getTypeMirror, but that brings in a lot of classpath challenges // that I don't want to deal with right now. notYetImplementedForSource(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "import java.util.List;", "public class A {", " List<@Foo.TypeAnnotation String> list;", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x2609", " public static abstract INNERCLASS com/example/buck/Foo$TypeAnnotation com/example/buck/Foo TypeAnnotation", "", " // access flags 0x0", " // signature Ljava/util/List<Ljava/lang/String;>;", " // declaration: java.util.List<java.lang.String>", " Ljava/util/List; list", " @Lcom/example/buck/Foo$TypeAnnotation;() : FIELD, 0 // invisible", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void omitsAnnotationsWithSourceRetention() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@SourceRetentionAnno()", "public class A { }", "@Retention(RetentionPolicy.SOURCE)", "@interface SourceRetentionAnno { }") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/SourceRetentionAnno", "// class version 52.0 (52)", "// access flags 0x2600", "abstract @interface com/example/buck/SourceRetentionAnno implements java/lang/annotation/Annotation {", "", "", " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithClassRetention() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@ClassRetentionAnno()", "public class A { }", "@Retention(RetentionPolicy.CLASS)", "@interface ClassRetentionAnno { }") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " @Lcom/example/buck/ClassRetentionAnno;() // invisible", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/ClassRetentionAnno", "// class version 52.0 (52)", "// access flags 0x2600", "abstract @interface com/example/buck/ClassRetentionAnno implements java/lang/annotation/Annotation {", "", "", " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithRuntimeRetention() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@RuntimeRetentionAnno()", "public class A { }", "@Retention(RetentionPolicy.RUNTIME)", "@interface RuntimeRetentionAnno { }") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " @Lcom/example/buck/RuntimeRetentionAnno;()", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/RuntimeRetentionAnno", "// class version 52.0 (52)", "// access flags 0x2600", "abstract @interface com/example/buck/RuntimeRetentionAnno implements java/lang/annotation/Annotation {", "", "", " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithPrimitiveValues() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "@Foo(primitiveValue=1)", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Lcom/example/buck/Foo;(primitiveValue=1)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithStringArrayValues() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "@Foo(stringArrayValue={\"1\", \"2\"})", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Lcom/example/buck/Foo;(stringArrayValue={\"1\", \"2\"})", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithEnumValues() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@Retention(RetentionPolicy.RUNTIME)", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithTypeValues() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "@Foo(typeValue=String.class)", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Lcom/example/buck/Foo;(typeValue=java.lang.String.class)", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithEnumArrayValues() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@Target({ElementType.CONSTRUCTOR, ElementType.FIELD})", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Ljava/lang/annotation/Target;(value={Ljava/lang/annotation/ElementType;.CONSTRUCTOR, Ljava/lang/annotation/ElementType;.FIELD})", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithAnnotationValues() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@Foo(annotationValue=@Retention(RetentionPolicy.RUNTIME))", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Lcom/example/buck/Foo;(annotationValue=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME))", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationsWithAnnotationArrayValues() throws IOException { notYetImplementedForMissingClasspath(); createAnnotationFullJar() .addFullJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "import java.lang.annotation.*;", "@Foo(annotationArrayValue=@Retention(RetentionPolicy.RUNTIME))", "public @interface A {}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/A implements java/lang/annotation/Annotation {", "", "", " @Lcom/example/buck/Foo;(annotationArrayValue={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)})", "}") .createAndCheckStubJar(); } @Test public void preservesAnnotationDefaultValues() throws IOException { tester .setSourceFile( "Foo.java", "package com.example.buck;", "import java.lang.annotation.*;", "public @interface Foo {", " int primitiveValue() default 42;", " String[] stringArrayValue() default {\"Hello\", \"World\", \"!\"};", " Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);", " Retention[] annotationArrayValue() default {@Retention(RetentionPolicy.SOURCE), @Retention(RetentionPolicy.CLASS), @Retention(RetentionPolicy.RUNTIME)};", " RetentionPolicy enumValue () default RetentionPolicy.CLASS;", " Class typeValue() default Foo.class;", "}") .addExpectedStub( "com/example/buck/Foo", "// class version 52.0 (52)", "// access flags 0x2601", "public abstract @interface com/example/buck/Foo implements java/lang/annotation/Annotation {", "", "", " // access flags 0x401", " public abstract primitiveValue()I", " default=42", "", " // access flags 0x401", " public abstract stringArrayValue()[Ljava/lang/String;", " default={\"Hello\", \"World\", \"!\"}", "", " // access flags 0x401", " public abstract annotationValue()Ljava/lang/annotation/Retention;", " default=@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE)", "", " // access flags 0x401", " public abstract annotationArrayValue()[Ljava/lang/annotation/Retention;", " default={@Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.SOURCE), @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.CLASS), @Ljava/lang/annotation/Retention;(value=Ljava/lang/annotation/RetentionPolicy;.RUNTIME)}", "", " // access flags 0x401", " public abstract enumValue()Ljava/lang/annotation/RetentionPolicy;", " default=Ljava/lang/annotation/RetentionPolicy;.CLASS", "", " // access flags 0x401", " public abstract typeValue()Ljava/lang/Class;", " default=com.example.buck.Foo.class", "}") .createAndCheckStubJar(); } @Test public void stubsEnums() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public enum A {", " Value1,", " Value2", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4031", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>", "public final enum com/example/buck/A extends java/lang/Enum {", "", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value2", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .createAndCheckStubJar(); } @Test public void stubsExplicitlyAbstractEnums() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public enum A {", " Value1 {", " @Override", " public void run() { }", " };", " public abstract void run();", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4421", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>", // abstract flag is removed in the stub: "public abstract enum com/example/buck/A extends java/lang/Enum {", "", " // access flags 0x4008", " static enum INNERCLASS com/example/buck/A$1 null null", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x101A", " private final static synthetic [Lcom/example/buck/A; $VALUES", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " // signature ()V", " // declaration: void <init>()", " private <init>(Ljava/lang/String;I)V", "", " // access flags 0x401", " public abstract run()V", "", " // access flags 0x1000", " synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V", "", " // access flags 0x8", " static <clinit>()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4021", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>", "public enum com/example/buck/A extends java/lang/Enum {", "", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x401", " public abstract run()V", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .createAndCheckStubJar(); } @Test public void stubsImplicitlyAbstractEnums() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public enum A implements Runnable {", " Value1 {", " @Override", " public void run() { }", " };", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4421", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A> implements java.lang.Runnable", // abstract flag is removed in the stub: "public abstract enum com/example/buck/A extends java/lang/Enum implements java/lang/Runnable {", "", " // access flags 0x4008", " static enum INNERCLASS com/example/buck/A$1 null null", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x101A", " private final static synthetic [Lcom/example/buck/A; $VALUES", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " // signature ()V", " // declaration: void <init>()", " private <init>(Ljava/lang/String;I)V", "", " // access flags 0x1000", " synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V", "", " // access flags 0x8", " static <clinit>()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4021", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;Ljava/lang/Runnable;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A> implements java.lang.Runnable", "public enum com/example/buck/A extends java/lang/Enum implements java/lang/Runnable {", "", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .createAndCheckStubJar(); } @Test public void stubsInnerClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public class B {", " public int count;", " public void foo() {}", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public I count", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "", " // access flags 0x1", " public foo()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsProtectedInnerClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " protected class B {", " public int count;", " public void foo() {}", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x4", " protected INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public I count", "", " // access flags 0x4", " protected <init>(Lcom/example/buck/A;)V", "", " // access flags 0x1", " public foo()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x4", " protected INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsDefaultInnerClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " class B {", " public int count;", " public void foo() {}", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x20", "class com/example/buck/A$B {", "", " // access flags 0x0", " INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public I count", "", " // access flags 0x0", " <init>(Lcom/example/buck/A;)V", "", " // access flags 0x1", " public foo()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x0", " INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } // TODO(jkeljo): We should only be stubbing private inner classes when they are referenced in // the ABI. That's a more involved change which I'll make soon, but for now I want to document // the existing behavior and ensure it is consistent across class and source-based ABIs. @Test public void stubsPrivateInnerClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " private class B {", " public int count;", " public void foo() {}", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x20", "class com/example/buck/A$B {", "", " // access flags 0x2", " private INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public I count", "", " // access flags 0x1", " public foo()V", "", " // access flags 0x2", " private <init>(Lcom/example/buck/A;)V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", // TODO(jkeljo): If the private inner is stubbed, there should be an entry here too " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsInnerEnums() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public enum B { Value }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x4031", "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;", "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B>", "public final enum com/example/buck/A$B extends java/lang/Enum {", "", " // access flags 0x4019", " public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A$B; Value", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A$B;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x4019", " public final static enum INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsAbstractInnerEnums() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public enum B implements Runnable {", " Value {", " @Override", " public void run() {}", " }", " }", "}") .addExpectedFullAbi( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x4421", "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;", "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B> implements java.lang.Runnable", // abstract flag is removed in the stub: "public abstract enum com/example/buck/A$B extends java/lang/Enum implements java/lang/Runnable {", "", " // access flags 0x4409", // abstract flag is removed in the stub: " public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B", " // access flags 0x4008", " static enum INNERCLASS com/example/buck/A$B$1 null null", " // access flags 0x1008", " static synthetic INNERCLASS com/example/buck/A$1 null null", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A$B; Value", "", " // access flags 0x101A", " private final static synthetic [Lcom/example/buck/A$B; $VALUES", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A$B;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;", "", " // access flags 0x2", " // signature ()V", " // declaration: void <init>()", " private <init>(Ljava/lang/String;I)V", "", " // access flags 0x1000", " synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V", "", " // access flags 0x8", " static <clinit>()V", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x4021", "// signature Ljava/lang/Enum<Lcom/example/buck/A$B;>;Ljava/lang/Runnable;", "// declaration: com/example/buck/A$B extends java.lang.Enum<com.example.buck.A$B> implements java.lang.Runnable", "public enum com/example/buck/A$B extends java/lang/Enum implements java/lang/Runnable {", "", " // access flags 0x4009", " public static enum INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A$B; Value", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A$B;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$B;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1008", " static synthetic INNERCLASS com/example/buck/A$1 null null", " // access flags 0x4409", // abstract flag is removed in the stub: " public static abstract enum INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x4009", " public static enum INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsNestedInnerClasses() throws IOException { notYetImplementedForSource(); tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public class B {", " public class C {", " public int count;", " public void foo() {}", " }", " }", "}") .addExpectedStub( "com/example/buck/A$B$C", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B$C {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C", "", " // access flags 0x1", " public I count", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A$B;)V", "", " // access flags 0x1", " public foo()V", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B$C com/example/buck/A$B C", "", " // access flags 0x1", " public <init>(Lcom/example/buck/A;)V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsReferencesToInnerClassesOfOtherTypes() throws IOException { notYetImplementedForSource(); tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " B.C field;", "}", "class B {", " public class C { }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", // An innerclass entry is present for B$C even though it's not an inner class of A, so // that the compiler and runtime know how to interpret the name B$C: " // access flags 0x1", " public INNERCLASS com/example/buck/B$C com/example/buck/B C", "", " // access flags 0x0", " Lcom/example/buck/B$C; field", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/B$C", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/B$C {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/B$C com/example/buck/B C", "", " // access flags 0x1", " public <init>(Lcom/example/buck/B;)V", "}") .addExpectedStub( "com/example/buck/B", "// class version 52.0 (52)", "// access flags 0x20", "class com/example/buck/B {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/B$C com/example/buck/B C", "", " // access flags 0x0", " <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubsStaticMemberClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public static class B {", " public int count;", " public void foo() {}", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x9", " public static INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public I count", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public foo()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x9", " public static INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void ignoresAnonymousClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public Runnable r = new Runnable() {", " public void run() { }", " };", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public Ljava/lang/Runnable; r", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void ignoresLocalClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public void method() {", " class Local { };", " }", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public method()V", "}") .createAndCheckStubJar(); } @Test public void preservesThrowsClausesOnInnerClassConstructors() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public class B {", " B() throws Exception, Throwable {", " }", " }", "}") .addExpectedStub( "com/example/buck/A$B", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A$B {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x0", " <init>(Lcom/example/buck/A;)V throws java/lang/Exception java/lang/Throwable ", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1", " public INNERCLASS com/example/buck/A$B com/example/buck/A B", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void abiSafeChangesResultInTheSameOutputJar() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " protected final static int count = 42;", " public String getGreeting() { return \"hello\"; }", " Class<?> clazz;", " public int other;", "}") .createStubJar() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " protected final static int count = 42;", " public String getGreeting() { return \"merhaba\"; }", " Class<?> clazz = String.class;", " public int other = 32;", "}") .assertStubJarIsIdentical(); } @Test public void shouldIncludeInnerClassTypeParameterReferenceInMethodParameter() throws IOException { notYetImplementedForSource(); tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public enum Inner { }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck;", "import java.util.Set;", "import com.example.buck.Outer;", "public interface A {", " void foo(Set<Outer.Inner> test);", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x601", "public abstract interface com/example/buck/A {", "", " // access flags 0x4019", " public final static enum INNERCLASS com/example/buck/Outer$Inner com/example/buck/Outer Inner", "", " // access flags 0x401", " // signature (Ljava/util/Set<Lcom/example/buck/Outer$Inner;>;)V", " // declaration: void foo(java.util.Set<com.example.buck.Outer$Inner>)", " public abstract foo(Ljava/util/Set;)V", "}") .createAndCheckStubJar(); } @Test public void ordersChangesResultInADifferentOutputJar() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " protected final static int count = 42;", " public String getGreeting() { return \"hello\"; }", " Class<?> clazz;", " public int other;", "}") .createStubJar() .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " Class<?> clazz;", " public String getGreeting() { return \"hello\"; }", " protected final static int count = 42;", " public int other;", "}") .assertStubJarIsDifferent(); } @Test public void shouldIncludeStaticFields() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public static String foo;", " public final static int count = 42;", " protected static void method() {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x9", " public static Ljava/lang/String; foo", "", " // access flags 0x19", " public final static I count = 42", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0xC", " protected static method()V", "}") .createAndCheckStubJar(); } @Test public void innerClassesInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public class Inner {", " public String getGreeting() { return \"hola\"; }", " }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", // Note: different package "import com.example.buck.Outer;", // Inner class becomes available "public class A {", " private Outer.Inner field;", // Reference the inner class "}") .testCanCompile(); } @Test public void staticMethodOfInnerClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public static class Inner {", " public static int testStatic() { return 0; }", " }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.Outer;", "public class A {", " public void testMethod() {", " int test = Outer.Inner.testStatic();", " }", "}") .testCanCompile(); } @Test public void privateFieldInInnerClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public String getInnerString() {", " return new Outer.Inner().innerPrivateString;", // Relies on synthetic method " }", " private static final class Inner {", " private final String innerPrivateString = \"hi!\";", " }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.Outer;", "public class A {", " public String testMethod() {", " Outer test = new Outer();", " return test.getInnerString();", " }", "}") .testCanCompile(); } @Test public void nestedClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Outer.java", "package com.example.buck;", "public class Outer {", " public class Inner {", " public class Nested { }", " }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.Outer;", "public class A {", " private Outer.Inner.Nested field;", "}") .testCanCompile(); } @Test public void methodReturningPrivateInnerClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Test.java", "package com.example.buck;", "public class Test {", " public static PrivateInner getPrivateInner() {", " return new PrivateInner();", " }", " private static class PrivateInner { }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.Test;", "public class A {", " public void test() {", " Object privateInnerObject = Test.getPrivateInner();", " }", "}") .testCanCompile(); } @Test public void methodOfPrivateSuperClassOfInnerPublicClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "Test.java", "package com.example.buck;", "public class Test {", " private class PrivateInner { ", " public void foo() { }", " }", " public class PublicInner extends PrivateInner { }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.Test;", "public class A {", " public void test() {", " Test test = new Test();", " Test.PublicInner inner = test.new PublicInner();", " inner.foo();", " }", "}") .testCanCompile(); } @Test public void bridgeMethodInStubsCanBeCompiledAgainst() throws IOException { notYetImplementedForMissingClasspath(); tester .setSourceFile( "TestGenericInterface.java", "package com.example.buck;", "public interface TestGenericInterface<T> {", " public int compare(T a, T b);", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "TestComparator.java", "package com.example.buck;", "import com.example.buck.TestGenericInterface;", "public class TestComparator implements TestGenericInterface<Integer> {", " public int compare(Integer a, Integer b) { return 1; }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.TestComparator;", "import com.example.buck.TestGenericInterface;", "public class A {", " public void test() {", " Object first = 1;", " Object second = 2;", " TestGenericInterface com = new TestComparator();", " int result = com.compare(first, second);", // Relies on bridge method " }", "}") .testCanCompile(); } @Test public void enumInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "TestEnum.java", "package com.example.buck;", "public enum TestEnum {", " Value1,", " Value2", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.TestEnum;", "public class A {", " TestEnum testEnum = TestEnum.Value1;", "}") .testCanCompile(); } @Test public void genericClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "GenericClass.java", "package com.example.buck;", "public class GenericClass<T> {", " public T theField;", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.GenericClass;", "public class A {", " public void test() {", " GenericClass<String> field = new GenericClass<>();", " field.theField = \"facebook\";", " }", "}") .testCanCompile(); } @Test public void classExtendingGenericClassInStubsCanBeCompiledAgainst() throws IOException { tester .setSourceFile( "GenericClass.java", "package com.example.buck;", "public class GenericClass<T> {", " public T theField;", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.GenericClass;", "public class A<T, M> extends GenericClass<T> {", " public M otherField;", " public A(T t, M m) {", " this.theField = t;", " this.otherField = m;", " }", "}") .testCanCompile(); } @Test public void privateConstructorResultsInCorrectCompileError() throws IOException { tester .setSourceFile( "PrivateTest.java", "package com.example.buck;", "public class PrivateTest {", " private PrivateTest() { }", "}") .createStubJar() .addStubJarToClasspath() .setSourceFile( "A.java", "package com.example.buck2;", "import com.example.buck.PrivateTest;", "public class A {", " public void foo() {", " PrivateTest test = new PrivateTest();", " }", "}") .addExpectedCompileError( Joiner.on('\n') .join( "A.java:5: error: PrivateTest() has private access in com.example.buck.PrivateTest", " PrivateTest test = new PrivateTest();", " ^")) .testCanCompile(); } @Test public void shouldPreserveSynchronizedKeywordOnMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public synchronized void doMagic() {}", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x21", " public synchronized doMagic()V", "}") .createAndCheckStubJar(); } @Test public void shouldKeepMultipleFieldsWithSameDescValue() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public static final A SEVERE = new A();", " public static final A NOT_SEVERE = new A();", " public static final A QUITE_MILD = new A();", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x19", " public final static Lcom/example/buck/A; SEVERE", "", " // access flags 0x19", " public final static Lcom/example/buck/A; NOT_SEVERE", "", " // access flags 0x19", " public final static Lcom/example/buck/A; QUITE_MILD", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void shouldNotStubClinit() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public static int i = 3;", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x9", " public static I i", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x8", " static <clinit>()V", // Should not stub this, even though it's default access "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x9", " public static I i", "", " // access flags 0x1", " public <init>()V", "}") .createAndCheckStubJar(); } @Test public void stubJarIsEquallyAtHomeWalkingADirectoryOfClassFiles() throws InterruptedException, IOException { Path fullJarPath = compileToJar( EMPTY_CLASSPATH, Collections.emptyList(), "A.java", Joiner.on("\n") .join( ImmutableList.of( "package com.example.buck;", "public class A {", " public String toString() { return null; }", " public void eatCake() {}", "}")), temp.newFolder()); Path classDir = temp.newFolder().toPath(); Unzip.extractZipFile(fullJarPath, classDir, Unzip.ExistingFileMode.OVERWRITE); Path stubJarPath = createStubJar(classDir); tester .setStubJar(stubJarPath) .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public toString()Ljava/lang/String;", "", " // access flags 0x1", " public eatCake()V", "}") .checkStubJar(); } @Test public void shouldNotIncludeSyntheticFields() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " public void method() {", " assert false;", " }", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1018", " final static synthetic Z $assertionsDisabled", // Should remove this field "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public method()V", "", " // access flags 0x8", " static <clinit>()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public method()V", "}") .createAndCheckStubJar(); } @Test public void shouldNotIncludeSyntheticClasses() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A {", " enum E { Value };", " public void method(E e) {", " switch (e) {", " case Value: break;", " }", " }", "}") .addExpectedStub( "com/example/buck/A$E", "// class version 52.0 (52)", "// access flags 0x4030", "// signature Ljava/lang/Enum<Lcom/example/buck/A$E;>;", "// declaration: com/example/buck/A$E extends java.lang.Enum<com.example.buck.A$E>", "final enum com/example/buck/A$E extends java/lang/Enum {", "", " // access flags 0x4018", " final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A$E; Value", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A$E;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A$E;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x1008", " static synthetic INNERCLASS com/example/buck/A$1 null null", // Should not include this class " // access flags 0x4018", " final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public method(Lcom/example/buck/A$E;)V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A {", "", " // access flags 0x4018", " final static enum INNERCLASS com/example/buck/A$E com/example/buck/A E", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public method(Lcom/example/buck/A$E;)V", "}") .createAndCheckStubJar(); } @Test public void shouldNotIncludeSyntheticMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public enum A {", " Value1 { }", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4021", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>", "public enum com/example/buck/A extends java/lang/Enum {", "", " // access flags 0x4008", " static enum INNERCLASS com/example/buck/A$1 null null", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x101A", " private final static synthetic [Lcom/example/buck/A; $VALUES", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " // signature ()V", " // declaration: void <init>()", " private <init>(Ljava/lang/String;I)V", "", " // access flags 0x1000", " synthetic <init>(Ljava/lang/String;ILcom/example/buck/A$1;)V", // Should not include this method "", " // access flags 0x8", " static <clinit>()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x4021", "// signature Ljava/lang/Enum<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A extends java.lang.Enum<com.example.buck.A>", "public enum com/example/buck/A extends java/lang/Enum {", "", "", " // access flags 0x4019", " public final static enum Lcom/example/buck/A; Value1", "", " // access flags 0x9", " public static values()[Lcom/example/buck/A;", "", " // access flags 0x9", " public static valueOf(Ljava/lang/String;)Lcom/example/buck/A;", "", " // access flags 0x2", " private <init>(Ljava/lang/String;I)V", "}") .createAndCheckStubJar(); } @Test public void shouldNotIncludeGenericBridgeMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A implements Comparable<A> {", " public int compareTo(A other) {", " return 0;", " }", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A implements java.lang.Comparable<com.example.buck.A>", "public class com/example/buck/A implements java/lang/Comparable {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public compareTo(Lcom/example/buck/A;)I", "", " // access flags 0x1041", " public synthetic bridge compareTo(Ljava/lang/Object;)I", // Should include this method "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "// signature Ljava/lang/Object;Ljava/lang/Comparable<Lcom/example/buck/A;>;", "// declaration: com/example/buck/A implements java.lang.Comparable<com.example.buck.A>", "public class com/example/buck/A implements java/lang/Comparable {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1", " public compareTo(Lcom/example/buck/A;)I", "}") .createAndCheckStubJar(); } /** * There's this fun case in javac where if a public class has a non-public superclass, all public * methods on the superclass get bridges in the subclass. Let's make sure */ @Test public void shouldNotIncludeNonPublicBaseClassBridgeMethods() throws IOException { tester .setSourceFile( "A.java", "package com.example.buck;", "public class A extends B {", "}", "class B {", " public void foo() {};", "}") .addExpectedFullAbi( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A extends com/example/buck/B {", "", "", " // access flags 0x1", " public <init>()V", "", " // access flags 0x1041", " public synthetic bridge foo()V", "}") .addExpectedStub( "com/example/buck/A", "// class version 52.0 (52)", "// access flags 0x21", "public class com/example/buck/A extends com/example/buck/B {", "", "", " // access flags 0x1", " public <init>()V", "}") .addExpectedStub( "com/example/buck/B", "// class version 52.0 (52)", "// access flags 0x20", "class com/example/buck/B {", "", "", " // access flags 0x0", " <init>()V", "", " // access flags 0x1", " public foo()V", "}") .createAndCheckStubJar(); } private Path createStubJar( SortedSet<Path> classpath, String fileName, String source, Path outputDir) throws IOException { Path stubJar = outputDir.resolve("stub.jar"); try (TestCompiler testCompiler = new TestCompiler()) { testCompiler.init(); testCompiler.useFrontendOnlyJavacTask(); testCompiler.addSourceFileContents(fileName, source); testCompiler.addClasspath(classpath); testCompiler.setProcessors( ImmutableList.of( new AbstractProcessor() { @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton("*"); } @Override public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { return false; } })); StubGenerator generator = new StubGenerator( SourceVersion.RELEASE_8, testCompiler.getElements(), testCompiler.getFileManager(), new DefaultBuckEventBus(new FakeClock(0), new BuildId())); testCompiler.addPostEnterCallback(generator::generate); testCompiler.compile(); testCompiler.getClasses().createJar(stubJar, true); } return stubJar; } private Path createStubJar(Path fullJar) throws IOException { Path stubJar = fullJar.getParent().resolve("stub.jar"); new StubJar(fullJar).setSourceAbiCompatible(true).writeTo(filesystem, stubJar); return stubJar; } private Path compileToJar( SortedSet<Path> classpath, List<Processor> processors, String fileName, String source, File outputDir) throws IOException { try (TestCompiler compiler = new TestCompiler()) { compiler.init(); compiler.addSourceFileContents(fileName, source); compiler.addClasspath(classpath); compiler.setProcessors(processors); compiler.compile(); Path jarPath = outputDir.toPath().resolve("output.jar"); compiler.getClasses().createJar(jarPath, false); return jarPath; } } private Tester createAnnotationFullJar() throws IOException { return tester .setSourceFile( "Foo.java", "package com.example.buck;", "import java.lang.annotation.*;", "import static java.lang.annotation.ElementType.*;", "@Retention(RetentionPolicy.RUNTIME)", "@Target(value={CONSTRUCTOR, FIELD, METHOD, PARAMETER, TYPE})", "public @interface Foo {", " int primitiveValue() default 0;", " String[] stringArrayValue() default {\"Hello\"};", " Retention annotationValue() default @Retention(RetentionPolicy.SOURCE);", " Retention[] annotationArrayValue() default {};", " RetentionPolicy enumValue () default RetentionPolicy.CLASS;", " Class typeValue() default Foo.class;", " @Target({TYPE_PARAMETER, TYPE_USE})", " @interface TypeAnnotation { }", "}") .compileFullJar(); } private void notYetImplementedForMissingClasspath() { assumeThat(testingMode, Matchers.not(Matchers.equalTo(MODE_SOURCE_BASED_MISSING_DEPS))); } private void notYetImplementedForSource() { assumeThat(testingMode, Matchers.equalTo(MODE_JAR_BASED)); } private final class Tester { private final List<String> expectedDirectory = new ArrayList<>(); private final List<String> actualDirectory = new ArrayList<>(); private final Map<String, List<String>> expectedFullAbis = new HashMap<>(); private final Map<String, List<String>> actualFullAbis = new HashMap<>(); private final Map<String, List<String>> expectedStubs = new HashMap<>(); private final Map<String, List<String>> actualStubs = new HashMap<>(); private final List<String> expectedCompileErrors = new ArrayList<>(); private String sourceFileName = ""; private String sourceFileContents = ""; private ImmutableSortedSet<Path> classpath = EMPTY_CLASSPATH; private Path stubJarPath; private Path fullJarPath; public Tester setSourceFile(String fileName, String... lines) { sourceFileName = fileName; sourceFileContents = Joiner.on('\n').join(lines); return this; } public Tester addExpectedFullAbi(String classBinaryName, String... abiLines) { String filePath = classBinaryName + ".class"; expectedFullAbis.put(filePath, Arrays.asList(abiLines)); return this; } public Tester addExpectedStub(String classBinaryName, String... stubLines) { String filePath = classBinaryName + ".class"; expectedDirectory.add(filePath); expectedStubs.put(filePath, Arrays.asList(stubLines)); return this; } public Tester addExpectedCompileError(String compileError) { expectedCompileErrors.add(compileError); return this; } public Tester setStubJar(Path stubJarPath) { this.stubJarPath = stubJarPath; return this; } public Tester createAndCheckStubJar() throws IOException { if (!expectedFullAbis.isEmpty()) { compileFullJar(); dumpFullJarAbi(); for (String entryName : expectedDirectory) { if (!expectedFullAbis.containsKey(entryName)) { // You don't have to put expectations for all full ABIs, only those that you feel really // need to be a certain way for the test to be valid. continue; } assertEquals( "Full ABI for " + entryName + " is not what was expected.", Joiner.on('\n').join(expectedFullAbis.get(entryName)), Joiner.on('\n').join(actualFullAbis.get(entryName))); } } createStubJar(); return checkStubJar(); } public Tester checkStubJar() throws IOException { dumpStubJar(); assertEquals("File list is not correct.", expectedDirectory, actualDirectory); for (String entryName : expectedDirectory) { assertEquals( "Stub for " + entryName + " is not correct", Joiner.on('\n').join(expectedStubs.get(entryName)), Joiner.on('\n').join(actualStubs.get(entryName))); } return this; } public Tester createStubJar() throws IOException { File outputDir = temp.newFolder(); if (testingMode != MODE_JAR_BASED) { stubJarPath = StubJarTest.this.createStubJar( testingMode == MODE_SOURCE_BASED ? classpath : Collections.emptySortedSet(), sourceFileName, sourceFileContents, outputDir.toPath()); } else { compileFullJar(); stubJarPath = StubJarTest.this.createStubJar(fullJarPath); } return this; } public Tester compileFullJar() throws IOException { File outputDir = temp.newFolder(); fullJarPath = compileToJar( classpath, Collections.emptyList(), sourceFileName, sourceFileContents, outputDir); return this; } public Tester addStubJarToClasspath() throws IOException { classpath = ImmutableSortedSet.<Path>naturalOrder().addAll(classpath).add(stubJarPath).build(); return this; } public Tester addFullJarToClasspath() throws IOException { classpath = ImmutableSortedSet.<Path>naturalOrder().addAll(classpath).add(fullJarPath).build(); return this; } public void testCanCompile() throws IOException { File outputDir = temp.newFolder(); try (TestCompiler compiler = new TestCompiler()) { compiler.init(); compiler.addSourceFileContents(sourceFileName, sourceFileContents); compiler.addClasspath(classpath); compiler.setProcessors(Collections.emptyList()); compiler.setAllowCompilationErrors(!expectedCompileErrors.isEmpty()); compiler.compile(); if (!expectedCompileErrors.isEmpty()) { List<String> actualCompileErrors = compiler .getDiagnosticMessages() .stream() .map( diagnostic -> diagnostic.substring(diagnostic.lastIndexOf(File.separatorChar) + 1)) .collect(Collectors.toList()); assertEquals(expectedCompileErrors, actualCompileErrors); } fullJarPath = outputDir.toPath().resolve("output.jar"); compiler.getClasses().createJar(fullJarPath, false); } } public void assertStubJarIsIdentical() throws IOException { Sha1HashCode originalHash = filesystem.computeSha1(stubJarPath); createStubJar(); assertEquals(originalHash, filesystem.computeSha1(stubJarPath)); } public void assertStubJarIsDifferent() throws IOException { Sha1HashCode originalHash = filesystem.computeSha1(stubJarPath); createStubJar(); assertNotEquals(originalHash, filesystem.computeSha1(stubJarPath)); } @SuppressWarnings("unused") public Tester dumpTestCode(boolean includeFullAbi) throws IOException { if (includeFullAbi) { compileFullJar(); dumpFullJarAbi(); } createStubJar(); dumpStubJar(); String indent = " "; StringBuilder result = new StringBuilder(); result.append("Test lines:\n"); result.append(" tester\n"); result.append(" .setSourceFile(\n"); result.append(indent); result.append('"'); result.append(sourceFileName); for (String sourceLine : sourceFileContents.split("\n")) { result.append("\",\n"); result.append(indent); result.append('"'); result.append(sourceLine.replace("\"", "\\\"")); } result.append("\")\n"); for (String fileName : actualDirectory) { if (includeFullAbi) { result.append(" .addExpectedFullAbi(\n"); result.append(indent); result.append('"'); result.append(fileName.substring(0, fileName.length() - ".class".length())); for (String abiLine : actualFullAbis.get(fileName)) { result.append("\",\n"); result.append(indent); result.append('"'); result.append(abiLine.replace("\"", "\\\"")); } result.append("\")\n"); } result.append(" .addExpectedStub(\n"); result.append(indent); result.append('"'); result.append(fileName.substring(0, fileName.length() - ".class".length())); for (String stubLine : actualStubs.get(fileName)) { result.append("\",\n"); result.append(indent); result.append('"'); result.append(stubLine.replace("\"", "\\\"")); } result.append("\")\n"); } result.append(" .createAndCheckStubJar();\n"); fail(result.toString()); return this; } protected void dumpStubJar() throws IOException { try (JarFile file = new JarFile(stubJarPath.toFile())) { Iterable<JarEntry> entries = file.stream()::iterator; for (JarEntry entry : entries) { String name = entry.getName(); if (JarFile.MANIFEST_NAME.equals(name)) { continue; } actualDirectory.add(name); actualStubs.put( name, new JarDumper().dumpEntry(file, entry).collect(Collectors.toList())); } } } protected void dumpFullJarAbi() throws IOException { try (JarFile file = new JarFile(fullJarPath.toFile())) { Iterable<JarEntry> entries = file.stream()::iterator; for (JarEntry entry : entries) { String name = entry.getName(); if (JarFile.MANIFEST_NAME.equals(name)) { continue; } actualFullAbis.put( name, new JarDumper() .setAsmFlags( ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) .dumpEntry(file, entry) .collect(Collectors.toList())); } } } } }