blob: eeadc7b8a0791bf01a3c8e5714f692fef365d038 [file] [log] [blame]
/*
* Copyright 2012-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.parser;
import static com.facebook.buck.testutil.WatchEvents.createPathEvent;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
import com.facebook.buck.cli.BuildTargetNodeToBuildRuleTransformer;
import com.facebook.buck.cli.FakeBuckConfig;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.BuckEventBusFactory;
import com.facebook.buck.event.FakeBuckEventListener;
import com.facebook.buck.event.TestEventConfigerator;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.java.JavaLibrary;
import com.facebook.buck.json.BuildFileParseException;
import com.facebook.buck.json.DefaultProjectBuildFileParserFactory;
import com.facebook.buck.json.ProjectBuildFileParser;
import com.facebook.buck.json.ProjectBuildFileParserFactory;
import com.facebook.buck.model.BuildFileTree;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetException;
import com.facebook.buck.model.FilesystemBackedBuildFileTree;
import com.facebook.buck.model.ImmutableFlavor;
import com.facebook.buck.rules.ActionGraph;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.FakeRepositoryFactory;
import com.facebook.buck.rules.FakeRuleKeyBuilderFactory;
import com.facebook.buck.rules.KnownBuildRuleTypes;
import com.facebook.buck.rules.Repository;
import com.facebook.buck.rules.RepositoryFactory;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.TargetGraphToActionGraph;
import com.facebook.buck.rules.TargetNode;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.testutil.WatchEvents;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.hash.HashCode;
import com.google.common.io.Files;
import org.easymock.EasyMockSupport;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.util.List;
import java.util.Map;
public class ParserTest extends EasyMockSupport {
private Path testBuildFile;
private Path includedByBuildFile;
private Path includedByIncludeFile;
private Path defaultIncludeFile;
private Parser testParser;
private KnownBuildRuleTypes buildRuleTypes;
private ProjectFilesystem filesystem;
private RepositoryFactory repositoryFactory;
private BuckEventBus eventBus;
@Rule
public TemporaryFolder tempDir = new TemporaryFolder();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void setUp() throws IOException, InterruptedException {
tempDir.newFolder("java", "com", "facebook");
defaultIncludeFile = tempDir.newFile(
"java/com/facebook/defaultIncludeFile").toPath();
Files.write(
"\n",
defaultIncludeFile.toFile(),
Charsets.UTF_8);
includedByIncludeFile = tempDir.newFile(
"java/com/facebook/includedByIncludeFile").toPath();
Files.write(
"\n",
includedByIncludeFile.toFile(),
Charsets.UTF_8);
includedByBuildFile = tempDir.newFile(
"java/com/facebook/includedByBuildFile").toPath();
Files.write(
"include_defs('//java/com/facebook/includedByIncludeFile')\n",
includedByBuildFile.toFile(),
Charsets.UTF_8);
testBuildFile = tempDir.newFile("java/com/facebook/BUCK").toPath();
Files.write(
"include_defs('//java/com/facebook/includedByBuildFile')\n" +
"java_library(name = 'foo')\n" +
"java_library(name = 'bar')\n" +
"genrule(name = 'baz', out = '')\n",
testBuildFile.toFile(),
Charsets.UTF_8);
tempDir.newFile("bar.py");
// Create a temp directory with some build files.
File root = tempDir.getRoot();
repositoryFactory = new FakeRepositoryFactory(root.toPath());
Repository repository = repositoryFactory.getRootRepository();
filesystem = repository.getFilesystem();
eventBus = BuckEventBusFactory.newInstance();
buildRuleTypes = repository.getKnownBuildRuleTypes();
DefaultProjectBuildFileParserFactory testBuildFileParserFactory =
new DefaultProjectBuildFileParserFactory(
filesystem,
new ParserConfig(new FakeBuckConfig()),
buildRuleTypes.getAllDescriptions());
testParser = createParser(emptyBuildTargets(), testBuildFileParserFactory);
}
private Parser createParser(Iterable<Map<String, Object>> rules)
throws IOException, InterruptedException {
return createParser(
ofInstance(
new FilesystemBackedBuildFileTree(filesystem, "BUCK")),
rules,
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes),
new BuildTargetParser());
}
private Parser createParser(
Iterable<Map<String, Object>> rules,
ProjectBuildFileParserFactory buildFileParserFactory)
throws IOException, InterruptedException {
return createParser(
ofInstance(
new FilesystemBackedBuildFileTree(filesystem, "BUCK")),
rules,
buildFileParserFactory,
new BuildTargetParser());
}
private Parser createParser(
Supplier<BuildFileTree> buildFileTreeSupplier,
Iterable<Map<String, Object>> rules,
ProjectBuildFileParserFactory buildFileParserFactory,
BuildTargetParser buildTargetParser)
throws IOException, InterruptedException {
return createParser(
buildFileTreeSupplier,
rules,
buildFileParserFactory,
buildTargetParser,
repositoryFactory);
}
private Parser createParser(
Supplier<BuildFileTree> buildFileTreeSupplier,
Iterable<Map<String, Object>> rules,
ProjectBuildFileParserFactory buildFileParserFactory,
BuildTargetParser buildTargetParser,
RepositoryFactory repositoryFactory)
throws IOException, InterruptedException {
Parser parser = new Parser(
repositoryFactory,
new ParserConfig(
new FakeBuckConfig(
ImmutableMap.<String, Map<String, String>>of(
"project", ImmutableMap.of("temp_files", ".*\\.swp$")))),
buildFileTreeSupplier,
buildTargetParser,
buildFileParserFactory,
new FakeRuleKeyBuilderFactory());
try {
parser.parseRawRulesInternal(rules);
} catch (BuildTargetException|IOException e) {
throw new RuntimeException(e);
}
return parser;
}
@Test
public void testParseBuildFilesForTargetsWithOverlappingTargets()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
// Execute buildTargetGraphForBuildTargets() with multiple targets that require parsing the same
// build file.
BuildTarget fooTarget = BuildTarget.builder("//java/com/facebook", "foo").build();
BuildTarget barTarget = BuildTarget.builder("//java/com/facebook", "bar").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(fooTarget, barTarget);
// The EventBus should be updated with events indicating how parsing ran.
FakeBuckEventListener listener = new FakeBuckEventListener();
eventBus.register(listener);
TargetGraph targetGraph = testParser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph actionGraph = buildActionGraph(eventBus, targetGraph);
BuildRule fooRule = actionGraph.findBuildRuleByTarget(fooTarget);
assertNotNull(fooRule);
BuildRule barRule = actionGraph.findBuildRuleByTarget(barTarget);
assertNotNull(barRule);
ImmutableList<ParseEvent> expected = ImmutableList.of(
TestEventConfigerator.configureTestEvent(ParseEvent.started(buildTargets), eventBus),
TestEventConfigerator.configureTestEvent(ParseEvent.finished(buildTargets,
Optional.of(targetGraph)),
eventBus));
Iterable<ParseEvent> events = Iterables.filter(listener.getEvents(), ParseEvent.class);
assertEquals(expected, ImmutableList.copyOf(events));
}
@Test
public void testMissingBuildRuleInValidFile()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
// Execute buildTargetGraphForBuildTargets() with a target in a valid file but a bad rule name.
BuildTarget fooTarget = BuildTarget.builder("//java/com/facebook", "foo").build();
BuildTarget razTarget = BuildTarget.builder("//java/com/facebook", "raz").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(fooTarget, razTarget);
thrown.expect(HumanReadableException.class);
thrown.expectMessage(
"No rule found when resolving target //java/com/facebook:raz in build file " +
"//java/com/facebook/BUCK");
testParser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
}
@Test
public void shouldThrowAnExceptionWhenAnUnknownFlavorIsSeen()
throws BuildFileParseException, BuildTargetException, InterruptedException, IOException {
BuildTarget flavored = BuildTarget.builder("//java/com/facebook", "foo")
.addFlavors(ImmutableFlavor.of("doesNotExist"))
.build();
thrown.expect(HumanReadableException.class);
thrown.expectMessage(
"Unrecognized flavor in target //java/com/facebook:foo#doesNotExist while parsing " +
"//java/com/facebook/BUCK.");
testParser.buildTargetGraphForBuildTargets(
ImmutableSortedSet.of(flavored),
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
}
@Test
public void shouldThrowAnExceptionWhenAFlavorIsAskedOfATargetThatDoesntSupportFlavors()
throws BuildFileParseException, BuildTargetException, InterruptedException, IOException {
BuildTarget flavored = BuildTarget.builder("//java/com/facebook", "baz")
.addFlavors(JavaLibrary.SRC_JAR)
.build();
thrown.expect(HumanReadableException.class);
thrown.expectMessage(
"Target //java/com/facebook:baz (type genrule) does not currently support flavors " +
"(tried [src])");
testParser.buildTargetGraphForBuildTargets(
ImmutableSortedSet.of(flavored),
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
}
@Test
public void testInvalidDepFromValidFile()
throws IOException, BuildFileParseException, BuildTargetException, InterruptedException {
// Ensure an exception with a specific message is thrown.
thrown.expect(HumanReadableException.class);
thrown.expectMessage(
"Couldn't get dependency '//java/com/facebook/invalid/lib:missing_rule' of target " +
"'//java/com/facebook/invalid:foo'");
// Execute buildTargetGraphForBuildTargets() with a target in a valid file but a bad rule name.
tempDir.newFolder("java", "com", "facebook", "invalid");
File testInvalidBuildFile = tempDir.newFile("java/com/facebook/invalid/BUCK");
Files.write(
"java_library(name = 'foo', deps = ['//java/com/facebook/invalid/lib:missing_rule'])\n" +
"java_library(name = 'bar')\n",
testInvalidBuildFile,
Charsets.UTF_8);
tempDir.newFolder("java", "com", "facebook", "invalid", "lib");
tempDir.newFile("java/com/facebook/invalid/lib/BUCK");
BuildTarget fooTarget = BuildTarget.builder("//java/com/facebook/invalid", "foo").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(fooTarget);
testParser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
}
@Test
public void whenAllRulesRequestedWithTrueFilterThenMultipleRulesReturned()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
ImmutableSet<BuildTarget> targets = testParser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
ImmutableSet<BuildTarget> expectedTargets = ImmutableSet.of(
(BuildTarget) BuildTarget.builder("//java/com/facebook", "foo").build(),
BuildTarget.builder("//java/com/facebook", "bar").build(),
BuildTarget.builder("//java/com/facebook", "baz").build());
assertEquals("Should have returned all rules.", expectedTargets, targets);
}
@Test
public void whenAllRulesAreRequestedMultipleTimesThenRulesAreOnlyParsedOnce()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false);
assertEquals("Should have cached build rules.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfNonPathEventThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
// Call filterAllTargetsInProject to populate the cache.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Process event.
WatchEvent<Object> event = WatchEvents.createOverflowEvent();
parser.onFileSystemChange(event);
// Call filterAllTargetsInProject to request cached rules.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenEnvironmentChangesThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
// Call filterAllTargetsInProject to populate the cache.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.of("Some Key", "Some Value"),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Call filterAllTargetsInProject to request cached rules.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.of("Some Key", "Some Other Value"),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenEnvironmentNotChangedThenCacheRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
// Call filterAllTargetsInProject to populate the cache.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.of("Some Key", "Some Value"),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Call filterAllTargetsInProject to request cached rules with identical environment.
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.of("Some Key", "Some Value"),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should not have invalidated cache.", 1, buildFileParserFactory.calls);
}
// TODO(jimp/devjasta): clean up the horrible ProjectBuildFileParserFactory mess.
private void parseBuildFile(
Path buildFile,
Parser parser,
ProjectBuildFileParserFactory buildFileParserFactory)
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
try (ProjectBuildFileParser projectBuildFileParser = buildFileParserFactory.createParser(
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance())) {
parser.parseBuildFile(
buildFile,
new ParserConfig(new FakeBuckConfig()),
projectBuildFileParser,
/* environment */ ImmutableMap.<String, String>of());
}
}
@Test
public void whenNotifiedOfBuildFileAddThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(testBuildFile, StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfBuildFileChangeThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(testBuildFile, StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfBuildFileDeleteThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(testBuildFile, StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfIncludeFileAddThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByBuildFile,
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfIncludeFileChangeThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByBuildFile,
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfIncludeFileDeleteThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByBuildFile,
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOf2ndOrderIncludeFileAddThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByIncludeFile,
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOf2ndOrderIncludeFileChangeThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByIncludeFile,
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOf2ndOrderIncludeFileDeleteThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(includedByIncludeFile,
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfDefaultIncludeFileAddThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(defaultIncludeFile,
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfDefaultIncludeFileChangeThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(defaultIncludeFile,
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfDefaultIncludeFileDeleteThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(defaultIncludeFile,
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
// TODO(user): avoid invalidation when arbitrary contained (possibly backup) files are added.
public void whenNotifiedOfContainedFileAddThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/SomeClass.java"),
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfContainedFileChangeThenCacheRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/SomeClass.java"),
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call did not repopulate the cache.
assertEquals("Should have not invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
// TODO(user): avoid invalidation when arbitrary contained (possibly backup) files are deleted.
public void whenNotifiedOfContainedFileDeleteThenCacheRulesAreInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/SomeClass.java"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfContainedTempFileAddThenCachedRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/MumbleSwp.Java.swp"),
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should not have invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfContainedTempFileChangeThenCachedRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/MumbleSwp.Java.swp"),
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should not have invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfContainedTempFileDeleteThenCachedRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("java/com/facebook/MumbleSwp.Java.swp"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call repopulated the cache.
assertEquals("Should not have invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfUnrelatedFileAddThenCacheRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("SomeClass.java__backup"),
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call did not repopulate the cache.
assertEquals("Should have not invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfUnrelatedFileChangeThenCacheRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(Paths.get("SomeClass.java__backup"),
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call did not repopulate the cache.
assertEquals("Should have not invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void whenNotifiedOfUnrelatedFileDeleteThenCacheRulesAreNotInvalidated()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets());
// Call parseBuildFile to populate the cache.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Process event.
WatchEvent<Path> event = createPathEvent(
Paths.get("SomeClass.java__backup"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(event);
// Call parseBuildFile to request cached rules.
parseBuildFile(testBuildFile, parser, buildFileParserFactory);
// Test that the second parseBuildFile call did not repopulate the cache.
assertEquals("Should have not invalidated cache.", 1, buildFileParserFactory.calls);
}
@Test
public void testGeneratedDeps()
throws IOException, BuildFileParseException, BuildTargetException, InterruptedException {
// Execute buildTargetGraphForBuildTargets() with a target in a valid file but a bad rule name.
tempDir.newFolder("java", "com", "facebook", "generateddeps");
File testGeneratedDepsBuckFile = tempDir.newFile("java/com/facebook/generateddeps/BUCK");
Files.write(
"java_library(name = 'foo')\n" +
"java_library(name = 'bar')\n" +
"add_deps(name = 'foo', deps = [':bar'])\n",
testGeneratedDepsBuckFile,
Charsets.UTF_8);
BuildTarget fooTarget = BuildTarget.builder("//java/com/facebook/generateddeps", "foo").build();
BuildTarget barTarget = BuildTarget.builder("//java/com/facebook/generateddeps", "bar").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(fooTarget, barTarget);
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
TargetGraph targetGraph = testParser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule fooRule = graph.findBuildRuleByTarget(fooTarget);
assertNotNull(fooRule);
BuildRule barRule = graph.findBuildRuleByTarget(barTarget);
assertNotNull(barRule);
assertEquals(ImmutableSet.of(barRule), fooRule.getDeps());
}
@Test
public void whenAllRulesAreRequestedWithDifferingIncludesThenRulesAreParsedTwice()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(
new FakeBuckConfig(
ImmutableMap.<String, Map<String, String>>of(
"buildfile", ImmutableMap.of("includes", "//bar.py")))),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
assertEquals("Should have invalidated cache.", 2, buildFileParserFactory.calls);
}
@Test
public void whenAllRulesThenSingleTargetRequestedThenRulesAreParsedOnce()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
BuildTarget foo = BuildTarget.builder("//java/com/facebook", "foo").build();
parser.buildTargetGraphForBuildTargets(
ImmutableList.of(foo),
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
assertEquals("Should have cached build rules.", 1, buildFileParserFactory.calls);
}
@Test
public void whenSingleTargetThenAllRulesRequestedThenRulesAreParsedOnce()
throws BuildFileParseException, BuildTargetException, IOException, InterruptedException {
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
Parser parser = createParser(emptyBuildTargets(), buildFileParserFactory);
BuildTarget foo = BuildTarget.builder("//java/com/facebook", "foo").build();
parser.buildTargetGraphForBuildTargets(
ImmutableList.of(foo),
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
parser.filterAllTargetsInProject(
filesystem,
new ParserConfig(new FakeBuckConfig()),
Predicates.<TargetNode<?>>alwaysTrue(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance(),
false /* enableProfiling */);
assertEquals("Should have replaced build rules", 1, buildFileParserFactory.calls);
}
@Test
@SuppressWarnings("unchecked") // Needed to mock generic class.
public void whenBuildFileTreeCacheInvalidatedTwiceDuringASingleBuildThenBuildFileTreeBuiltOnce()
throws IOException {
BuckEvent mockEvent = createMock(BuckEvent.class);
expect(mockEvent.getEventName()).andReturn("CommandStarted").anyTimes();
expect(mockEvent.getBuildId()).andReturn(new BuildId("BUILD1"));
BuildFileTree mockBuildFileTree = createMock(BuildFileTree.class);
Supplier<BuildFileTree> mockSupplier = createMock(Supplier.class);
expect(mockSupplier.get()).andReturn(mockBuildFileTree).once();
replay(mockEvent, mockSupplier, mockBuildFileTree);
Parser.BuildFileTreeCache cache = new Parser.BuildFileTreeCache(mockSupplier);
cache.onCommandStartedEvent(mockEvent);
cache.invalidateIfStale();
cache.get();
cache.invalidateIfStale();
cache.get();
verify(mockEvent, mockSupplier);
}
@Test
@SuppressWarnings("unchecked") // Needed to mock generic class.
public void whenBuildFileTreeCacheInvalidatedDuringTwoBuildsThenBuildFileTreeBuiltTwice()
throws IOException {
BuckEvent mockEvent = createMock(BuckEvent.class);
expect(mockEvent.getEventName()).andReturn("CommandStarted").anyTimes();
expect(mockEvent.getBuildId()).andReturn(new BuildId("BUILD1"));
expect(mockEvent.getBuildId()).andReturn(new BuildId("BUILD2"));
BuildFileTree mockBuildFileTree = createMock(BuildFileTree.class);
Supplier<BuildFileTree> mockSupplier = createMock(Supplier.class);
expect(mockSupplier.get()).andReturn(mockBuildFileTree).times(2);
replay(mockEvent, mockSupplier, mockBuildFileTree);
Parser.BuildFileTreeCache cache = new Parser.BuildFileTreeCache(mockSupplier);
cache.onCommandStartedEvent(mockEvent);
cache.invalidateIfStale();
cache.get();
cache.onCommandStartedEvent(mockEvent);
cache.invalidateIfStale();
cache.get();
verify(mockEvent, mockSupplier);
}
@Test(expected = BuildFileParseException.class)
public void whenSubprocessReturnsFailureThenProjectBuildFileParserThrowsOnClose()
throws IOException, BuildFileParseException, InterruptedException {
// This test depends on unix utilities that don't exist on Windows.
assumeTrue(Platform.detect() != Platform.WINDOWS);
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
try (ProjectBuildFileParser buildFileParser =
buildFileParserFactory.createNoopParserThatAlwaysReturnsError()) {
buildFileParser.initIfNeeded();
// close() is called implicitly at the end of this block. It must throw.
}
}
@Test
public void whenSubprocessReturnsSuccessThenProjectBuildFileParserClosesCleanly()
throws IOException, BuildFileParseException, InterruptedException {
// This test depends on unix utilities that don't exist on Windows.
assumeTrue(Platform.detect() != Platform.WINDOWS);
TestProjectBuildFileParserFactory buildFileParserFactory =
new TestProjectBuildFileParserFactory(filesystem, buildRuleTypes);
try (ProjectBuildFileParser buildFileParser =
buildFileParserFactory.createNoopParserThatAlwaysReturnsSuccess()) {
buildFileParser.initIfNeeded();
// close() is called implicitly at the end of this block. It must not throw.
}
}
@Test
public void whenBuildFilePathChangedThenFlavorsOfTargetsInPathAreInvalidated() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
tempDir.newFolder("bar");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'foo', visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
File testBarBuckFile = tempDir.newFile("bar/BUCK");
Files.write(
"java_library(name = 'bar',\n" +
" deps = ['//foo:foo'])\n",
testBarBuckFile,
Charsets.UTF_8);
// Fetch //bar:bar#src to put it in cache.
BuildTarget barTarget = BuildTarget
.builder("//bar", "bar")
.addFlavors(ImmutableFlavor.of("src"))
.build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(barTarget);
parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
// Rewrite //bar:bar so it doesn't depend on //foo:foo any more.
// Delete foo/BUCK and invalidate the cache, which should invalidate
// the cache entry for //bar:bar#src.
testFooBuckFile.delete();
Files.write(
"java_library(name = 'bar')\n",
testBarBuckFile,
Charsets.UTF_8);
WatchEvent<Path> deleteEvent = createPathEvent(
Paths.get("foo").resolve("BUCK"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(deleteEvent);
WatchEvent<Path> modifyEvent = createPathEvent(
Paths.get("bar").resolve("BUCK"),
StandardWatchEventKinds.ENTRY_MODIFY);
parser.onFileSystemChange(modifyEvent);
parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
}
@Test
public void whenBuildFileContainsSourcesUnderSymLinkNewSourcesNotAddedUntilCacheCleaned()
throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("bar");
tempDir.newFile("bar/Bar.java");
tempDir.newFolder("foo");
Path rootPath = tempDir.getRoot().toPath();
java.nio.file.Files.createSymbolicLink(rootPath.resolve("foo/bar"), rootPath.resolve("bar"));
Path testBuckFile = rootPath.resolve("foo").resolve("BUCK");
Files.write(
"java_library(name = 'lib', srcs=glob(['bar/*.java']))\n",
testBuckFile.toFile(),
Charsets.UTF_8);
// Fetch //:lib to put it in cache.
BuildTarget libTarget = BuildTarget.builder("//foo", "lib").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(libTarget);
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
{
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(ImmutableList.of(Paths.get("foo/bar/Bar.java")), libRule.getInputs());
}
tempDir.newFile("bar/Baz.java");
WatchEvent<Path> createEvent = createPathEvent(
Paths.get("bar/Baz.java"),
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(createEvent);
{
// Even though we've created this new file, the parser can't know it
// has anything to do with our lib (which looks in foo/*.java)
// until we clean the parser cache.
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(ImmutableList.of(Paths.get("foo/bar/Bar.java")), libRule.getInputs());
}
// Now tell the parser to forget about build files with inputs under symlinks.
parser.cleanCache();
{
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(
ImmutableList.of(Paths.get("foo/bar/Bar.java"), Paths.get("foo/bar/Baz.java")),
libRule.getInputs());
}
}
@Test
public void whenBuildFileContainsSourcesUnderSymLinkDeletedSourcesNotRemovedUntilCacheCleaned()
throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("bar");
tempDir.newFile("bar/Bar.java");
tempDir.newFolder("foo");
File bazSourceFile = tempDir.newFile("bar/Baz.java");
Path rootPath = tempDir.getRoot().toPath();
java.nio.file.Files.createSymbolicLink(rootPath.resolve("foo/bar"), rootPath.resolve("bar"));
Path testBuckFile = rootPath.resolve("foo").resolve("BUCK");
Files.write(
"java_library(name = 'lib', srcs=glob(['bar/*.java']))\n",
testBuckFile.toFile(),
Charsets.UTF_8);
// Fetch //:lib to put it in cache.
BuildTarget libTarget = BuildTarget.builder("//foo", "lib").build();
Iterable<BuildTarget> buildTargets = ImmutableList.of(libTarget);
BuckEventBus eventBus = BuckEventBusFactory.newInstance();
{
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(
ImmutableList.of(Paths.get("foo/bar/Bar.java"), Paths.get("foo/bar/Baz.java")),
libRule.getInputs());
}
bazSourceFile.delete();
WatchEvent<Path> deleteEvent = createPathEvent(
Paths.get("bar/Baz.java"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(deleteEvent);
{
// Even though we've deleted a source file, the parser can't know it
// has anything to do with our lib (which looks in foo/*.java)
// until we clean the parser cache.
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(
ImmutableList.of(Paths.get("foo/bar/Bar.java"), Paths.get("foo/bar/Baz.java")),
libRule.getInputs());
}
// Now tell the parser to forget about build files with inputs under symlinks.
parser.cleanCache();
{
TargetGraph targetGraph = parser.buildTargetGraphForBuildTargets(
buildTargets,
new ParserConfig(new FakeBuckConfig()),
eventBus,
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
ActionGraph graph = buildActionGraph(eventBus, targetGraph);
BuildRule libRule = graph.findBuildRuleByTarget(libTarget);
assertEquals(
ImmutableList.of(Paths.get("foo/bar/Bar.java")),
libRule.getInputs());
}
}
@Test
public void buildTargetHashCodePopulatesCorrectly() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("89ea162462da33de83d3ceed77bf3e87dc4e9a24")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
}
@Test
public void targetWithSourceFileChangesHash() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', srcs=glob(['*.java']), visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
File testFooJavaFile = tempDir.newFile("foo/Foo.java");
Files.write(
"// Ceci n'est pas une Javafile\n",
testFooJavaFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("d57f85b354fba3fc10722747c2048af7d88b7625")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
}
@Test
public void deletingSourceFileChangesHash() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', srcs=glob(['*.java']), visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
File testFooJavaFile = tempDir.newFile("foo/Foo.java");
Files.write(
"// Ceci n'est pas une Javafile\n",
testFooJavaFile,
Charsets.UTF_8);
File testBarJavaFile = tempDir.newFile("foo/Bar.java");
Files.write(
"// Seriously, no Java here\n",
testBarJavaFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("576a2a847bd78def7ac842d40735aa0a358d82a4")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
testBarJavaFile.delete();
WatchEvent<Path> deleteEvent = createPathEvent(
Paths.get("foo/Bar.java"),
StandardWatchEventKinds.ENTRY_DELETE);
parser.onFileSystemChange(deleteEvent);
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("d57f85b354fba3fc10722747c2048af7d88b7625")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
}
@Test
public void renamingSourceFileChangesHash() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', srcs=glob(['*.java']), visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
File testFooJavaFile = tempDir.newFile("foo/Foo.java");
Files.write(
"// Ceci n'est pas une Javafile\n",
testFooJavaFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("d57f85b354fba3fc10722747c2048af7d88b7625")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
Path testFooJavaFilePath = testFooJavaFile.toPath();
java.nio.file.Files.move(testFooJavaFilePath, testFooJavaFilePath.resolveSibling("Bar.java"));
WatchEvent<Path> deleteEvent = createPathEvent(
Paths.get("foo/Foo.java"),
StandardWatchEventKinds.ENTRY_DELETE);
WatchEvent<Path> createEvent = createPathEvent(
Paths.get("foo/Bar.java"),
StandardWatchEventKinds.ENTRY_CREATE);
parser.onFileSystemChange(deleteEvent);
parser.onFileSystemChange(createEvent);
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("361b267b0c9a71296fca5d3f11aec39912289c42")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget));
}
@Test
public void twoBuildTargetHashCodesPopulatesCorrectly() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', visibility=['PUBLIC'])\n" +
"java_library(name = 'lib2', visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
BuildTarget fooLib2Target = BuildTarget.builder("//foo", "lib2").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("89ea162462da33de83d3ceed77bf3e87dc4e9a24"),
fooLib2Target,
HashCode.fromString("6ba2b4d75e848ed204579d55ad616c2776596f7d")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget, fooLib2Target));
}
@Test
public void addingDepToTargetChangesHashOfDependingTargetOnly() throws Exception {
Parser parser = createParser(emptyBuildTargets());
tempDir.newFolder("foo");
File testFooBuckFile = tempDir.newFile("foo/BUCK");
Files.write(
"java_library(name = 'lib', visibility=['PUBLIC'], deps=[':lib2'])\n" +
"java_library(name = 'lib2', visibility=['PUBLIC'])\n",
testFooBuckFile,
Charsets.UTF_8);
BuildTarget fooLibTarget = BuildTarget.builder("//foo", "lib").build();
BuildTarget fooLib2Target = BuildTarget.builder("//foo", "lib2").build();
assertEquals(
ImmutableMap.of(
fooLibTarget,
HashCode.fromString("0176c450c8f85c1a2a4942923488c3552e03a42f"),
fooLib2Target,
HashCode.fromString("6ba2b4d75e848ed204579d55ad616c2776596f7d")),
buildTargetGraphAndGetHashCodes(parser, fooLibTarget, fooLib2Target));
}
private ImmutableMap<BuildTarget, HashCode> buildTargetGraphAndGetHashCodes(
Parser parser,
BuildTarget... buildTargets) throws Exception {
// Build the target graph so we can access the hash code cache.
//
// TODO(user): It'd be really nice if parser.getBuildTargetHashCodeCache()
// knew how to run the parser for targets that weren't yet parsed, but
// then we'd need to pass in the BuckEventBusFactory, Console, etc.
// to every call to get()..
ImmutableList<BuildTarget> buildTargetsList = ImmutableList.copyOf(buildTargets);
parser.buildTargetGraphForBuildTargets(
buildTargetsList,
new ParserConfig(new FakeBuckConfig()),
BuckEventBusFactory.newInstance(),
new TestConsole(),
ImmutableMap.<String, String>of(),
/* enableProfiling */ false);
return parser.getBuildTargetHashCodeCache().getAll(buildTargetsList);
}
private ActionGraph buildActionGraph(BuckEventBus eventBus, TargetGraph targetGraph) {
return new TargetGraphToActionGraph(eventBus, new BuildTargetNodeToBuildRuleTransformer())
.apply(targetGraph);
}
private Iterable<Map<String, Object>> emptyBuildTargets() {
return Sets.newHashSet();
}
/**
* ProjectBuildFileParser test double which counts the number of times rules are parsed to test
* caching logic in Parser.
*/
private static class TestProjectBuildFileParserFactory implements ProjectBuildFileParserFactory {
private final ProjectFilesystem projectFilesystem;
private final KnownBuildRuleTypes buildRuleTypes;
public int calls = 0;
public TestProjectBuildFileParserFactory(
ProjectFilesystem projectFilesystem,
KnownBuildRuleTypes buildRuleTypes) {
this.projectFilesystem = projectFilesystem;
this.buildRuleTypes = buildRuleTypes;
}
@Override
public ProjectBuildFileParser createParser(
Console console,
ImmutableMap<String, String> environment,
BuckEventBus buckEventBus) {
return new TestProjectBuildFileParser("python" /* pythonInterpreter */);
}
public ProjectBuildFileParser createNoopParserThatAlwaysReturnsError() {
// "false" is a unix utility that always returns error code 1 (failure).
return new TestProjectBuildFileParser("false" /* pythonInterpreter */);
}
public ProjectBuildFileParser createNoopParserThatAlwaysReturnsSuccess() {
// "true" is a unix utility that always returns error code 0 (success).
return new TestProjectBuildFileParser("true" /* pythonInterpreter */);
}
private class TestProjectBuildFileParser extends ProjectBuildFileParser {
public TestProjectBuildFileParser(String pythonInterpreter) {
super(
projectFilesystem,
new ParserConfig(
new FakeBuckConfig(
ImmutableMap.<String, Map<String, String>>of(
"buildfile",
ImmutableMap.of("includes", "//java/com/facebook/defaultIncludeFile"),
"python",
ImmutableMap.of("interpreter", pythonInterpreter)))),
buildRuleTypes.getAllDescriptions(),
new TestConsole(),
ImmutableMap.<String, String>of(),
BuckEventBusFactory.newInstance());
}
@Override
protected List<Map<String, Object>> getAllRulesInternal(Path buildFile)
throws IOException {
calls += 1;
return super.getAllRulesInternal(buildFile);
}
}
}
/**
* Analogue to {@link Suppliers#ofInstance(Object)}.
*/
private static Supplier<BuildFileTree> ofInstance(final BuildFileTree buildFileTree) {
return new Supplier<BuildFileTree>() {
@Override
public BuildFileTree get() {
return buildFileTree;
}
};
}
}