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