/*
* Copyright 2014-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.cli;
import static com.facebook.buck.util.MoreStringsForTests.normalizeNewlines;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.facebook.buck.io.MorePaths;
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.util.HumanReadableException;
import com.facebook.buck.util.ObjectMappers;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
public class TargetsCommandIntegrationTest {
private static final String ABSOLUTE_PATH_TO_FILE_OUTSIDE_THE_PROJECT_THAT_EXISTS_ON_THE_FS =
"/bin/sh";
private static final CharMatcher LOWER_CASE_HEX_DIGITS =
CharMatcher.inRange('0', '9').or(CharMatcher.inRange('a', 'f'));
@Rule public TemporaryPaths tmp = new TemporaryPaths();
@Rule public ExpectedException thrown = ExpectedException.none();
@Test
public void testOutputPath() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-output", "//:test", "//:another-test");
result.assertSuccess();
assertEquals(
"//:another-test "
+ MorePaths.pathWithPlatformSeparators("buck-out/gen/another-test/test-output")
+ "\n"
+ "//:test "
+ MorePaths.pathWithPlatformSeparators("buck-out/gen/test/test-output")
+ "\n",
result.getStdout());
}
@Test
public void testRuleKeyWithOneTarget() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--show-rulekey", "//:test");
result.assertSuccess();
assertThat(result.getStdout().trim(), Matchers.matchesPattern("//:test [a-f0-9]{40}"));
}
@Test
public void testRuleKey() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-rulekey", "//:test", "//:another-test");
result.assertSuccess();
parseAndVerifyTargetsAndHashes(result.getStdout(), "//:another-test", "//:test");
}
@Test
public void testBothOutputAndRuleKey() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-rulekey", "--show-output", "//:test");
result.assertSuccess();
assertThat(
result.getStdout().trim(),
Matchers.matchesPattern(
"//:test [a-f0-9]{40} "
+ Pattern.quote(
MorePaths.pathWithPlatformSeparators("buck-out/gen/test/test-output"))));
}
@Test
public void testOutputWithoutTarget() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--show-output");
result.assertSuccess();
assertEquals(
"//:another-test "
+ MorePaths.pathWithPlatformSeparators("buck-out/gen/another-test/test-output")
+ "\n"
+ "//:test "
+ MorePaths.pathWithPlatformSeparators("buck-out/gen/test/test-output")
+ "\n",
result.getStdout());
}
@Test
public void testRuleKeyWithoutTarget() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--show-rulekey");
result.assertSuccess();
assertThat(
result.getStdout().trim(),
Matchers.matchesPattern("//:another-test [a-f0-9]{40}\n//:test [a-f0-9]{40}"));
}
@Test
public void testCellPath() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--show-cell-path", "//:test");
result.assertSuccess();
assertEquals(
"//:test " + MorePaths.pathWithPlatformSeparators(tmp.getRoot().toRealPath()) + "\n",
result.getStdout());
}
@Test
public void testCellPathAndOutput() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-output", "--show-cell-path", "//:test");
result.assertSuccess();
assertEquals(
"//:test "
+ MorePaths.pathWithPlatformSeparators(tmp.getRoot().toRealPath())
+ " "
+ MorePaths.pathWithPlatformSeparators("buck-out/gen/test/test-output")
+ "\n",
result.getStdout());
}
private ImmutableList<String> parseAndVerifyTargetsAndHashes(
String outputLine, String... targets) {
List<String> lines =
Splitter.on('\n').splitToList(CharMatcher.whitespace().trimFrom(outputLine));
assertEquals(targets.length, lines.size());
ImmutableList.Builder<String> hashes = ImmutableList.builder();
for (int i = 0; i < targets.length; ++i) {
String line = lines.get(i);
String target = targets[i];
hashes.add(parseAndVerifyTargetAndHash(line, target));
}
return hashes.build();
}
private String parseAndVerifyTargetAndHash(String outputLine, String target) {
List<String> targetAndHash =
Splitter.on(' ').splitToList(CharMatcher.whitespace().trimFrom(outputLine));
assertEquals(2, targetAndHash.size());
assertEquals(target, targetAndHash.get(0));
assertFalse(targetAndHash.get(1).isEmpty());
assertTrue(LOWER_CASE_HEX_DIGITS.matchesAllOf(targetAndHash.get(1)));
return targetAndHash.get(1);
}
@Test
public void testTargetHashWithoutTarget() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--show-target-hash");
result.assertSuccess();
parseAndVerifyTargetsAndHashes(result.getStdout(), "//:another-test", "//:test");
}
@Test
public void testRuleKeyWithReferencedFiles() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "java_library_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-rulekey", "--referenced-file", "Test.java");
result.assertSuccess();
parseAndVerifyTargetAndHash(result.getStdout(), "//:test");
}
@Test
public void testRuleKeyWithReferencedFilesAndDetectTestChanges() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "java_library_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-rulekey", "--detect-test-changes", "--referenced-file", "Test.java");
result.assertSuccess();
parseAndVerifyTargetsAndHashes(result.getStdout(), "//:lib", "//:test");
}
@Test
public void testTargetHash() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-target-hash", "//:test", "//:another-test");
result.assertSuccess();
parseAndVerifyTargetsAndHashes(result.getStdout(), "//:another-test", "//:test");
}
@Test
public void testTargetHashAndRuleKeyThrows() throws IOException {
thrown.expect(HumanReadableException.class);
thrown.expectMessage("Cannot show rule key and target hash at the same time.");
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
workspace.runBuckCommand("targets", "--show-target-hash", "--show-rulekey", "//:test");
}
@Test
public void testTargetHashAndRuleKeyAndOutputThrows() throws IOException {
thrown.expect(HumanReadableException.class);
thrown.expectMessage("Cannot show rule key and target hash at the same time.");
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
workspace.runBuckCommand(
"targets", "--show-target-hash", "--show-rulekey", "--show-output", "//:test");
}
@Test
public void testTargetHashXcodeWorkspaceWithTests() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result.assertSuccess();
parseAndVerifyTargetAndHash(result.getStdout(), "//workspace:workspace");
}
@Test
public void testTargetHashXcodeWorkspaceWithTestsForAllTargets() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-target-hash", "--detect-test-changes");
result.assertSuccess();
parseAndVerifyTargetsAndHashes(
result.getStdout(),
"//bin:bin",
"//bin:genrule",
"//lib:lib",
"//test:test",
"//workspace:workspace");
}
@Test
public void testTargetHashWithBrokenTargets() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(
this, "detect_test_changes_with_broken_targets", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//:test");
result.assertSuccess();
parseAndVerifyTargetAndHash(result.getStdout(), "//:test");
}
@Test
public void testTargetHashXcodeWorkspaceWithoutTestsDiffersFromWithTests() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result.assertSuccess();
String hash = parseAndVerifyTargetAndHash(result.getStdout(), "//workspace:workspace");
ProcessResult result2 =
workspace.runBuckCommand("targets", "--show-target-hash", "//workspace:workspace");
result2.assertSuccess();
String hash2 = parseAndVerifyTargetAndHash(result2.getStdout(), "//workspace:workspace");
assertNotEquals(hash, hash2);
}
@Test
public void testTargetHashChangesAfterModifyingSourceFile() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result.assertSuccess();
String hash = parseAndVerifyTargetAndHash(result.getStdout(), "//workspace:workspace");
String fileName = "test/Test.m";
Files.write(workspace.getPath(fileName), "// This is not a test\n".getBytes(UTF_8));
ProcessResult result2 =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result2.assertSuccess();
String hash2 = parseAndVerifyTargetAndHash(result2.getStdout(), "//workspace:workspace");
assertNotEquals(hash, hash2);
}
@Test
public void testTargetHashChangesAfterModifyingSourceFileForAllTargets() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--show-target-hash", "--detect-test-changes");
result.assertSuccess();
List<String> hashes =
parseAndVerifyTargetsAndHashes(
result.getStdout(),
"//bin:bin",
"//bin:genrule",
"//lib:lib",
"//test:test",
"//workspace:workspace");
String fileName = "test/Test.m";
Files.write(workspace.getPath(fileName), "// This is not a test\n".getBytes(UTF_8));
ProcessResult result2 =
workspace.runBuckCommand("targets", "--show-target-hash", "--detect-test-changes");
result2.assertSuccess();
List<String> hashesAfterModification =
parseAndVerifyTargetsAndHashes(
result2.getStdout(),
"//bin:bin",
"//bin:genrule",
"//lib:lib",
"//test:test",
"//workspace:workspace");
assertNotEquals(hashes.get(0), hashesAfterModification.get(0));
// bin:genrule wasn't changed
assertEquals(hashes.get(1), hashesAfterModification.get(1));
assertNotEquals(hashes.get(2), hashesAfterModification.get(2));
assertNotEquals(hashes.get(3), hashesAfterModification.get(3));
assertNotEquals(hashes.get(4), hashesAfterModification.get(4));
}
@Test
public void testTargetHashChangesAfterDeletingSourceFile() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result.assertSuccess();
String hash = parseAndVerifyTargetAndHash(result.getStdout(), "//workspace:workspace");
String fileName = "test/Test.m";
Files.delete(workspace.getPath(fileName));
ProcessResult result2 =
workspace.runBuckCommand(
"targets", "--show-target-hash", "--detect-test-changes", "//workspace:workspace");
result2.assertSuccess();
String hash2 = parseAndVerifyTargetAndHash(result2.getStdout(), "//workspace:workspace");
assertNotEquals(hash, hash2);
}
@Test
public void testBuckTargetsReferencedFileWithFileOutsideOfProject() throws IOException {
// The contents of the project are not relevant for this test. We just want a non-empty project
// to prevent against a regression where all of the build rules are printed.
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "project_slice", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets",
"--referenced-file",
ABSOLUTE_PATH_TO_FILE_OUTSIDE_THE_PROJECT_THAT_EXISTS_ON_THE_FS);
result.assertSuccess(
"Even though the file is outside the project, " + "`buck targets` should succeed.");
assertEquals("Because no targets match, stdout should be empty.", "", result.getStdout());
}
@Test
public void testBuckTargetsReferencedFileWithFilesInAndOutsideOfProject() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "project_slice", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets",
"--type",
"prebuilt_jar",
"--referenced-file",
ABSOLUTE_PATH_TO_FILE_OUTSIDE_THE_PROJECT_THAT_EXISTS_ON_THE_FS,
"libs/guava.jar", // relative path in project
tmp.getRoot().resolve("libs/junit.jar").toString()); // absolute path in project
result.assertSuccess(
"Even though one referenced file is outside the project, "
+ "`buck targets` should succeed.");
assertEquals(
ImmutableSet.of("//libs:guava", "//libs:junit"),
ImmutableSet.copyOf(Splitter.on('\n').omitEmptyStrings().split(result.getStdout())));
}
@Test
public void testBuckTargetsReferencedFileWithNonExistentFile() throws IOException {
// The contents of the project are not relevant for this test. We just want a non-empty project
// to prevent against a regression where all of the build rules are printed.
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "project_slice", tmp);
workspace.setUp();
String pathToNonExistentFile = "modules/dep1/dep2/hello.txt";
assertFalse(Files.exists(workspace.getPath(pathToNonExistentFile)));
ProcessResult result =
workspace.runBuckCommand("targets", "--referenced-file", pathToNonExistentFile);
result.assertSuccess("Even though the file does not exist, buck targets` should succeed.");
assertEquals("Because no targets match, stdout should be empty.", "", result.getStdout());
}
@Test
public void testValidateBuildTargetForNonAliasTarget() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "target_validation", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--resolve-alias", "//:test-library");
assertTrue(result.getStdout(), result.getStdout().contains("//:test-library"));
try {
workspace.runBuckCommand("targets", "--resolve-alias", "//:");
} catch (HumanReadableException e) {
assertEquals("//: cannot end with a colon", e.getMessage());
}
try {
workspace.runBuckCommand("targets", "--resolve-alias", "//:test-libarry");
} catch (HumanReadableException e) {
assertEquals("//:test-libarry is not a valid target.", e.getMessage());
}
try {
workspace.runBuckCommand("targets", "--resolve-alias", "//blah/foo");
} catch (HumanReadableException e) {
assertEquals("//blah/foo must contain exactly one colon (found 0)", e.getMessage());
}
}
@Test
public void testJsonOutputWithShowOptions() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--json", "--show-output", "//:test");
// Parse the observed JSON.
JsonNode observed =
ObjectMappers.READER.readTree(ObjectMappers.createParser(result.getStdout()));
System.out.println(observed.toString());
String expectedJson = workspace.getFileContents("output_path_json.js");
JsonNode expected =
ObjectMappers.READER.readTree(ObjectMappers.createParser(normalizeNewlines(expectedJson)));
MatcherAssert.assertThat(
"Output from targets command should match expected JSON.", observed, equalTo(expected));
}
@Test
public void testJsonOutputWithShowCellPath() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--json", "--show-cell-path", "//:test");
// Parse the observed JSON.
JsonNode observed =
ObjectMappers.READER.readTree(ObjectMappers.createParser(result.getStdout()));
assertTrue(observed.isArray());
JsonNode targetNode = observed.get(0);
assertTrue(targetNode.isObject());
JsonNode cellPath = targetNode.get("buck.cell_path");
assertNotNull(cellPath);
assertEquals(
cellPath.asText(), MorePaths.pathWithPlatformSeparators(tmp.getRoot().toRealPath()));
}
@Test
public void testJsonOutputWithShowFullOutput() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand("targets", "--json", "--show-full-output", "//:test");
// Parse the observed JSON.
JsonNode observed =
ObjectMappers.READER.readTree(ObjectMappers.createParser(result.getStdout()));
assertTrue(observed.isArray());
JsonNode targetNode = observed.get(0);
assertTrue(targetNode.isObject());
JsonNode cellPath = targetNode.get("buck.outputPath");
assertNotNull(cellPath);
Path expectedPath = tmp.getRoot().resolve("buck-out/gen/test/test-output");
String expectedRootPath = MorePaths.pathWithPlatformSeparators(expectedPath);
assertEquals(expectedRootPath, cellPath.asText());
}
@Test
public void testShowAllTargets() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "xcode_workspace_with_tests", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets");
result.assertSuccess();
assertEquals(
ImmutableSet.of(
"//bin:bin", "//bin:genrule", "//lib:lib", "//test:test", "//workspace:workspace"),
ImmutableSet.copyOf(Splitter.on('\n').omitEmptyStrings().split(result.getStdout())));
}
@Test
public void testShowAllTargetsWithJson() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result = workspace.runBuckCommand("targets", "--json", "--show-output");
result.assertSuccess();
// Parse the observed JSON.
JsonNode observed =
ObjectMappers.READER.readTree(ObjectMappers.createParser(result.getStdout()));
String expectedJson = workspace.getFileContents("output_path_json_all.js");
JsonNode expected =
ObjectMappers.READER.readTree(ObjectMappers.createParser(normalizeNewlines(expectedJson)));
assertEquals("Output from targets command should match expected JSON.", expected, observed);
}
@Test
public void testSpecificAttributesWithJson() throws IOException {
ProjectWorkspace workspace =
TestDataHelper.createProjectWorkspaceForScenario(this, "output_path", tmp);
workspace.setUp();
ProcessResult result =
workspace.runBuckCommand(
"targets", "--json", "--show-output", "--output-attributes", "buck.outputPath", "name");
result.assertSuccess();
// Parse the observed JSON.
JsonNode observed =
ObjectMappers.READER.readTree(ObjectMappers.createParser(result.getStdout()));
String expectedJson = workspace.getFileContents("output_path_json_all_filtered.js");
JsonNode expected =
ObjectMappers.READER.readTree(ObjectMappers.createParser(normalizeNewlines(expectedJson)));
assertEquals("Output from targets command should match expected JSON.", expected, observed);
}
}