1 /* 2 * Copyright 2016 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.deps; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import com.google.common.base.Joiner; 22 import com.google.common.collect.ImmutableList; 23 import com.google.common.collect.ImmutableMap; 24 import com.google.common.collect.ImmutableSet; 25 import com.google.turbine.binder.Binder; 26 import com.google.turbine.binder.Binder.BindingResult; 27 import com.google.turbine.binder.ClassPathBinder; 28 import com.google.turbine.diag.SourceFile; 29 import com.google.turbine.lower.IntegrationTestSupport; 30 import com.google.turbine.lower.Lower; 31 import com.google.turbine.lower.Lower.Lowered; 32 import com.google.turbine.parse.Parser; 33 import com.google.turbine.proto.DepsProto; 34 import com.google.turbine.testing.TestClassPaths; 35 import com.google.turbine.tree.Tree.CompUnit; 36 import java.io.BufferedOutputStream; 37 import java.io.IOException; 38 import java.io.OutputStream; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.LinkedHashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Optional; 46 import java.util.jar.JarEntry; 47 import java.util.jar.JarOutputStream; 48 import java.util.stream.Collectors; 49 import org.junit.Rule; 50 import org.junit.Test; 51 import org.junit.rules.TemporaryFolder; 52 import org.junit.runner.RunWith; 53 import org.junit.runners.JUnit4; 54 55 @RunWith(JUnit4.class) 56 public class DependenciesTest { 57 58 @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 59 60 class LibraryBuilder { 61 final Map<String, String> sources = new LinkedHashMap<>(); 62 private ImmutableList<Path> classpath = ImmutableList.of(); 63 addSourceLines(String path, String... lines)64 LibraryBuilder addSourceLines(String path, String... lines) { 65 sources.put(path, Joiner.on('\n').join(lines)); 66 return this; 67 } 68 setClasspath(Path... classpath)69 LibraryBuilder setClasspath(Path... classpath) { 70 this.classpath = ImmutableList.copyOf(classpath); 71 return this; 72 } 73 compileToJar(String path)74 Path compileToJar(String path) throws Exception { 75 Path lib = temporaryFolder.newFile(path).toPath(); 76 Map<String, byte[]> classes = IntegrationTestSupport.runJavac(sources, classpath); 77 try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { 78 for (Map.Entry<String, byte[]> entry : classes.entrySet()) { 79 jos.putNextEntry(new JarEntry(entry.getKey() + ".class")); 80 jos.write(entry.getValue()); 81 } 82 } 83 return lib; 84 } 85 } 86 87 static class DepsBuilder { 88 List<Path> classpath; 89 ImmutableList.Builder<CompUnit> units = ImmutableList.builder(); 90 setClasspath(Path... classpath)91 DepsBuilder setClasspath(Path... classpath) { 92 this.classpath = ImmutableList.copyOf(classpath); 93 return this; 94 } 95 addSourceLines(String path, String... lines)96 DepsBuilder addSourceLines(String path, String... lines) { 97 units.add(Parser.parse(new SourceFile(path, Joiner.on('\n').join(lines)))); 98 return this; 99 } 100 run()101 DepsProto.Dependencies run() throws IOException { 102 BindingResult bound = 103 Binder.bind( 104 units.build(), 105 ClassPathBinder.bindClasspath(classpath), 106 TestClassPaths.TURBINE_BOOTCLASSPATH, 107 /* moduleVersion= */ Optional.empty()); 108 109 Lowered lowered = 110 Lower.lowerAll( 111 Lower.LowerOptions.createDefault(), 112 bound.units(), 113 bound.modules(), 114 bound.classPathEnv()); 115 116 return Dependencies.collectDeps( 117 Optional.of("//test"), TestClassPaths.TURBINE_BOOTCLASSPATH, bound, lowered); 118 } 119 } 120 depsMap(DepsProto.Dependencies deps)121 private Map<Path, DepsProto.Dependency.Kind> depsMap(DepsProto.Dependencies deps) { 122 return deps.getDependencyList().stream() 123 .collect(Collectors.toMap(d -> Paths.get(d.getPath()), DepsProto.Dependency::getKind)); 124 } 125 126 @Test simple()127 public void simple() throws Exception { 128 Path liba = 129 new LibraryBuilder().addSourceLines("A.java", "class A {}").compileToJar("liba.jar"); 130 DepsProto.Dependencies deps = 131 new DepsBuilder() 132 .setClasspath(liba) 133 .addSourceLines("Test.java", "class Test extends A {}") 134 .run(); 135 136 assertThat(depsMap(deps)).containsExactly(liba, DepsProto.Dependency.Kind.EXPLICIT); 137 } 138 139 @Test excluded()140 public void excluded() throws Exception { 141 Path liba = 142 new LibraryBuilder() 143 .addSourceLines( 144 "A.java", // 145 "class A {}") 146 .compileToJar("liba.jar"); 147 Path libb = 148 new LibraryBuilder() 149 .setClasspath(liba) 150 .addSourceLines( 151 "B.java", // 152 "class B {", 153 " public static final A a = new A();", 154 "}") 155 .compileToJar("libb.jar"); 156 DepsProto.Dependencies deps = 157 new DepsBuilder() 158 .setClasspath(liba, libb) 159 .addSourceLines("Test.java", "class Test extends B {}") 160 .run(); 161 162 assertThat(depsMap(deps)).containsExactly(libb, DepsProto.Dependency.Kind.EXPLICIT); 163 } 164 165 @Test transitive()166 public void transitive() throws Exception { 167 Path liba = 168 new LibraryBuilder() 169 .addSourceLines( 170 "A.java", // 171 "class A {", 172 " public static final class Y {}", 173 "}") 174 .compileToJar("liba.jar"); 175 Path libb = 176 new LibraryBuilder() 177 .setClasspath(liba) 178 .addSourceLines("B.java", "class B extends A {}") 179 .compileToJar("libb.jar"); 180 DepsProto.Dependencies deps = 181 new DepsBuilder() 182 .setClasspath(liba, libb) 183 .addSourceLines( 184 "Test.java", // 185 "class Test extends B {", 186 " public static class X extends Y {}", 187 "}") 188 .run(); 189 assertThat(depsMap(deps)) 190 .containsExactly( 191 libb, DepsProto.Dependency.Kind.EXPLICIT, liba, DepsProto.Dependency.Kind.EXPLICIT); 192 } 193 194 @Test closure()195 public void closure() throws Exception { 196 Path libi = 197 new LibraryBuilder() 198 .addSourceLines( 199 "i/I.java", 200 "package i;", // 201 "public interface I {}") 202 .compileToJar("libi.jar"); 203 Path liba = 204 new LibraryBuilder() 205 .setClasspath(libi) 206 .addSourceLines( 207 "a/A.java", // 208 "package a;", 209 "import i.I;", 210 "public class A implements I {}") 211 .compileToJar("liba.jar"); 212 Path libb = 213 new LibraryBuilder() 214 .setClasspath(liba, libi) 215 .addSourceLines( 216 "b/B.java", // 217 "package b;", 218 "import a.A;", 219 "public class B extends A {}") 220 .compileToJar("libb.jar"); 221 { 222 DepsProto.Dependencies deps = 223 new DepsBuilder() 224 .setClasspath(liba, libb, libi) 225 .addSourceLines( 226 "Test.java", // 227 "import b.B;", 228 "class Test extends B {}") 229 .run(); 230 assertThat(depsMap(deps)) 231 .containsExactly( 232 libi, 233 DepsProto.Dependency.Kind.EXPLICIT, 234 libb, 235 DepsProto.Dependency.Kind.EXPLICIT, 236 liba, 237 DepsProto.Dependency.Kind.EXPLICIT); 238 } 239 { 240 // partial classpath 241 DepsProto.Dependencies deps = 242 new DepsBuilder() 243 .setClasspath(liba, libb) 244 .addSourceLines( 245 "Test.java", // 246 "import b.B;", 247 "class Test extends B {}") 248 .run(); 249 assertThat(depsMap(deps)) 250 .containsExactly( 251 libb, DepsProto.Dependency.Kind.EXPLICIT, liba, DepsProto.Dependency.Kind.EXPLICIT); 252 } 253 } 254 255 @Test unreducedClasspathTest()256 public void unreducedClasspathTest() throws IOException { 257 ImmutableList<String> classpath = 258 ImmutableList.of( 259 "a.jar", "b.jar", "c.jar", "d.jar", "e.jar", "f.jar", "g.jar", "h.jar", "i.jar", 260 "j.jar"); 261 ImmutableSet<String> directJars = ImmutableSet.of(); 262 ImmutableList<String> depsArtifacts = ImmutableList.of(); 263 assertThat(Dependencies.reduceClasspath(classpath, directJars, depsArtifacts)) 264 .containsExactlyElementsIn(classpath); 265 } 266 267 @Test reducedClasspathTest()268 public void reducedClasspathTest() throws IOException { 269 Path cdeps = temporaryFolder.newFile("c.jdeps").toPath(); 270 Path ddeps = temporaryFolder.newFile("d.jdeps").toPath(); 271 Path gdeps = temporaryFolder.newFile("g.jdeps").toPath(); 272 ImmutableList<String> classpath = 273 ImmutableList.of( 274 "a.jar", "b.jar", "c.jar", "d.jar", "e.jar", "f.jar", "g.jar", "h.jar", "i.jar", 275 "j.jar"); 276 ImmutableSet<String> directJars = ImmutableSet.of("c.jar", "d.jar", "g.jar"); 277 ImmutableList<String> depsArtifacts = 278 ImmutableList.of(cdeps.toString(), ddeps.toString(), gdeps.toString()); 279 writeDeps( 280 cdeps, 281 ImmutableMap.of( 282 "b.jar", DepsProto.Dependency.Kind.EXPLICIT, 283 "e.jar", DepsProto.Dependency.Kind.EXPLICIT)); 284 writeDeps( 285 ddeps, 286 ImmutableMap.of( 287 "f.jar", DepsProto.Dependency.Kind.UNUSED, 288 "j.jar", DepsProto.Dependency.Kind.UNUSED)); 289 writeDeps(gdeps, ImmutableMap.of("i.jar", DepsProto.Dependency.Kind.IMPLICIT)); 290 assertThat(Dependencies.reduceClasspath(classpath, directJars, depsArtifacts)) 291 .containsExactly("b.jar", "c.jar", "d.jar", "e.jar", "g.jar", "i.jar") 292 .inOrder(); 293 } 294 295 @Test packageInfo()296 public void packageInfo() throws Exception { 297 Path libpackageInfo = 298 new LibraryBuilder() 299 .addSourceLines( 300 "p/Anno.java", 301 "package p;", 302 "import java.lang.annotation.Retention;", 303 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 304 "@Retention(RUNTIME)", 305 "@interface Anno {}") 306 .addSourceLines( 307 "p/package-info.java", // 308 "@Anno", 309 "package p;") 310 .compileToJar("libpackage-info.jar"); 311 Path libp = 312 new LibraryBuilder() 313 .setClasspath(libpackageInfo) 314 .addSourceLines( 315 "p/P.java", // 316 "package p;", 317 "public class P {}") 318 .compileToJar("libp.jar"); 319 { 320 DepsProto.Dependencies deps = 321 new DepsBuilder() 322 .setClasspath(libp, libpackageInfo) 323 .addSourceLines( 324 "Test.java", // 325 "import p.P;", 326 "class Test {", 327 " P p;", 328 "}") 329 .run(); 330 assertThat(depsMap(deps)) 331 .containsExactly( 332 libpackageInfo, 333 DepsProto.Dependency.Kind.EXPLICIT, 334 libp, 335 DepsProto.Dependency.Kind.EXPLICIT); 336 } 337 } 338 339 @Test annotations_recursive()340 public void annotations_recursive() throws Exception { 341 Path libA = libA(); 342 Path libB = libB(); 343 344 DepsProto.Dependencies deps = 345 new DepsBuilder() 346 .setClasspath(libA, libB) 347 .addSourceLines( 348 "Test.java", // 349 "import a.A;", 350 "import b.B;", 351 "@A(B.class)", 352 "class Test {", 353 "}") 354 .run(); 355 assertThat(depsMap(deps)) 356 .containsExactly( 357 libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); 358 } 359 360 @Test annotations_field()361 public void annotations_field() throws Exception { 362 Path libA = libA(); 363 Path libB = libB(); 364 DepsProto.Dependencies deps = 365 new DepsBuilder() 366 .setClasspath(libA, libB) 367 .addSourceLines( 368 "Test.java", // 369 "import a.A;", 370 "import b.B;", 371 "class Test {", 372 " @A(B.class)", 373 " int x;", 374 "}") 375 .run(); 376 assertThat(depsMap(deps)) 377 .containsExactly( 378 libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); 379 } 380 381 @Test annotations_method()382 public void annotations_method() throws Exception { 383 Path libA = libA(); 384 Path libB = libB(); 385 DepsProto.Dependencies deps = 386 new DepsBuilder() 387 .setClasspath(libA, libB) 388 .addSourceLines( 389 "Test.java", // 390 "import a.A;", 391 "import b.B;", 392 "class Test {", 393 " @A(B.class)", 394 " void f() {}", 395 "}") 396 .run(); 397 assertThat(depsMap(deps)) 398 .containsExactly( 399 libA, DepsProto.Dependency.Kind.EXPLICIT, libB, DepsProto.Dependency.Kind.EXPLICIT); 400 } 401 libB()402 private Path libB() throws Exception { 403 return new LibraryBuilder() 404 .addSourceLines( 405 "b/B.java", 406 "package b;", 407 "import java.lang.annotation.Retention;", 408 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 409 "@Retention(RUNTIME)", 410 "public @interface B {", 411 "}") 412 .compileToJar("libb.jar"); 413 } 414 libA()415 private Path libA() throws Exception { 416 return new LibraryBuilder() 417 .addSourceLines( 418 "a/A.java", 419 "package a;", 420 "import java.lang.annotation.Retention;", 421 "import static java.lang.annotation.RetentionPolicy.RUNTIME;", 422 "@Retention(RUNTIME)", 423 "public @interface A {", 424 " Class<?> value() default Object.class;", 425 "}") 426 .compileToJar("liba.jar"); 427 } 428 writeDeps(Path path, ImmutableMap<String, DepsProto.Dependency.Kind> deps)429 void writeDeps(Path path, ImmutableMap<String, DepsProto.Dependency.Kind> deps) 430 throws IOException { 431 DepsProto.Dependencies.Builder builder = 432 DepsProto.Dependencies.newBuilder().setSuccess(true).setRuleLabel("//test"); 433 for (Map.Entry<String, DepsProto.Dependency.Kind> e : deps.entrySet()) { 434 builder.addDependency( 435 DepsProto.Dependency.newBuilder().setPath(e.getKey()).setKind(e.getValue())); 436 } 437 try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(path))) { 438 builder.build().writeTo(os); 439 } 440 } 441 } 442