/* * Copyright 2013-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.jvm.java; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import com.facebook.buck.artifact_cache.ArtifactCache; import com.facebook.buck.artifact_cache.DirArtifactCacheTestUtil; import com.facebook.buck.artifact_cache.TestArtifactCaches; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.json.HasJsonField; import com.facebook.buck.jvm.java.testutil.AbiCompilationModeTest; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.testutil.Zip; import com.facebook.buck.testutil.integration.ProjectWorkspace; import com.facebook.buck.testutil.integration.ProjectWorkspace.ProcessResult; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.testutil.integration.ZipInspector; import com.facebook.buck.util.ObjectMappers; import com.facebook.buck.util.environment.Platform; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Strings; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Ordering; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; /** * Integration test that verifies that a {@link DefaultJavaLibrary} writes its ABI key as part of * compilation. */ public class DefaultJavaLibraryIntegrationTest extends AbiCompilationModeTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); private ProjectWorkspace workspace; private ProjectFilesystem filesystem; @Before public void setUp() throws InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS || Platform.detect() == Platform.LINUX); filesystem = new ProjectFilesystem(tmp.getRoot()); } @Test public void testBuildJavaLibraryWithoutSrcsAndVerifyAbi() throws InterruptedException, IOException { setUpProjectWorkspaceForScenario("abi"); workspace.enableDirCache(); // Run `buck build`. BuildTarget target = BuildTargetFactory.newInstance("//:no_srcs"); ProcessResult buildResult = workspace.runBuckCommand("build", target.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); Path outputPath = BuildTargets.getGenPath( filesystem, target, "lib__%s__output/" + target.getShortName() + ".jar"); Path outputFile = workspace.getPath(outputPath); assertTrue(Files.exists(outputFile)); // TODO(mbolin): When we produce byte-for-byte identical JAR files across builds, do: // // HashCode hashOfOriginalJar = Files.hash(outputFile, Hashing.sha1()); // // And then compare that to the output when //:no_srcs is built again with --no-cache. long sizeOfOriginalJar = Files.size(outputFile); // This verifies that the ABI key was written correctly. workspace.verify(); // Verify the build cache. Path buildCache = workspace.getPath(filesystem.getBuckPaths().getCacheDir()); assertTrue(Files.isDirectory(buildCache)); ArtifactCache dirCache = TestArtifactCaches.createDirCacheForTest(workspace.getDestPath(), buildCache); int totalArtifactsCount = DirArtifactCacheTestUtil.getAllFilesInCache(dirCache).size(); assertEquals( "There should be two entries (a zip and metadata) per rule key type (default and input-" + "based) in the build cache.", 4, totalArtifactsCount); // Run `buck clean`. ProcessResult cleanResult = workspace.runBuckCommand("clean"); cleanResult.assertSuccess("Successful clean should exit with 0."); totalArtifactsCount = getAllFilesInPath(buildCache).size(); assertEquals("The build cache should still exist.", 4, totalArtifactsCount); // Corrupt the build cache! File artifactZip = FluentIterable.from( ImmutableList.copyOf(DirArtifactCacheTestUtil.getAllFilesInCache(dirCache))) .toSortedList(Ordering.natural()) .get(0) .toFile(); FileSystem zipFs = FileSystems.newFileSystem(artifactZip.toPath(), /* loader */ null); Path outputInZip = zipFs.getPath("/" + outputPath.toString()); new ZipOutputStream(Files.newOutputStream(outputInZip, StandardOpenOption.TRUNCATE_EXISTING)) .close(); zipFs.close(); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", target.getFullyQualifiedName()); buildResult2.assertSuccess("Successful build should exit with 0."); assertTrue(Files.isRegularFile(outputFile)); ZipFile outputZipFile = new ZipFile(outputFile.toFile()); assertEquals( "The output file will be an empty zip if it is read from the build cache.", 0, outputZipFile.stream().count()); outputZipFile.close(); // Run `buck clean` followed by `buck build` yet again, but this time, specify `--no-cache`. ProcessResult cleanResult2 = workspace.runBuckCommand("clean"); cleanResult2.assertSuccess("Successful clean should exit with 0."); ProcessResult buildResult3 = workspace.runBuckCommand("build", "--no-cache", target.getFullyQualifiedName()); buildResult3.assertSuccess(); outputZipFile = new ZipFile(outputFile.toFile()); assertNotEquals( "The contents of the file should no longer be pulled from the corrupted build cache.", 0, outputZipFile.stream().count()); outputZipFile.close(); assertEquals( "We cannot do a byte-for-byte comparision with the original JAR because timestamps might " + "have changed, but we verify that they are the same size, as a proxy.", sizeOfOriginalJar, Files.size(outputFile)); } @Test public void testBucksClasspathNotOnBuildClasspath() throws IOException { setUpProjectWorkspaceForScenario("guava_no_deps"); // Run `buck build`. ProcessResult buildResult = workspace.runBuckCommand("build", "//:foo"); buildResult.assertFailure( "Build should have failed since //:foo depends on Guava and " + "Args4j but does not include it in its deps."); workspace.verify(); } @Test public void testNoDepsCompilesCleanly() throws IOException { setUpProjectWorkspaceForScenario("guava_no_deps"); // Run `buck build`. ProcessResult buildResult = workspace.runBuckCommand("build", "//:bar"); buildResult.assertSuccess("Build should have succeeded."); workspace.verify(); } @Test public void testBuildJavaLibraryWithFirstOrder() throws IOException { setUpProjectWorkspaceForScenario("warn_on_transitive"); // Run `buck build`. ProcessResult buildResult = workspace.runBuckCommand("build", "//:raz", "-b", "FIRST_ORDER_ONLY"); buildResult.assertFailure("Build should have failed."); workspace.verify(); } @Test public void testBuildJavaLibraryExportsDirectoryEntries() throws IOException { setUpProjectWorkspaceForScenario("export_directory_entries"); // Run `buck build`. BuildTarget target = BuildTargetFactory.newInstance("//:empty_directory_entries"); ProcessResult buildResult = workspace.runBuckBuild(target.getFullyQualifiedName()); buildResult.assertSuccess(); Path outputFile = workspace.getPath( BuildTargets.getGenPath( filesystem, target, "lib__%s__output/" + target.getShortName() + ".jar")); assertTrue(Files.exists(outputFile)); ImmutableSet.Builder<String> jarContents = ImmutableSet.builder(); try (ZipFile zipFile = new ZipFile(outputFile.toFile())) { for (ZipEntry zipEntry : Collections.list(zipFile.entries())) { jarContents.add(zipEntry.getName()); } } // TODO(mread): Change the output to the intended output. assertEquals( jarContents.build(), ImmutableSet.of("META-INF/", "META-INF/MANIFEST.MF", "swag.txt", "yolo.txt")); workspace.verify(); } @Test public void testFileChangeThatDoesNotModifyAbiAvoidsRebuild() throws IOException { setUpProjectWorkspaceForScenario("rulekey_changed_while_abi_stable"); // Run `buck build`. BuildTarget bizTarget = BuildTargetFactory.newInstance("//:biz"); BuildTarget utilTarget = BuildTargetFactory.newInstance("//:util"); ProcessResult buildResult = workspace.runBuckCommand("build", bizTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); Path utilRuleKeyPath = BuildTargets.getScratchPath(filesystem, utilTarget, ".%s/metadata/RULE_KEY"); String utilRuleKey = getContents(utilRuleKeyPath); Path utilAbiRuleKeyPath = BuildTargets.getScratchPath(filesystem, utilTarget, ".%s/metadata/INPUT_BASED_RULE_KEY"); String utilAbiRuleKey = getContents(utilAbiRuleKeyPath); Path bizRuleKeyPath = BuildTargets.getScratchPath(filesystem, bizTarget, ".%s/metadata/RULE_KEY"); String bizRuleKey = getContents(bizRuleKeyPath); Path bizAbiRuleKeyPath = BuildTargets.getScratchPath(filesystem, bizTarget, ".%s/metadata/INPUT_BASED_RULE_KEY"); String bizAbiRuleKey = getContents(bizAbiRuleKeyPath); Path utilOutputPath = BuildTargets.getGenPath( filesystem, utilTarget, "lib__%s__output/" + utilTarget.getShortName() + ".jar"); long utilJarSize = Files.size(workspace.getPath(utilOutputPath)); Path bizOutputPath = BuildTargets.getGenPath( filesystem, bizTarget, "lib__%s__output/" + bizTarget.getShortName() + ".jar"); FileTime bizJarLastModified = Files.getLastModifiedTime(workspace.getPath(bizOutputPath)); // TODO(mbolin): Run uber-biz.jar and verify it prints "Hello World!\n". // Edit Util.java in a way that does not affect its ABI. workspace.replaceFileContents("Util.java", "Hello World", "Hola Mundo"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:biz"); buildResult2.assertSuccess("Successful build should exit with 0."); assertThat(utilRuleKey, not(equalTo(getContents(utilRuleKeyPath)))); assertThat(utilAbiRuleKey, not(equalTo(getContents(utilAbiRuleKeyPath)))); workspace.getBuildLog().assertTargetBuiltLocally(utilTarget.toString()); assertThat(bizRuleKey, not(equalTo(getContents(bizRuleKeyPath)))); assertEquals(bizAbiRuleKey, getContents(bizAbiRuleKeyPath)); workspace.getBuildLog().assertTargetHadMatchingInputRuleKey(bizTarget.toString()); assertThat( "util.jar should have been rewritten, so its file size should have changed.", utilJarSize, not(equalTo(Files.size(workspace.getPath(utilOutputPath))))); assertEquals( "biz.jar should not have been rewritten, so its last-modified time should be the same.", bizJarLastModified, Files.getLastModifiedTime(workspace.getPath(bizOutputPath))); // TODO(mbolin): Run uber-biz.jar and verify it prints "Hola Mundo!\n". // TODO(mbolin): This last scenario that is being tested would be better as a unit test. // Run `buck build` one last time. This ensures that a dependency java_library() rule (:util) // that is built via BuildRuleSuccess.Type.MATCHING_INPUT_BASED_RULE_KEY does not // explode when its dependent rule (:biz) invokes the dependency's getAbiKey() method as part of // its own getAbiKeyForDeps(). ProcessResult buildResult3 = workspace.runBuckCommand("build", "//:biz"); buildResult3.assertSuccess("Successful build should exit with 0."); } @Test public void testJavaLibraryOnlyDependsOnTheAbiVersionsOfItsDeps() throws IOException { compileAgainstAbisOnly(); setUpProjectWorkspaceForScenario("depends_only_on_abi_test"); workspace.enableDirCache(); // Build A ProcessResult firstBuildResult = workspace.runBuckBuild("//:a"); firstBuildResult.assertSuccess("Successful build should exit with 0."); // Perform clean ProcessResult cleanResult = workspace.runBuckCommand("clean"); cleanResult.assertSuccess("Successful clean should exit with 0."); // Edit A workspace.replaceFileContents("A.java", "getB", "getNewB"); // Rebuild A ProcessResult secondBuildResult = workspace.runBuckBuild("//:a"); secondBuildResult.assertSuccess("Successful build should exit with 0."); BuildTarget b = BuildTargetFactory.newInstance("//:b"); BuildTarget c = BuildTargetFactory.newInstance("//:c"); BuildTarget d = BuildTargetFactory.newInstance("//:d"); // Confirm that we got an input based rule key hit on B#abi, C#abi, D#abi workspace .getBuildLog() .assertTargetWasFetchedFromCache( b.withFlavors(HasJavaAbi.CLASS_ABI_FLAVOR).getFullyQualifiedName()); workspace .getBuildLog() .assertTargetWasFetchedFromCache( c.withFlavors(HasJavaAbi.CLASS_ABI_FLAVOR).getFullyQualifiedName()); workspace .getBuildLog() .assertTargetWasFetchedFromCache( d.withFlavors(HasJavaAbi.CLASS_ABI_FLAVOR).getFullyQualifiedName()); // Confirm that B, C, and D were not re-built workspace.getBuildLog().assertNoLogEntry(b.getFullyQualifiedName()); workspace.getBuildLog().assertNoLogEntry(c.getFullyQualifiedName()); workspace.getBuildLog().assertNoLogEntry(d.getFullyQualifiedName()); } @Test public void testAnnotationProcessorDepChangeThatDoesNotModifyAbiCausesRebuild() throws IOException { setUpProjectWorkspaceForScenario("annotation_processors"); // Run `buck build` to create the dep file BuildTarget mainTarget = BuildTargetFactory.newInstance("//:main"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", mainTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Edit a dependency of the annotation processor in a way that doesn't change the ABI workspace.replaceFileContents("Util.java", "false", "true"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:main"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll see //:annotation_processor's dep file on disk and not rebuild it, // but still rebuild //:main because the code of the annotation processor has changed workspace.getBuildLog().assertTargetBuiltLocally("//:util"); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetHadMatchingInputRuleKey("//:annotation_processor_lib"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor"); } @Test public void testAnnotationProcessorFileChangeThatDoesNotModifyAbiCausesRebuild() throws IOException { setUpProjectWorkspaceForScenario("annotation_processors"); // Run `buck build` to create the dep file BuildTarget mainTarget = BuildTargetFactory.newInstance("//:main"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", mainTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Edit a source file in the annotation processor in a way that doesn't change the ABI workspace.replaceFileContents("AnnotationProcessor.java", "false", "true"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:main"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll rebuild //:annotation_processor because of the source change, // and then rebuild //:main because the code of the annotation processor has changed workspace.getBuildLog().assertTargetHadMatchingRuleKey("//:util"); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor"); } @Test public void testAnnotationProcessorFileChangeThatDoesNotModifyCodeDoesNotCauseRebuild() throws IOException { setUpProjectWorkspaceForScenario("annotation_processors"); // Run `buck build` to create the dep file BuildTarget mainTarget = BuildTargetFactory.newInstance("//:main"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", mainTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Edit a source file in the annotation processor in a way that doesn't change the ABI workspace.replaceFileContents("AnnotationProcessor.java", "false", "false /* false */"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:main"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll rebuild //:annotation_processor because of the source change, // and then rebuild //:main because the code of the annotation processor has changed workspace.getBuildLog().assertTargetHadMatchingRuleKey("//:util"); workspace.getBuildLog().assertTargetHadMatchingInputRuleKey("//:main"); workspace.getBuildLog().assertTargetHadMatchingInputRuleKey("//:annotation_processor"); workspace.getBuildLog().assertTargetBuiltLocally("//:annotation_processor_lib"); } @Test public void testFileChangeThatDoesNotModifyAbiOfAUsedClassAvoidsRebuild() throws IOException { setUpProjectWorkspaceForScenario("dep_file_rule_key"); // Run `buck build` to create the dep file BuildTarget bizTarget = BuildTargetFactory.newInstance("//:biz"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", bizTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:biz"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Edit MoreUtil.java in a way that changes its ABI workspace.replaceFileContents("MoreUtil.java", "printHelloWorld", "printHelloWorld2"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:biz"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll fetch //:biz's dep file from the cache and realize we don't need // to rebuild it because //:biz didn't use MoreUtil. workspace.getBuildLog().assertTargetBuiltLocally("//:util"); workspace.getBuildLog().assertTargetHadMatchingDepfileRuleKey("//:biz"); } @Test public void testResourceFileChangeCanTakeAdvantageOfDepBasedKeys() throws IOException { setUpProjectWorkspaceForScenario("resource_in_dep_file"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", ":main"); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Edit the unread_file.txt resource workspace.replaceFileContents("unread_file.txt", "hello", "goodbye"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:main"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll fetch //:main's dep file from the cache and realize we don't need // to rebuild it because //:main didn't use unread_file. workspace.getBuildLog().assertTargetBuiltLocally("//:util"); workspace.getBuildLog().assertTargetHadMatchingDepfileRuleKey("//:main"); // Edit read_file.txt resource workspace.replaceFileContents("read_file.txt", "me", "you"); workspace.runBuckCommand("build", ":main").assertSuccess(); // Since that file was used during the compilation, we must rebuild workspace.getBuildLog().assertTargetBuiltLocally("//:util"); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); } @Test public void testFileChangeThatDoesNotModifyAbiOfAUsedClassAvoidsRebuildEvenWithBuckClean() throws IOException { setUpProjectWorkspaceForScenario("dep_file_rule_key"); workspace.enableDirCache(); // Run `buck build` to warm the cache. BuildTarget bizTarget = BuildTargetFactory.newInstance("//:biz"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", bizTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:biz"); workspace.getBuildLog().assertTargetBuiltLocally("//:util"); // Run `buck clean` so that we're forced to fetch the dep file from the cache. ProcessResult cleanResult = workspace.runBuckCommand("clean"); cleanResult.assertSuccess("Successful clean should exit with 0."); // Edit MoreUtil.java in a way that changes its ABI workspace.replaceFileContents("MoreUtil.java", "printHelloWorld", "printHelloWorld2"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:biz"); buildResult2.assertSuccess("Successful build should exit with 0."); // If all goes well, we'll fetch //:biz's dep file from the cache and realize we don't need // to rebuild it because //:biz didn't use MoreUtil. workspace.getBuildLog().assertTargetBuiltLocally("//:util"); workspace.getBuildLog().assertTargetWasFetchedFromCacheByManifestMatch("//:biz"); } // Yes, we actually had the bug against which this test is guarding. @Test public void testAddedSourceFileInvalidatesManifest() throws IOException { setUpProjectWorkspaceForScenario("manifest_key"); workspace.enableDirCache(); // Run `buck build` to warm the cache. BuildTarget mainTarget = BuildTargetFactory.newInstance("//:main"); // Warm the used classes file ProcessResult buildResult = workspace.runBuckCommand("build", mainTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:main"); // Run `buck clean` so that we're forced to fetch the dep file from the cache. ProcessResult cleanResult = workspace.runBuckCommand("clean"); cleanResult.assertSuccess("Successful clean should exit with 0."); // Add a new source file workspace.writeContentsToPath( "package com.example; public class NewClass { }", "NewClass.java"); // Run `buck build` again. ProcessResult buildResult2 = workspace.runBuckCommand("build", mainTarget.getFullyQualifiedName()); buildResult2.assertSuccess("Successful build should exit with 0."); // The new source file should result in a different manifest being downloaded and thus a // cache miss. workspace.getBuildLog().assertTargetBuiltLocally("//:main"); } @Test public void testClassUsageFileOutput() throws IOException { setUpProjectWorkspaceForScenario("class_usage_file"); // Run `buck build`. BuildTarget bizTarget = BuildTargetFactory.newInstance("//:biz"); ProcessResult buildResult = workspace.runBuckCommand("build", bizTarget.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); Path bizClassUsageFilePath = BuildTargets.getGenPath(filesystem, bizTarget, "lib__%s__output/used-classes.json"); final List<String> lines = Files.readAllLines(workspace.getPath(bizClassUsageFilePath), UTF_8); assertEquals("Expected just one line of JSON", 1, lines.size()); final String utilJarPath; if (compileAgainstAbis.equals(TRUE)) { utilJarPath = MorePaths.pathWithPlatformSeparators("buck-out/gen/util#class-abi/util-abi.jar"); } else { utilJarPath = MorePaths.pathWithPlatformSeparators("buck-out/gen/lib__util__output/util.jar"); } final String utilClassPath = MorePaths.pathWithPlatformSeparators("com/example/Util.class"); JsonNode jsonNode = ObjectMappers.READER.readTree(lines.get(0)); assertThat( jsonNode, new HasJsonField( utilJarPath, Matchers.equalTo( ObjectMappers.legacyCreate().valueToTree(new String[] {utilClassPath})))); } @Test public void updatingAResourceWhichIsJavaLibraryCausesAJavaLibraryToBeRepacked() throws IOException { setUpProjectWorkspaceForScenario("resource_change_causes_repack"); // Run `buck build`. ProcessResult buildResult = workspace.runBuckCommand("build", "//:lib"); buildResult.assertSuccess("Successful build should exit with 0."); workspace.copyFile("ResClass.java.new", "ResClass.java"); workspace.resetBuildLogFile(); // The copied file changed the contents but not the ABI of :lib. Because :lib is included as a // resource of :res, it's expected that both :lib and :res are rebuilt (:lib because of a code // change, :res in order to repack the resource) buildResult = workspace.runBuckCommand("build", "//:lib"); buildResult.assertSuccess("Successful build should exit with 0."); workspace.getBuildLog().assertTargetBuiltLocally("//:res"); workspace.getBuildLog().assertTargetBuiltLocally("//:lib"); } @Test public void ensureProvidedDepsAreIncludedWhenCompilingButNotWhenPackaging() throws IOException { setUpProjectWorkspaceForScenario("provided_deps"); // Run `buck build`. BuildTarget binaryTarget = BuildTargetFactory.newInstance("//:binary"); BuildTarget binary2Target = BuildTargetFactory.newInstance("//:binary_2"); ProcessResult buildResult = workspace.runBuckCommand( "build", binaryTarget.getFullyQualifiedName(), binary2Target.getFullyQualifiedName()); buildResult.assertSuccess("Successful build should exit with 0."); for (Path filename : new Path[] { BuildTargets.getGenPath(filesystem, binaryTarget, "%s.jar"), BuildTargets.getGenPath(filesystem, binary2Target, "%s.jar") }) { Path file = workspace.getPath(filename); try (Zip zip = new Zip(file, /* for writing? */ false)) { Set<String> allNames = zip.getFileNames(); // Representative file from provided_deps we don't expect to be there. assertFalse(allNames.contains("org/junit/Test.class")); // Representative file from the deps that we do expect to be there. assertTrue(allNames.contains("com/google/common/collect/Sets.class")); // The file we built. assertTrue(allNames.contains("com/facebook/buck/example/Example.class")); } } } @Test public void ensureChangingDepFromProvidedToTransitiveTriggersRebuild() throws IOException { setUpProjectWorkspaceForScenario("provided_deps"); workspace.runBuckBuild("//:binary").assertSuccess("Successful build should exit with 0."); workspace.replaceFileContents("BUCK", "provided_deps = [ ':junit' ],", ""); workspace.replaceFileContents("BUCK", "deps = [ ':guava' ]", "deps = [ ':guava', ':junit' ]"); workspace.resetBuildLogFile(); workspace.runBuckBuild("//:binary").assertSuccess(); workspace.getBuildLog().assertTargetBuiltLocally("//:binary"); } @Test public void ensureThatSourcePathIsSetSensibly() throws IOException { setUpProjectWorkspaceForScenario("sourcepath"); ProcessResult result = workspace.runBuckBuild("//:b"); // This should fail, since we expect the symbol for A not to be found. result.assertFailure(); String stderr = result.getStderr(); assertTrue(stderr, stderr.contains("cannot find symbol")); } @Test public void testSaveClassFilesToDisk() throws IOException { setUpProjectWorkspaceForScenario("spool_class_files_to_disk"); BuildTarget target = BuildTargetFactory.newInstance("//:a"); ProcessResult result = workspace.runBuckBuild(target.getFullyQualifiedName()); result.assertSuccess(); Path classesDir = workspace.getPath(BuildTargets.getScratchPath(filesystem, target, "lib__%s__classes")); assertTrue(Files.exists(classesDir)); assertTrue(Files.isDirectory(classesDir)); ArrayList<String> classFiles = new ArrayList<>(); for (File file : classesDir.toFile().listFiles()) { classFiles.add(file.getName()); } assertThat( "There should be 2 class files saved to disk from the compiler", classFiles, hasSize(2)); assertThat(classFiles, hasItem("A.class")); assertThat(classFiles, hasItem("B.class")); } @Test public void testSpoolClassFilesDirectlyToJar() throws IOException { setUpProjectWorkspaceForScenario("spool_class_files_directly_to_jar"); BuildTarget target = BuildTargetFactory.newInstance("//:a"); ProcessResult result = workspace.runBuckBuild(target.getFullyQualifiedName()); result.assertSuccess(); Path classesDir = workspace.getPath(BuildTargets.getScratchPath(filesystem, target, "lib__%s__classes")); assertThat(Files.exists(classesDir), is(Boolean.TRUE)); assertThat( "There should be no class files in disk", ImmutableList.copyOf(classesDir.toFile().listFiles()), hasSize(0)); Path jarPath = workspace.getPath(BuildTargets.getGenPath(filesystem, target, "lib__%s__output/a.jar")); assertTrue(Files.exists(jarPath)); ZipInputStream zip = new ZipInputStream(new FileInputStream(jarPath.toFile())); assertThat(zip.getNextEntry().getName(), is("META-INF/")); assertThat(zip.getNextEntry().getName(), is("META-INF/MANIFEST.MF")); assertThat(zip.getNextEntry().getName(), is("A.class")); assertThat(zip.getNextEntry().getName(), is("B.class")); zip.close(); } @Test public void testSpoolClassFilesDirectlyToJarWithRemoveClasses() throws IOException { setUpProjectWorkspaceForScenario("spool_class_files_directly_to_jar_with_remove_classes"); BuildTarget target = BuildTargetFactory.newInstance("//:a"); ProcessResult result = workspace.runBuckBuild(target.getFullyQualifiedName()); result.assertSuccess(); Path classesDir = workspace.getPath(BuildTargets.getScratchPath(filesystem, target, "lib__%s__classes")); assertThat("Classes directory should exist.", Files.exists(classesDir), is(Boolean.TRUE)); assertThat( "No class files should be stored on disk.", ImmutableList.copyOf(classesDir.toFile().listFiles()), hasSize(0)); Path jarPath = workspace.getPath(BuildTargets.getGenPath(filesystem, target, "lib__%s__output/a.jar")); assertTrue(Files.exists(jarPath)); // Check that normal and member classes were removed as expected. ZipInspector zipInspector = new ZipInspector(jarPath); zipInspector.assertFileExists("test/pkg/A.class"); zipInspector.assertFileExists("test/pkg/B.class"); zipInspector.assertFileExists("test/pkg/C.class"); zipInspector.assertFileDoesNotExist("test/pkg/RemovableZ.class"); zipInspector.assertFileDoesNotExist("B$removableB.class"); zipInspector.assertFileDoesNotExist("C$deletableD.class"); } @Test public void testSaveClassFilesToDiskWithRemoveClasses() throws IOException { setUpProjectWorkspaceForScenario("spool_class_files_to_disk_remove_classes"); BuildTarget target = BuildTargetFactory.newInstance("//:a"); ProcessResult result = workspace.runBuckBuild(target.getFullyQualifiedName()); result.assertSuccess(); Path classesDir = workspace.getPath(BuildTargets.getScratchPath(filesystem, target, "lib__%s__classes")); assertThat("Classes directory should exist.", Files.exists(classesDir), is(Boolean.TRUE)); Path jarPath = workspace.getPath(BuildTargets.getGenPath(filesystem, target, "lib__%s__output/a.jar")); assertTrue("Jar should exist.", Files.exists(jarPath)); // Check that normal and member classes were removed as expected. ZipInspector zipInspector = new ZipInspector(jarPath); zipInspector.assertFileExists("test/pkg/A.class"); zipInspector.assertFileExists("test/pkg/B.class"); zipInspector.assertFileDoesNotExist("test/pkg/RemovableC.class"); zipInspector.assertFileDoesNotExist("test/pkg/B$MemberD.class"); zipInspector.assertFileDoesNotExist("DeletableB.class"); } @Test public void testSpoolClassFilesDirectlyToJarWithAnnotationProcessor() throws IOException { setUpProjectWorkspaceForScenario("spool_class_files_directly_to_jar_with_annotation_processor"); BuildTarget target = BuildTargetFactory.newInstance("//:a"); ProcessResult result = workspace.runBuckBuild(target.getFullyQualifiedName()); result.assertSuccess(); Path classesDir = workspace.getPath(BuildTargets.getScratchPath(filesystem, target, "lib__%s__classes")); assertThat(Files.exists(classesDir), is(Boolean.TRUE)); assertThat( "There should be no class files in disk", ImmutableList.copyOf(classesDir.toFile().listFiles()), hasSize(0)); Path sourcesDir = workspace.getPath(BuildTargets.getAnnotationPath(filesystem, target, "__%s_gen__")); assertThat(Files.exists(sourcesDir), is(Boolean.TRUE)); assertThat( "There should one source file in disk, from the Immutable class.", ImmutableList.copyOf(sourcesDir.toFile().listFiles()), hasSize(1)); Path jarPath = workspace.getPath(BuildTargets.getGenPath(filesystem, target, "lib__%s__output/a.jar")); assertTrue(Files.exists(jarPath)); ZipInspector zipInspector = new ZipInspector(jarPath); zipInspector.assertFileDoesNotExist("ImmutableC.java"); zipInspector.assertFileExists("ImmutableC.class"); } @Test public void shouldIncludeUserSuppliedManifestIfProvided() throws IOException { setUpProjectWorkspaceForScenario("manifest"); Manifest m = new Manifest(); Attributes attrs = new Attributes(); attrs.putValue("Data", "cheese"); m.getEntries().put("Example", attrs); m.write(System.out); Path path = workspace.buildAndReturnOutput("//:library"); try (InputStream is = Files.newInputStream(path); JarInputStream jis = new JarInputStream(is)) { Manifest manifest = jis.getManifest(); String value = manifest.getEntries().get("Example").getValue("Data"); assertEquals("cheese", value); } } /** Asserts that the specified file exists and returns its contents. */ private String getContents(Path relativePathToFile) throws IOException { Path file = workspace.getPath(relativePathToFile); assertTrue(relativePathToFile + " should exist and be an ordinary file.", Files.exists(file)); String content = Strings.nullToEmpty(new String(Files.readAllBytes(file), UTF_8)).trim(); assertFalse(relativePathToFile + " should not be empty.", content.isEmpty()); return content; } private ImmutableList<Path> getAllFilesInPath(Path path) throws IOException { final List<Path> allFiles = new ArrayList<>(); Files.walkFileTree( path, ImmutableSet.of(), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { allFiles.add(file); return super.visitFile(file, attrs); } }); return ImmutableList.copyOf(allFiles); } private ProjectWorkspace setUpProjectWorkspaceForScenario(String scenario) throws IOException { workspace = TestDataHelper.createProjectWorkspaceForScenario(this, scenario, tmp); workspace.setUp(); setWorkspaceCompilationMode(workspace); return workspace; } }