aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java')
-rw-r--r--src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java832
1 files changed, 832 insertions, 0 deletions
diff --git a/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java
new file mode 100644
index 0000000000..f80dec42d0
--- /dev/null
+++ b/src/java_tools/buildjar/javatests/com/google/devtools/build/java/turbine/javac/JavacTurbineTest.java
@@ -0,0 +1,832 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Function;
+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.common.collect.Iterables;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.java.turbine.TurbineOptions;
+import com.google.devtools.build.java.turbine.javac.JavacTurbine.Result;
+import com.google.devtools.build.lib.view.proto.Deps;
+import com.google.devtools.build.lib.view.proto.Deps.Dependency;
+
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.api.ClientCodeWrapper.Trusted;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.util.Context;
+
+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;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.BufferedInputStream;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+
+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.Element;
+import javax.lang.model.element.TypeElement;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardLocation;
+
+/** Unit tests for {@link JavacTurbine}. */
+@RunWith(JUnit4.class)
+public class JavacTurbineTest {
+
+ @Rule public TemporaryFolder temp = new TemporaryFolder();
+
+ Path sourcedir;
+ List<Path> sources;
+ Path tempdir;
+ Path output;
+ Path outputDeps;
+
+ TurbineOptions.Builder optionsBuilder = TurbineOptions.builder();
+
+ @Before
+ public void setUp() throws IOException {
+ sourcedir = temp.newFolder().toPath();
+ tempdir = temp.newFolder("_temp").toPath();
+ output = temp.newFile("out.jar").toPath();
+ outputDeps = temp.newFile("out.jdeps").toPath();
+
+ sources = new ArrayList<>();
+
+ optionsBuilder
+ .setOutput(output.toString())
+ .setTempDir(tempdir.toString())
+ .addBootClassPathEntries(
+ ImmutableList.copyOf(Splitter.on(':').split(System.getProperty("sun.boot.class.path"))))
+ .setOutputDeps(outputDeps.toString())
+ .setStrictJavaDeps("ERROR")
+ .addAllJavacOpts(Arrays.asList("-source", "7", "-target", "7"))
+ .setTargetLabel("//test")
+ .setRuleKind("java_library");
+ }
+
+ private void addSourceLines(String path, String... lines) throws IOException {
+ Path source = sourcedir.resolve(path);
+ sources.add(source);
+ Files.write(source, Arrays.asList(lines), StandardCharsets.UTF_8);
+ }
+
+ void compile() throws IOException {
+ optionsBuilder.addSources(ImmutableList.copyOf(Iterables.transform(sources, TO_STRING)));
+ try (JavacTurbine turbine = new JavacTurbine(optionsBuilder.build())) {
+ assertThat(turbine.compile()).isEqualTo(Result.OK_WITH_REDUCED_CLASSPATH);
+ }
+ }
+
+ private Map<String, byte[]> collectOutputs() throws IOException {
+ return collectFiles(output);
+ }
+
+ @Test
+ public void hello() throws Exception {
+ addSourceLines(
+ "Hello.java",
+ "class Hello {",
+ " public static void main(String[] args) {",
+ " System.err.println(\"Hello World\");",
+ " }",
+ "}");
+
+ compile();
+
+ Map<String, byte[]> outputs = collectOutputs();
+
+ assertThat(outputs.keySet()).containsExactly("Hello.class");
+
+ String text = textify(outputs.get("Hello.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x20",
+ "class Hello {",
+ "",
+ "",
+ " // access flags 0x0",
+ " <init>()V",
+ "",
+ " // access flags 0x9",
+ " public static main([Ljava/lang/String;)V",
+ "}",
+ ""
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+
+ // verify that FLOW is disabled, as if we had passed -relax
+ // if it isn't we'd get an error about the missing return in f().
+ @Test
+ public void relax() throws Exception {
+ addSourceLines("Hello.java", "class Hello {", " int f() {}", "}");
+
+ compile();
+
+ Map<String, byte[]> outputs = collectOutputs();
+
+ assertThat(outputs.keySet()).containsExactly("Hello.class");
+
+ String text = textify(outputs.get("Hello.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x20",
+ "class Hello {",
+ "",
+ "",
+ " // access flags 0x0",
+ " <init>()V",
+ "",
+ " // access flags 0x0",
+ " f()I",
+ "}",
+ ""
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+
+ public @interface MyAnnotation {}
+
+ /**
+ * A sample annotation processor for testing.
+ *
+ * <p>Writes two output files (one source, one data) the very first round it's called. Used to
+ * verify that annotation processor output is collected into the output jar.
+ */
+ @SupportedAnnotationTypes("MyAnnotation")
+ public static class MyProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ boolean first = true;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (!first) {
+ // Write the output files exactly once to ensure we don't try to write the same file
+ // twice or do work on the final round.
+ return false;
+ }
+ if (roundEnv.getRootElements().isEmpty()) {
+ return false;
+ }
+ first = false;
+ Element element = roundEnv.getRootElements().iterator().next();
+ try {
+ JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile("Generated", element);
+ try (OutputStream os = sourceFile.openOutputStream()) {
+ os.write("class Generated {}".getBytes(StandardCharsets.UTF_8));
+ }
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ try {
+ FileObject file =
+ processingEnv
+ .getFiler()
+ .createResource(StandardLocation.CLASS_OUTPUT, "com.foo", "hello.txt", element);
+ try (OutputStream os = file.openOutputStream()) {
+ os.write("hello".getBytes(StandardCharsets.UTF_8));
+ }
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void processing() throws Exception {
+ addSourceLines("MyAnnotation.java", "public @interface MyAnnotation {}");
+ addSourceLines(
+ "Hello.java",
+ "@MyAnnotation",
+ "class Hello {",
+ " public static void main(String[] args) {",
+ " System.err.println(\"Hello World\");",
+ " }",
+ "}");
+
+ optionsBuilder.setProcessors(ImmutableList.of(MyProcessor.class.getName()));
+ optionsBuilder.addProcessorPathEntries(
+ ImmutableList.copyOf(Splitter.on(':').split(System.getProperty("java.class.path"))));
+ optionsBuilder.addClassPathEntries(
+ ImmutableList.copyOf(Splitter.on(':').split(System.getProperty("java.class.path"))));
+
+ compile();
+
+ Map<String, byte[]> outputs = collectOutputs();
+ assertThat(outputs.keySet())
+ .containsExactly(
+ "Generated.class", "MyAnnotation.class", "Hello.class", "com/foo/hello.txt");
+
+ {
+ String text = textify(outputs.get("Generated.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x20",
+ "class Generated {",
+ "",
+ "",
+ " // access flags 0x0",
+ " <init>()V",
+ "}",
+ ""
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+
+ // sanity-check that annotation processing doesn't interfere with stripping
+ {
+ String text = textify(outputs.get("Hello.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x20",
+ "class Hello {",
+ "",
+ "",
+ " @LMyAnnotation;() // invisible",
+ "",
+ " // access flags 0x0",
+ " <init>()V",
+ "",
+ " // access flags 0x9",
+ " public static main([Ljava/lang/String;)V",
+ "}",
+ ""
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+ }
+
+ static Map<String, byte[]> collectFiles(Path jar) throws IOException {
+ Map<String, byte[]> files = new LinkedHashMap<>();
+ try (JarFile jf = new JarFile(jar.toFile())) {
+ Enumeration<JarEntry> entries = jf.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ files.put(entry.getName(), ByteStreams.toByteArray(jf.getInputStream(entry)));
+ }
+ }
+ return files;
+ }
+
+ static String textify(byte[] bytes) {
+ StringWriter sw = new StringWriter();
+ ClassReader cr = new ClassReader(bytes);
+ cr.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(sw, true)), 0);
+ return sw.toString();
+ }
+
+ private static final Function<Object, String> TO_STRING =
+ new Function<Object, String>() {
+ @Override
+ public String apply(Object input) {
+ return String.valueOf(input);
+ }
+ };
+
+ @Test
+ public void extractLanguageLevel() {
+ ImmutableList.Builder<String> output = ImmutableList.builder();
+ JavacTurbine.addLanguageLevel(
+ output,
+ Arrays.asList(
+ "-g", "-source", "6", "-target", "6", "-Xlint:all", "-source", "7", "-target", "7"));
+ assertThat(output.build())
+ .containsExactly("-source", "6", "-target", "6", "-source", "7", "-target", "7")
+ .inOrder();
+ }
+
+ @Test
+ public void jdeps() throws Exception {
+
+ Path libC = temp.newFile("libc.jar").toPath();
+ compileLib(
+ libC,
+ Collections.<Path>emptyList(),
+ Arrays.asList(new StringJavaFileObject("C.java", "interface C { String getString(); }")));
+
+ Path libA = temp.newFile("liba.jar").toPath();
+ compileLib(
+ libA,
+ Collections.singleton(libC),
+ Arrays.asList(new StringJavaFileObject("A.java", "interface A { C getC(); }")));
+
+ Path depsA =
+ writedeps(
+ "liba.jdeps",
+ Deps.Dependencies.newBuilder()
+ .setSuccess(true)
+ .setRuleLabel("//lib:a")
+ .addDependency(
+ Deps.Dependency.newBuilder()
+ .setPath(libC.toString())
+ .setKind(Deps.Dependency.Kind.EXPLICIT))
+ .build());
+
+ Path libB = temp.newFile("libb.jar").toPath();
+ compileLib(
+ libB,
+ Collections.<Path>emptyList(),
+ Arrays.asList(new StringJavaFileObject("B.java", "interface B {}")));
+
+ optionsBuilder.addClassPathEntries(
+ ImmutableList.of(libA.toString(), libB.toString(), libC.toString()));
+ optionsBuilder.addAllDepsArtifacts(ImmutableList.of(depsA.toString()));
+ optionsBuilder.addDirectJarToTarget(libA.toString(), "//lib:a");
+ optionsBuilder.addDirectJarToTarget(libB.toString(), "//lib:b");
+ optionsBuilder.addIndirectJarToTarget(libC.toString(), "//lib:c");
+ optionsBuilder.setTargetLabel("//my:target");
+
+ addSourceLines(
+ "Hello.java",
+ "class Hello {",
+ " public static A a = null;",
+ " public static String s = a.getC().getString();",
+ " public static void main(String[] args) {",
+ " B b = null;",
+ " }",
+ "}");
+
+ compile();
+
+ Deps.Dependencies depsProto = getDeps();
+
+ assertThat(depsProto.getSuccess()).isTrue();
+ assertThat(depsProto.getRuleLabel()).isEqualTo("//my:target");
+ assertThat(getEntries(depsProto))
+ .containsExactlyEntriesIn(
+ ImmutableMap.of(
+ libA.toString(), Deps.Dependency.Kind.EXPLICIT,
+ libB.toString(), Deps.Dependency.Kind.IMPLICIT,
+ libC.toString(), Deps.Dependency.Kind.IMPLICIT));
+ }
+
+ private Map<String, Deps.Dependency.Kind> getEntries(Deps.Dependencies deps) {
+ Map<String, Deps.Dependency.Kind> result = new LinkedHashMap<>();
+ for (Dependency dep : deps.getDependencyList()) {
+ result.put(dep.getPath(), dep.getKind());
+ }
+ return result;
+ }
+
+ private Deps.Dependencies getDeps() throws IOError {
+ Deps.Dependencies depsProto;
+ try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(outputDeps))) {
+ Deps.Dependencies.Builder builder = Deps.Dependencies.newBuilder();
+ builder.mergeFrom(in);
+ depsProto = builder.build();
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ return depsProto;
+ }
+
+ private void compileLib(
+ Path jar, Iterable<Path> classpath, Iterable<? extends JavaFileObject> units)
+ throws IOException {
+ final Path outdir = temp.newFolder().toPath();
+ JavacFileManager fm = new JavacFileManager(new Context(), false, StandardCharsets.UTF_8);
+ fm.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singleton(outdir));
+ fm.setLocationFromPaths(StandardLocation.CLASS_PATH, classpath);
+ List<String> options = Arrays.asList("-d", outdir.toString());
+ JavacTool tool = JavacTool.create();
+
+ JavacTask task =
+ tool.getTask(new PrintWriter(System.err, true), fm, null, options, null, units);
+ assertThat(task.call()).isTrue();
+
+ try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jar))) {
+ Files.walkFileTree(
+ outdir,
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
+ throws IOException {
+ JarEntry je = new JarEntry(outdir.relativize(path).toString());
+ jos.putNextEntry(je);
+ Files.copy(path, jos);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ }
+
+ @Trusted
+ static class StringJavaFileObject extends SimpleJavaFileObject {
+ private final String content;
+
+ StringJavaFileObject(String name, String... lines) {
+ super(URI.create(name), JavaFileObject.Kind.SOURCE);
+ this.content = Joiner.on('\n').join(lines);
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return content;
+ }
+ }
+
+ @Test
+ public void reducedClasspath() throws Exception {
+
+ Path libD = temp.newFile("libd.jar").toPath();
+ compileLib(
+ libD,
+ Collections.<Path>emptyList(),
+ Arrays.asList(new StringJavaFileObject("D.java", "public class D {}")));
+
+ Path libC = temp.newFile("libc.jar").toPath();
+ compileLib(
+ libC,
+ Collections.singleton(libD),
+ Arrays.asList(new StringJavaFileObject("C.java", "class C { static D d; }")));
+
+ Path libB = temp.newFile("libb.jar").toPath();
+ compileLib(
+ libB,
+ Arrays.asList(libC, libD),
+ Arrays.asList(new StringJavaFileObject("B.java", "class B { static C c; }")));
+
+ Path libA = temp.newFile("liba.jar").toPath();
+ compileLib(
+ libA,
+ Arrays.asList(libB, libC, libD),
+ Arrays.asList(new StringJavaFileObject("A.java", "class A { static B b; }")));
+ Path depsA =
+ writedeps(
+ "liba.jdeps",
+ Deps.Dependencies.newBuilder()
+ .setSuccess(true)
+ .setRuleLabel("//lib:a")
+ .addDependency(
+ Deps.Dependency.newBuilder()
+ .setPath(libB.toString())
+ .setKind(Deps.Dependency.Kind.EXPLICIT))
+ .build());
+
+ optionsBuilder.addClassPathEntries(
+ ImmutableList.of(libA.toString(), libB.toString(), libC.toString(), libD.toString()));
+ optionsBuilder.addAllDepsArtifacts(ImmutableList.of(depsA.toString()));
+ optionsBuilder.addDirectJarToTarget(libA.toString(), "//lib:a");
+ optionsBuilder.addIndirectJarToTarget(libB.toString(), "//lib:b");
+ optionsBuilder.addIndirectJarToTarget(libC.toString(), "//lib:c");
+ optionsBuilder.addIndirectJarToTarget(libD.toString(), "//lib:d");
+ optionsBuilder.setTargetLabel("//my:target");
+
+ addSourceLines(
+ "Hello.java",
+ "class Hello {",
+ " public static A a = new A();",
+ " public static void main(String[] args) {",
+ " A a = null;",
+ " B b = null;",
+ " C c = null;",
+ " D d = null;",
+ " }",
+ "}");
+
+ optionsBuilder.addSources(ImmutableList.copyOf(Iterables.transform(sources, TO_STRING)));
+
+ try (JavacTurbine turbine = new JavacTurbine(optionsBuilder.build())) {
+ assertThat(turbine.compile()).isEqualTo(Result.OK_WITH_REDUCED_CLASSPATH);
+ Context context = turbine.context;
+
+ JavacFileManager fm = (JavacFileManager) context.get(JavaFileManager.class);
+ assertThat(fm.getLocationAsPaths(StandardLocation.CLASS_PATH)).containsExactly(libA, libB);
+
+ Deps.Dependencies depsProto = getDeps();
+
+ assertThat(depsProto.getSuccess()).isTrue();
+ assertThat(depsProto.getRuleLabel()).isEqualTo("//my:target");
+ assertThat(getEntries(depsProto))
+ .containsExactlyEntriesIn(
+ ImmutableMap.of(
+ libA.toString(),
+ Deps.Dependency.Kind.EXPLICIT,
+ libB.toString(),
+ Deps.Dependency.Kind.IMPLICIT));
+ }
+ }
+
+ Path writedeps(String name, Deps.Dependencies deps) throws IOException {
+ Path path = temp.newFile(name).toPath();
+ try (OutputStream os = Files.newOutputStream(path)) {
+ deps.writeTo(os);
+ }
+ return path;
+ }
+
+ @Test
+ public void reducedClasspathFallback() throws Exception {
+
+ Path libD = temp.newFile("libd.jar").toPath();
+ compileLib(
+ libD,
+ Collections.<Path>emptyList(),
+ Arrays.asList(
+ new StringJavaFileObject("D.java", "public class D { static final int CONST = 42; }")));
+
+ Path libC = temp.newFile("libc.jar").toPath();
+ compileLib(
+ libC,
+ Collections.singleton(libD),
+ Arrays.asList(new StringJavaFileObject("C.java", "class C extends D {}")));
+
+ Path libB = temp.newFile("libb.jar").toPath();
+ compileLib(
+ libB,
+ Arrays.asList(libC, libD),
+ Arrays.asList(new StringJavaFileObject("B.java", "class B extends C {}")));
+
+ Path libA = temp.newFile("liba.jar").toPath();
+ compileLib(
+ libA,
+ Arrays.asList(libB, libC, libD),
+ Arrays.asList(new StringJavaFileObject("A.java", "class A extends B {}")));
+ Path depsA =
+ writedeps(
+ "liba.jdeps",
+ Deps.Dependencies.newBuilder()
+ .setSuccess(true)
+ .setRuleLabel("//lib:a")
+ .addDependency(
+ Deps.Dependency.newBuilder()
+ .setPath(libB.toString())
+ .setKind(Deps.Dependency.Kind.EXPLICIT))
+ .build());
+
+ optionsBuilder.addClassPathEntries(
+ ImmutableList.of(libA.toString(), libB.toString(), libC.toString(), libD.toString()));
+ optionsBuilder.addAllDepsArtifacts(ImmutableList.of(depsA.toString()));
+ optionsBuilder.addDirectJarToTarget(libA.toString(), "//lib:a");
+ optionsBuilder.addIndirectJarToTarget(libB.toString(), "//lib:b");
+ optionsBuilder.addIndirectJarToTarget(libC.toString(), "//lib:c");
+ optionsBuilder.addIndirectJarToTarget(libD.toString(), "//lib:d");
+ optionsBuilder.setTargetLabel("//my:target");
+
+ addSourceLines(
+ "Hello.java",
+ "class Hello {",
+ " public static final int CONST = A.CONST;",
+ " public static void main(String[] args) {}",
+ "}");
+
+ optionsBuilder.addSources(ImmutableList.copyOf(Iterables.transform(sources, TO_STRING)));
+
+ try (JavacTurbine turbine = new JavacTurbine(optionsBuilder.build())) {
+ assertThat(turbine.compile()).isEqualTo(Result.OK_WITH_FULL_CLASSPATH);
+ Context context = turbine.context;
+
+ JavacFileManager fm = (JavacFileManager) context.get(JavaFileManager.class);
+ assertThat(fm.getLocationAsPaths(StandardLocation.CLASS_PATH))
+ .containsExactly(libA, libB, libC, libD);
+
+ Deps.Dependencies depsProto = getDeps();
+
+ assertThat(depsProto.getSuccess()).isTrue();
+ assertThat(depsProto.getRuleLabel()).isEqualTo("//my:target");
+ assertThat(getEntries(depsProto))
+ .containsExactlyEntriesIn(
+ ImmutableMap.of(
+ libA.toString(), Deps.Dependency.Kind.EXPLICIT,
+ libB.toString(), Deps.Dependency.Kind.IMPLICIT,
+ libC.toString(), Deps.Dependency.Kind.IMPLICIT,
+ libD.toString(), Deps.Dependency.Kind.IMPLICIT));
+ }
+ }
+
+ @Test
+ public void constants() throws Exception {
+ addSourceLines(
+ "Const.java",
+ "class Const {",
+ " public static final int A = 42;",
+ " public static final int B = 42 + 42;",
+ " public static final int C = new Integer(42);",
+ " public static final int D = 42 + new Integer(42);",
+ " public static final Integer E = 42;",
+ " public static final String F = \"42\";",
+ " public static final java.lang.String G = \"42\";",
+ "}");
+
+ compile();
+
+ Map<String, byte[]> outputs = collectOutputs();
+
+ assertThat(outputs.keySet()).containsExactly("Const.class");
+
+ String text = textify(outputs.get("Const.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x20",
+ "class Const {",
+ "",
+ "",
+ " // access flags 0x19",
+ " public final static I A = 42",
+ "",
+ " // access flags 0x19",
+ " public final static I B = 84",
+ "",
+ " // access flags 0x19",
+ " public final static I C",
+ "",
+ " // access flags 0x19",
+ " public final static I D",
+ "",
+ " // access flags 0x19",
+ " public final static Ljava/lang/Integer; E",
+ "",
+ " // access flags 0x19",
+ " public final static Ljava/lang/String; F = \"42\"",
+ "",
+ " // access flags 0x19",
+ " public final static Ljava/lang/String; G = \"42\"",
+ "",
+ " // access flags 0x0",
+ " <init>()V",
+ "}",
+ "",
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+
+ @Test
+ public void constantsEnum() throws Exception {
+ addSourceLines(
+ "TheEnum.java",
+ // TODO(cushon): fix google-java-format's handling of lists of string literals
+ "public enum TheEnum {",
+ " ONE, TWO, THREE;",
+ "}");
+
+ compile();
+ Map<String, byte[]> outputs = collectOutputs();
+ // just don't crash; enum constants need to be preserved
+ assertThat(outputs.keySet()).containsExactly("TheEnum.class");
+
+ String text = textify(outputs.get("TheEnum.class"));
+ String[] expected = {
+ "// class version 51.0 (51)",
+ "// access flags 0x4031",
+ "// signature Ljava/lang/Enum<LTheEnum;>;",
+ "// declaration: TheEnum extends java.lang.Enum<TheEnum>",
+ "public final enum TheEnum extends java/lang/Enum {",
+ "",
+ "",
+ " // access flags 0x4019",
+ " public final static enum LTheEnum; ONE",
+ "",
+ " // access flags 0x4019",
+ " public final static enum LTheEnum; TWO",
+ "",
+ " // access flags 0x4019",
+ " public final static enum LTheEnum; THREE",
+ "",
+ " // access flags 0x101A",
+ " private final static synthetic [LTheEnum; $VALUES",
+ "",
+ " // access flags 0x9",
+ " public static values()[LTheEnum;",
+ "",
+ " // access flags 0x9",
+ " public static valueOf(Ljava/lang/String;)LTheEnum;",
+ "",
+ " // access flags 0x2",
+ " // signature ()V",
+ " // declaration: void <init>()",
+ " private <init>(Ljava/lang/String;I)V",
+ "",
+ " // access flags 0x8",
+ " static <clinit>()V",
+ "}",
+ ""
+ };
+ assertThat(text).isEqualTo(Joiner.on('\n').join(expected));
+ }
+
+ /**
+ * A sample annotation processor for testing.
+ *
+ * <p>Writes an output file that isn't valid UTF-8 to test handling of encoding errors.
+ */
+ @SupportedAnnotationTypes("MyAnnotation")
+ public static class MyBadEncodingProcessor extends AbstractProcessor {
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ boolean first = true;
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (!first) {
+ return false;
+ }
+ if (roundEnv.getRootElements().isEmpty()) {
+ return false;
+ }
+ first = false;
+ Element element = roundEnv.getRootElements().iterator().next();
+ try {
+ JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile("Generated", element);
+ try (OutputStream os = sourceFile.openOutputStream()) {
+ os.write(
+ "class Generated { public static String x = \"".getBytes(StandardCharsets.UTF_8));
+ os.write(0xc2); // write an unpaired surrogate
+ os.write("\";}}".getBytes(StandardCharsets.UTF_8));
+ }
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ return false;
+ }
+ }
+
+ @Test
+ public void badEncoding() throws Exception {
+ addSourceLines("MyAnnotation.java", "public @interface MyAnnotation {}");
+ addSourceLines(
+ "Hello.java",
+ "@MyAnnotation",
+ "class Hello {",
+ " public static void main(String[] args) {",
+ " System.err.println(\"Hello World\");",
+ " }",
+ "}");
+
+ optionsBuilder.setProcessors(ImmutableList.of(MyBadEncodingProcessor.class.getName()));
+ optionsBuilder.addProcessorPathEntries(
+ ImmutableList.copyOf(Splitter.on(':').split(System.getProperty("java.class.path"))));
+ optionsBuilder.addClassPathEntries(
+ ImmutableList.copyOf(Splitter.on(':').split(System.getProperty("java.class.path"))));
+
+ optionsBuilder.addSources(ImmutableList.copyOf(Iterables.transform(sources, TO_STRING)));
+ try (StringWriter sw = new StringWriter();
+ JavacTurbine turbine =
+ new JavacTurbine(new PrintWriter(sw, true), optionsBuilder.build())) {
+ Result result = turbine.compile();
+ assertThat(result).isEqualTo(Result.ERROR);
+ assertThat(sw.toString()).contains("error reading");
+ }
+ }
+}