• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.turbine.processing;
18 
19 import static com.google.common.collect.ImmutableList.toImmutableList;
20 import static com.google.common.truth.Truth.assertThat;
21 import static java.util.Arrays.stream;
22 import static org.junit.Assert.fail;
23 
24 import com.google.common.base.Joiner;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.collect.ObjectArrays;
29 import com.google.common.truth.Expect;
30 import com.google.turbine.binder.Binder;
31 import com.google.turbine.binder.ClassPathBinder;
32 import com.google.turbine.binder.bound.TypeBoundClass;
33 import com.google.turbine.binder.env.CompoundEnv;
34 import com.google.turbine.binder.env.Env;
35 import com.google.turbine.binder.env.SimpleEnv;
36 import com.google.turbine.binder.sym.ClassSymbol;
37 import com.google.turbine.diag.SourceFile;
38 import com.google.turbine.lower.IntegrationTestSupport;
39 import com.google.turbine.lower.IntegrationTestSupport.TestInput;
40 import com.google.turbine.parse.Parser;
41 import com.google.turbine.processing.TurbineElement.TurbineTypeElement;
42 import com.google.turbine.testing.TestClassPaths;
43 import com.google.turbine.tree.Tree.CompUnit;
44 import com.sun.source.util.JavacTask;
45 import com.sun.source.util.TaskEvent;
46 import com.sun.source.util.TaskListener;
47 import java.io.IOException;
48 import java.util.ArrayDeque;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Optional;
54 import javax.lang.model.element.Element;
55 import javax.lang.model.element.ElementKind;
56 import javax.lang.model.element.Name;
57 import javax.lang.model.element.TypeElement;
58 import javax.lang.model.util.ElementScanner8;
59 import javax.lang.model.util.Elements;
60 import javax.tools.DiagnosticCollector;
61 import javax.tools.JavaFileObject;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 import org.junit.runners.Parameterized;
66 import org.junit.runners.Parameterized.Parameters;
67 
68 @RunWith(Parameterized.class)
69 public class TurbineElementsHidesTest {
70 
71   @Rule public final Expect expect = Expect.create();
72 
73   @Parameters
parameters()74   public static Iterable<TestInput[]> parameters() {
75     // An array of test inputs. Each element is an array of lines of sources to compile.
76     String[][] inputs = {
77       {
78         "=== A.java ===", //
79         "abstract class A {",
80         "  int f;",
81         "  static int f() { return 1; }",
82         "  static int f(int x) { return 1; }",
83         "}",
84         "=== B.java ===",
85         "abstract class B extends A {",
86         "  int f;",
87         "  int g;",
88         "  static int f() { return 1; }",
89         "  static int f(int x) { return 1; }",
90         "  static int g() { return 1; }",
91         "  static int g(int x) { return 1; }",
92         "}",
93         "=== C.java ===",
94         "abstract class C extends B {",
95         "  int f;",
96         "  int g;",
97         "  int h;",
98         "  static int f() { return 1; }",
99         "  static int g() { return 1; }",
100         "  static int h() { return 1; }",
101         "  static int f(int x) { return 1; }",
102         "  static int g(int x) { return 1; }",
103         "  static int h(int x) { return 1; }",
104         "}",
105       },
106       {
107         "=== A.java ===",
108         "class A {",
109         "  class I {",
110         "  }",
111         "}",
112         "=== B.java ===",
113         "class B extends A {",
114         "  class I extends A.I {",
115         "  }",
116         "}",
117         "=== C.java ===",
118         "class C extends B {",
119         "  class I extends B.I {",
120         "  }",
121         "}",
122       },
123       {
124         "=== A.java ===",
125         "class A {",
126         "  class I {",
127         "  }",
128         "}",
129         "=== B.java ===",
130         "class B extends A {",
131         "  interface I {}",
132         "}",
133         "=== C.java ===",
134         "class C extends B {",
135         "  @interface I {}",
136         "}",
137       },
138       {
139         // the containing class or interface of Intf.foo is an interface
140         "=== Outer.java ===",
141         "class Outer {",
142         "  static class Inner {",
143         "    static void foo() {}",
144         "    static class Innerer extends Inner {",
145         "      interface Intf {",
146         "        static void foo() {}",
147         "      }",
148         "    }",
149         "  }",
150         "}",
151       },
152       {
153         // test two top-level classes with the same name
154         "=== one/A.java ===",
155         "package one;",
156         "public class A {",
157         "}",
158         "=== two/A.java ===",
159         "package two;",
160         "public class A {",
161         "}",
162       },
163     };
164     // https://bugs.openjdk.java.net/browse/JDK-8275746
165     if (Runtime.version().feature() >= 11) {
166       inputs =
167           ObjectArrays.concat(
168               inputs,
169               new String[][] {
170                 {
171                   // interfaces
172                   "=== A.java ===",
173                   "interface A {",
174                   "  static void f() {}",
175                   "  int x = 42;",
176                   "}",
177                   "=== B.java ===",
178                   "interface B extends A {",
179                   "  static void f() {}",
180                   "  int x = 42;",
181                   "}",
182                 }
183               },
184               String[].class);
185     }
186     return stream(inputs)
187         .map(input -> TestInput.parse(Joiner.on('\n').join(input)))
188         .map(x -> new TestInput[] {x})
189         .collect(toImmutableList());
190   }
191 
192   private final TestInput input;
193 
TurbineElementsHidesTest(TestInput input)194   public TurbineElementsHidesTest(TestInput input) {
195     this.input = input;
196   }
197 
198   // Compile the test inputs with javac and turbine, and assert that 'hides' returns the same
199   // results under each implementation.
200   @Test
test()201   public void test() throws Exception {
202     HidesTester javac = runJavac();
203     HidesTester turbine = runTurbine();
204     assertThat(javac.keys()).containsExactlyElementsIn(turbine.keys());
205     for (String k1 : javac.keys()) {
206       for (String k2 : javac.keys()) {
207         expect
208             .withMessage("hides(%s, %s)", k1, k2)
209             .that(javac.test(k1, k2))
210             .isEqualTo(turbine.test(k1, k2));
211       }
212     }
213   }
214 
215   static class HidesTester {
216     // The elements for a particular annotation processing implementation
217     final Elements elements;
218     // A collection of Elements to use as test inputs, keyed by unique strings that can be used to
219     // compare them across processing implementations
220     final ImmutableMap<String, Element> inputs;
221 
HidesTester(Elements elements, ImmutableMap<String, Element> inputs)222     HidesTester(Elements elements, ImmutableMap<String, Element> inputs) {
223       this.elements = elements;
224       this.inputs = inputs;
225     }
226 
test(String k1, String k2)227     boolean test(String k1, String k2) {
228       return elements.hides(inputs.get(k1), inputs.get(k2));
229     }
230 
keys()231     public ImmutableSet<String> keys() {
232       return inputs.keySet();
233     }
234   }
235 
236   /** Compiles the test input with turbine. */
runTurbine()237   private HidesTester runTurbine() throws IOException {
238     ImmutableList<CompUnit> units =
239         input.sources.entrySet().stream()
240             .map(e -> new SourceFile(e.getKey(), e.getValue()))
241             .map(Parser::parse)
242             .collect(toImmutableList());
243     Binder.BindingResult bound =
244         Binder.bind(
245             units,
246             ClassPathBinder.bindClasspath(ImmutableList.of()),
247             TestClassPaths.TURBINE_BOOTCLASSPATH,
248             Optional.empty());
249     Env<ClassSymbol, TypeBoundClass> env =
250         CompoundEnv.<ClassSymbol, TypeBoundClass>of(bound.classPathEnv())
251             .append(new SimpleEnv<>(bound.units()));
252     ModelFactory factory = new ModelFactory(env, ClassLoader.getSystemClassLoader(), bound.tli());
253     TurbineTypes turbineTypes = new TurbineTypes(factory);
254     TurbineElements elements = new TurbineElements(factory, turbineTypes);
255     ImmutableList<TurbineTypeElement> typeElements =
256         bound.units().keySet().stream().map(factory::typeElement).collect(toImmutableList());
257     return new HidesTester(elements, collectElements(typeElements));
258   }
259 
260   /** Compiles the test input with turbine. */
runJavac()261   private HidesTester runJavac() throws Exception {
262     DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
263     JavacTask javacTask =
264         IntegrationTestSupport.runJavacAnalysis(
265             input.sources, ImmutableList.of(), ImmutableList.of(), diagnostics);
266     List<TypeElement> typeElements = new ArrayList<>();
267     javacTask.addTaskListener(
268         new TaskListener() {
269           @Override
270           public void started(TaskEvent e) {
271             if (e.getKind().equals(TaskEvent.Kind.ANALYZE)) {
272               typeElements.add(e.getTypeElement());
273             }
274           }
275         });
276     Elements elements = javacTask.getElements();
277     if (!javacTask.call()) {
278       fail(Joiner.on("\n").join(diagnostics.getDiagnostics()));
279     }
280     return new HidesTester(elements, collectElements(typeElements));
281   }
282 
283   /** Scans a test compilation for elements to use as test inputs. */
collectElements(List<? extends TypeElement> typeElements)284   private ImmutableMap<String, Element> collectElements(List<? extends TypeElement> typeElements) {
285     Map<String, Element> elements = new HashMap<>();
286     for (TypeElement typeElement : typeElements) {
287       elements.put(key(typeElement), typeElement);
288       new ElementScanner8<Void, Void>() {
289         @Override
290         public Void scan(Element e, Void unused) {
291           Element p = elements.put(key(e), e);
292           if (p != null && !e.equals(p) && !p.getKind().equals(ElementKind.CONSTRUCTOR)) {
293             throw new AssertionError(key(e) + " " + p + " " + e);
294           }
295           return super.scan(e, unused);
296         }
297       }.visit(typeElement);
298     }
299     return ImmutableMap.copyOf(elements);
300   }
301 
302   /** A unique string representation of an element. */
key(Element e)303   private static String key(Element e) {
304     ArrayDeque<Name> names = new ArrayDeque<>();
305     Element curr = e;
306     do {
307       if (curr.getSimpleName().length() > 0) {
308         names.addFirst(curr.getSimpleName());
309       }
310       curr = curr.getEnclosingElement();
311     } while (curr != null);
312     String key = e.getKind() + ":" + Joiner.on('.').join(names);
313     if (e.getKind().equals(ElementKind.METHOD)) {
314       key += ":" + e.asType();
315     }
316     return key;
317   }
318 }
319