blob: ce6ee3662720c859abf5ebd39e22867f0b70e460 [file] [log] [blame]
/*
* 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.java;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
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 com.facebook.buck.java.abi.AbiWriterProtocol;
import com.facebook.buck.testutil.Zip;
import com.facebook.buck.testutil.integration.DebuggableTemporaryFolder;
import com.facebook.buck.testutil.integration.ProjectWorkspace;
import com.facebook.buck.testutil.integration.ProjectWorkspace.ProcessResult;
import com.facebook.buck.testutil.integration.TestDataHelper;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Files;
import org.junit.Rule;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Integration test that verifies that a {@link DefaultJavaLibrary} writes its ABI key as part
* of compilation.
*/
public class DefaultJavaLibraryIntegrationTest {
@Rule
public DebuggableTemporaryFolder tmp = new DebuggableTemporaryFolder();
private ProjectWorkspace workspace;
@Test
public void testBuildJavaLibraryWithoutSrcsAndVerifyAbi() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "abi", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build", "//:no_srcs");
buildResult.assertSuccess("Successful build should exit with 0.");
File outputFile = workspace.getFile("buck-out/gen/lib__no_srcs__output/no_srcs.jar");
assertTrue(outputFile.exists());
// 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 = outputFile.length();
// This verifies that the ABI key was written correctly.
workspace.verify();
// Verify the build cache.
File buildCache = workspace.getFile("cache_dir");
assertTrue(buildCache.isDirectory());
assertEquals("There should be one entry (a zip) in the build cache.",
1,
buildCache.listFiles().length);
// Verify the ABI key entry in the build cache.
Path artifactZip = buildCache.listFiles()[0].toPath();
FileSystem zipFs = FileSystems.newFileSystem(artifactZip, /* loader */ null);
Path abiKeyEntry = zipFs.getPath("/buck-out/bin/.no_srcs/metadata/ABI_KEY");
assertEquals(AbiWriterProtocol.EMPTY_ABI_KEY,
new String(java.nio.file.Files.readAllBytes(abiKeyEntry)));
// Run `buck clean`.
ProcessResult cleanResult = workspace.runBuckCommand("clean");
cleanResult.assertSuccess("Successful clean should exit with 0.");
assertEquals("The build cache should still exist.", 1, buildCache.listFiles().length);
// Corrupt the build cache!
Path outputInZip = zipFs.getPath("/buck-out/gen/lib__no_srcs__output/no_srcs.jar");
java.nio.file.Files.write(outputInZip, "Hello world!".getBytes(), WRITE);
zipFs.close();
// Run `buck build` again.
ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:no_srcs");
buildResult2.assertSuccess("Successful build should exit with 0.");
assertTrue(outputFile.isFile());
assertEquals(
"The content of the output file will be 'Hello World!' if it is read from the build cache.",
"Hello world!",
Files.toString(outputFile, Charsets.UTF_8));
// 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", "//:no_srcs");
buildResult3.assertSuccess();
assertNotEquals(
"The contents of the file should no longer be pulled from the corrupted build cache.",
"Hello world!",
Files.toString(outputFile, Charsets.UTF_8));
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,
outputFile.length());
}
@Test
public void testBucksClasspathNotOnBuildClasspath() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "guava_no_deps", tmp);
workspace.setUp();
// 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 {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "guava_no_deps", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build", "//:bar");
buildResult.assertSuccess("Build should have succeeded.");
workspace.verify();
}
@Test
public void testBuildJavaLibraryWithTransitive() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "warn_on_transitive", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build", "//:raz", "-b", "TRANSITIVE");
buildResult.assertSuccess("Successful build should exit with 0.");
workspace.verify();
}
@Test
public void testBuildJavaLibraryWithFirstOrder() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "warn_on_transitive", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build",
"//:raz",
"-b",
"FIRST_ORDER_ONLY");
buildResult.assertFailure("Build should have failed.");
workspace.verify();
}
@Test
public void testBuildJavaLibraryWithWarnOnTransitive() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "warn_on_transitive", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build",
"//:raz",
"-b",
"WARN_ON_TRANSITIVE");
String expectedWarning = Joiner.on("\n").join(
"Rule //:raz builds with its transitive dependencies but not with its first order " +
"dependencies.",
"The following packages were missing:",
"Blargh",
"Meh",
"Try adding the following deps:",
"//:foo",
"//:blargh");
buildResult.assertSuccess("Build should have succeeded with warnings.");
assertThat(
buildResult.getStderr(),
containsString(expectedWarning));
workspace.verify();
}
@Test
public void testBuildJavaLibraryExportsDirectoryEntries() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "export_directory_entries", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckBuild("//:empty_directory_entries");
buildResult.assertSuccess();
File outputFile = workspace.getFile(
"buck-out/gen/lib__empty_directory_entries__output/empty_directory_entries.jar");
assertTrue(outputFile.exists());
ImmutableSet.Builder<String> jarContents = ImmutableSet.builder();
try (ZipFile zipFile = new ZipFile(outputFile)) {
for (ZipEntry zipEntry : Collections.list(zipFile.entries())) {
jarContents.add(zipEntry.getName());
}
}
// TODO(user): Change the output to the intended output.
assertEquals(
jarContents.build(),
ImmutableSet.of(
"META-INF/MANIFEST.MF",
"swag.txt",
"yolo.txt"));
workspace.verify();
}
@Test
public void testFileChangeThatDoesNotModifyAbiAvoidsRebuild() throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "rulekey_changed_while_abi_stable", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build", "//:biz");
buildResult.assertSuccess("Successful build should exit with 0.");
String utilRuleKey = getContents("buck-out/bin/.util/metadata/RULE_KEY");
String utilRuleKeyNoDeps = getContents("buck-out/bin/.util/metadata/RULE_KEY_NO_DEPS");
String utilAbi = getContents("buck-out/bin/.util/metadata/ABI_KEY");
String utilAbiForDeps = getContents("buck-out/bin/.util/metadata/ABI_KEY_FOR_DEPS");
String bizRuleKey = getContents("buck-out/bin/.biz/metadata/RULE_KEY");
String bizRuleKeyNoDeps = getContents("buck-out/bin/.biz/metadata/RULE_KEY_NO_DEPS");
String bizAbi = getContents("buck-out/bin/.biz/metadata/ABI_KEY");
String bizAbiForDeps = getContents("buck-out/bin/.biz/metadata/ABI_KEY_FOR_DEPS");
long utilJarSize = workspace.getFile("buck-out/gen/lib__util__output/util.jar").length();
long bizJarLastModified = workspace.getFile("buck-out/gen/lib__biz__output/biz.jar")
.lastModified();
// 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.
String originalUtilJava = getContents("Util.java");
String replacementContents = originalUtilJava.replace("Hello World", "Hola Mundo");
Files.write(replacementContents, workspace.getFile("Util.java"), Charsets.UTF_8);
// Run `buck build` again.
ProcessResult buildResult2 = workspace.runBuckCommand("build", "//:biz");
buildResult2.assertSuccess("Successful build should exit with 0.");
assertThat(utilRuleKey, not(equalTo(getContents("buck-out/bin/.util/metadata/RULE_KEY"))));
assertThat(utilRuleKeyNoDeps,
not(equalTo(getContents("buck-out/bin/.util/metadata/RULE_KEY_NO_DEPS"))));
assertEquals(utilAbi, getContents("buck-out/bin/.util/metadata/ABI_KEY"));
assertEquals(utilAbiForDeps, getContents("buck-out/bin/.util/metadata/ABI_KEY_FOR_DEPS"));
assertThat(bizRuleKey, not(equalTo(getContents("buck-out/bin/.biz/metadata/RULE_KEY"))));
assertEquals(bizRuleKeyNoDeps, getContents("buck-out/bin/.biz/metadata/RULE_KEY_NO_DEPS"));
assertEquals(bizAbi, getContents("buck-out/bin/.biz/metadata/ABI_KEY"));
assertEquals(bizAbiForDeps, getContents("buck-out/bin/.biz/metadata/ABI_KEY_FOR_DEPS"));
assertThat(
"util.jar should have been rewritten, so its file size should have changed.",
utilJarSize,
not(equalTo(workspace.getFile("buck-out/gen/lib__util__output/util.jar").length())));
assertEquals(
"biz.jar should not have been rewritten, so its last-modified time should be the same.",
bizJarLastModified,
workspace.getFile("buck-out/gen/lib__biz__output/biz.jar").lastModified());
// 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_DEPS_ABI_AND_RULE_KEY_NO_DEPS 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 updatingAResourceWhichIsJavaLibraryCausesAJavaLibraryToBeRepacked()
throws IOException {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "resource_change_causes_repack", tmp);
workspace.setUp();
// 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 {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "provided_deps", tmp);
workspace.setUp();
// Run `buck build`.
ProcessResult buildResult = workspace.runBuckCommand("build", "//:binary");
buildResult.assertSuccess("Successful build should exit with 0.");
File file = workspace.getFile("buck-out/gen/binary.jar");
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 {
workspace = TestDataHelper.createProjectWorkspaceForScenario(
this, "provided_deps", tmp);
workspace.setUp();
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("//:library");
}
@Test
public void ensureThatSourcePathIsSetSensibly() throws IOException {
ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(
this,
"sourcepath",
tmp);
workspace.setUp();
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"));
}
/**
* Asserts that the specified file exists and returns its contents.
*/
private String getContents(String relativePathToFile) throws IOException {
File file = workspace.getFile(relativePathToFile);
assertTrue(relativePathToFile + " should exist and be an ordinary file.", file.exists());
String content = Strings.nullToEmpty(Files.toString(file, Charsets.UTF_8)).trim();
assertFalse(relativePathToFile + " should not be empty.", content.isEmpty());
return content;
}
}