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