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.main; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; 21 import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath; 22 import static java.nio.charset.StandardCharsets.UTF_8; 23 import static java.util.Objects.requireNonNull; 24 import static org.junit.Assert.assertThrows; 25 26 import com.google.common.base.Joiner; 27 import com.google.common.collect.ImmutableList; 28 import com.google.protobuf.ExtensionRegistry; 29 import com.google.turbine.diag.TurbineError; 30 import com.google.turbine.lower.IntegrationTestSupport; 31 import com.google.turbine.lower.IntegrationTestSupport.TestInput; 32 import com.google.turbine.main.Main.Result; 33 import com.google.turbine.options.TurbineOptions.ReducedClasspathMode; 34 import com.google.turbine.proto.DepsProto; 35 import com.google.turbine.proto.DepsProto.Dependency.Kind; 36 import java.io.BufferedInputStream; 37 import java.io.BufferedOutputStream; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.io.OutputStream; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.util.Map; 44 import java.util.jar.JarEntry; 45 import java.util.jar.JarOutputStream; 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.Test; 49 import org.junit.rules.TemporaryFolder; 50 import org.junit.runner.RunWith; 51 import org.junit.runners.JUnit4; 52 53 @RunWith(JUnit4.class) 54 public class ReducedClasspathTest { 55 56 @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 57 58 private Path liba; 59 private Path libb; 60 private Path libc; 61 private Path libcJdeps; 62 63 @Before setup()64 public void setup() throws Exception { 65 Map<String, byte[]> compiled = 66 IntegrationTestSupport.runJavac( 67 TestInput.parse( 68 String.join( 69 "\n", 70 ImmutableList.of( 71 "=== a/A.java ===", 72 "package a;", 73 "public class A {", 74 " public static class I {}", 75 "}", 76 "=== b/B.java ===", 77 "package b;", 78 "import a.A;", 79 "public class B extends A {}", 80 "=== c/C.java ===", 81 "package c;", 82 "import b.B;", 83 "public class C extends B {}"))) 84 .sources, 85 /* classpath= */ ImmutableList.of()); 86 87 liba = createLibrary(compiled, "liba.jar", "a/A", "a/A$I"); 88 libb = createLibrary(compiled, "libb.jar", "b/B"); 89 libc = createLibrary(compiled, "libc.jar", "c/C"); 90 91 libcJdeps = temporaryFolder.newFile("libc.jdeps").toPath(); 92 try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(libcJdeps))) { 93 DepsProto.Dependencies.newBuilder() 94 .addDependency( 95 DepsProto.Dependency.newBuilder() 96 .setKind(Kind.EXPLICIT) 97 .setPath(libb.toString()) 98 .build()) 99 .build() 100 .writeTo(os); 101 } 102 } 103 createLibrary(Map<String, byte[]> compiled, String jarPath, String... classNames)104 private Path createLibrary(Map<String, byte[]> compiled, String jarPath, String... classNames) 105 throws IOException { 106 Path lib = temporaryFolder.newFile(jarPath).toPath(); 107 try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(lib))) { 108 for (String className : classNames) { 109 jos.putNextEntry(new JarEntry(className + ".class")); 110 jos.write(requireNonNull(compiled.get(className), className)); 111 } 112 } 113 return lib; 114 } 115 116 @Test succeedsWithoutFallingBack()117 public void succeedsWithoutFallingBack() throws Exception { 118 Path src = temporaryFolder.newFile("Test.java").toPath(); 119 Files.write( 120 src, 121 ImmutableList.of( 122 "import c.C;", // 123 "class Test extends C {", 124 "}"), 125 UTF_8); 126 127 Path output = temporaryFolder.newFile("output.jar").toPath(); 128 129 Result result = 130 Main.compile( 131 optionsWithBootclasspath() 132 .setOutput(output.toString()) 133 .setSources(ImmutableList.of(src.toString())) 134 .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) 135 .setClassPath( 136 ImmutableList.of( 137 // ensure that the compilation succeeds without falling back by adding 138 // a jar to the transitive classpath that doesn't exist, which would cause 139 // the compilation to fail if it fell back 140 temporaryFolder.newFile("no.such.jar").toString(), 141 liba.toString(), 142 libb.toString(), 143 libc.toString())) 144 .setDirectJars(ImmutableList.of(libc.toString())) 145 .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) 146 .build()); 147 assertThat(result.transitiveClasspathFallback()).isFalse(); 148 } 149 150 @Test succeedsAfterFallingBack()151 public void succeedsAfterFallingBack() throws Exception { 152 Path src = temporaryFolder.newFile("Test.java").toPath(); 153 Files.write( 154 src, 155 ImmutableList.of( 156 "import c.C;", // 157 "class Test extends C {", 158 " I i;", 159 "}"), 160 UTF_8); 161 162 Path output = temporaryFolder.newFile("output.jar").toPath(); 163 164 Result result = 165 Main.compile( 166 optionsWithBootclasspath() 167 .setOutput(output.toString()) 168 .setSources(ImmutableList.of(src.toString())) 169 .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) 170 .setClassPath(ImmutableList.of(liba.toString(), libb.toString(), libc.toString())) 171 .setDirectJars(ImmutableList.of(libc.toString())) 172 .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) 173 .build()); 174 assertThat(result.transitiveClasspathFallback()).isTrue(); 175 assertThat(result.reducedClasspathLength()).isEqualTo(2); 176 assertThat(result.transitiveClasspathLength()).isEqualTo(3); 177 } 178 179 @Test bazelFallback()180 public void bazelFallback() throws Exception { 181 Path src = temporaryFolder.newFile("Test.java").toPath(); 182 Files.write( 183 src, 184 ImmutableList.of( 185 "import c.C;", // 186 "class Test extends C {", 187 " I i;", 188 "}"), 189 UTF_8); 190 191 Path output = temporaryFolder.newFile("output.jar").toPath(); 192 Path jdeps = temporaryFolder.newFile("output.jdeps").toPath(); 193 194 Result result = 195 Main.compile( 196 optionsWithBootclasspath() 197 .setOutput(output.toString()) 198 .setTargetLabel("//java/com/google/foo") 199 .setOutputDeps(jdeps.toString()) 200 .setSources(ImmutableList.of(src.toString())) 201 .setReducedClasspathMode(ReducedClasspathMode.BAZEL_REDUCED) 202 .setClassPath(ImmutableList.of(libc.toString())) 203 .setReducedClasspathLength(1) 204 .setFullClasspathLength(3) 205 .build()); 206 assertThat(result.transitiveClasspathFallback()).isTrue(); 207 assertThat(result.reducedClasspathLength()).isEqualTo(1); 208 assertThat(result.transitiveClasspathLength()).isEqualTo(3); 209 DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder(); 210 try (InputStream is = new BufferedInputStream(Files.newInputStream(jdeps))) { 211 deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry()); 212 } 213 assertThat(deps.build()) 214 .isEqualTo( 215 DepsProto.Dependencies.newBuilder() 216 .setRequiresReducedClasspathFallback(true) 217 .setRuleLabel("//java/com/google/foo") 218 .build()); 219 } 220 221 @Test noFallbackWithoutDirectJarsAndJdeps()222 public void noFallbackWithoutDirectJarsAndJdeps() throws Exception { 223 Path src = temporaryFolder.newFile("Test.java").toPath(); 224 Files.write( 225 src, 226 ImmutableList.of( 227 "import c.C;", // 228 "class Test extends C {", 229 " I i;", 230 "}"), 231 UTF_8); 232 233 Path output = temporaryFolder.newFile("output.jar").toPath(); 234 235 TurbineError e = 236 assertThrows( 237 TurbineError.class, 238 () -> 239 Main.compile( 240 optionsWithBootclasspath() 241 .setOutput(output.toString()) 242 .setSources(ImmutableList.of(src.toString())) 243 .setReducedClasspathMode(ReducedClasspathMode.JAVABUILDER_REDUCED) 244 .setClassPath(ImmutableList.of(libc.toString())) 245 .setDepsArtifacts(ImmutableList.of(libcJdeps.toString())) 246 .build())); 247 assertThat(e).hasMessageThat().contains("could not resolve I"); 248 } 249 lines(String... lines)250 static String lines(String... lines) { 251 return Joiner.on(System.lineSeparator()).join(lines); 252 } 253 } 254