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