/*
* Copyright 2016-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.rules;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import com.facebook.buck.event.AbstractBuckEvent;
import com.facebook.buck.event.BuckEvent;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.DefaultBuckEventBus;
import com.facebook.buck.event.EventKey;
import com.facebook.buck.model.BuildId;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.timing.FakeClock;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.eventbus.Subscribe;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Stream;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class UnskippedRulesTrackerTest {
private SourcePathResolver sourcePathResolver;
private UnskippedRulesTracker unskippedRulesTracker;
private BuckEventBus eventBus;
private BlockingQueue<BuckEvent> events = new LinkedBlockingQueue<>();
private BuildRule ruleA;
private BuildRule ruleB;
private BuildRule ruleC;
private BuildRule ruleD;
private BuildRule ruleE;
private BuildRule ruleF;
private BuildRule ruleG;
private BuildRule ruleH;
@Before
public void setUp() {
BuildRuleResolver resolver =
new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
sourcePathResolver = new SourcePathResolver(ruleFinder);
RuleDepsCache depsCache = new RuleDepsCache(resolver);
unskippedRulesTracker = new UnskippedRulesTracker(depsCache, resolver);
eventBus = new DefaultBuckEventBus(new FakeClock(1), new BuildId());
eventBus.register(
new Object() {
@Subscribe
public void onUnskippedRuleCountUpdated(BuckEvent event) {
events.add(event);
}
});
ruleH = resolver.addToIndex(createRule("//:h"));
ruleG = resolver.addToIndex(createRule("//:g"));
ruleF = resolver.addToIndex(createRule("//:f"));
ruleE = resolver.addToIndex(createRule("//:e", ImmutableSet.of(ruleG, ruleH)));
ruleD = resolver.addToIndex(createRule("//:d", ImmutableSet.of(ruleG), ImmutableSet.of(ruleF)));
ruleC = resolver.addToIndex(createRule("//:c", ImmutableSet.of(ruleD, ruleE)));
ruleB = resolver.addToIndex(createRule("//:b", ImmutableSet.of(), ImmutableSet.of(ruleD)));
ruleA = resolver.addToIndex(createRule("//:a", ImmutableSet.of(ruleD)));
}
@After
public void checkForExtraEvents() throws InterruptedException {
assertNoNewEvents();
}
// Visualisation of the action graph (rules depend on rules below them):
//
// a b c
// \|/ \
// d e
// / \ / \
// f g h
//
// b -> d and d -> f are runtime dependencies.
@Test
public void addingRuleMarksItsTransitiveDepsAsUnskipped() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.registerTopLevelRule(ruleC, eventBus);
assertReceivedEvent(7);
}
@Test
public void addingRulesDepsDoesNotChangeState() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.registerTopLevelRule(ruleD, eventBus);
assertNoNewEvents();
}
@Test
public void usingRuleMarksItsDepsAsSkipped() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleC, eventBus);
assertReceivedEvent(6);
unskippedRulesTracker.markRuleAsUsed(ruleE, eventBus);
assertReceivedEvent(5);
}
@Test
public void usedRuleIsNeverSkipped() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.markRuleAsUsed(ruleF, eventBus);
assertNoNewEvents();
unskippedRulesTracker.markRuleAsUsed(ruleD, eventBus);
assertReceivedEvent(3);
unskippedRulesTracker.markRuleAsUsed(ruleA, eventBus);
assertNoNewEvents();
}
@Test
public void rulesCanBeMarkedAsUsedEvenIfNoTopLevelRuleIsRegistered() throws InterruptedException {
unskippedRulesTracker.markRuleAsUsed(ruleF, eventBus);
assertReceivedEvent(1);
unskippedRulesTracker.markRuleAsUsed(ruleD, eventBus);
assertReceivedEvent(2);
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(3);
}
@Test
public void onlyTopLevelRuleExecuted() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.markRuleAsUsed(ruleA, eventBus);
assertReceivedEvent(1);
}
@Test
public void usingARuleDoesNotMarkItsRuntimeDepsAsSkipped() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleB, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.markRuleAsUsed(ruleB, eventBus);
assertNoNewEvents();
unskippedRulesTracker.markRuleAsUsed(ruleD, eventBus);
assertReceivedEvent(3);
unskippedRulesTracker.markRuleAsUsed(ruleF, eventBus);
assertNoNewEvents();
}
@Test
public void multipleTopLevelRules() throws InterruptedException {
unskippedRulesTracker.registerTopLevelRule(ruleA, eventBus);
assertReceivedEvent(4);
unskippedRulesTracker.markRuleAsUsed(ruleF, eventBus);
assertNoNewEvents();
unskippedRulesTracker.registerTopLevelRule(ruleB, eventBus);
assertReceivedEvent(5);
unskippedRulesTracker.registerTopLevelRule(ruleC, eventBus);
assertReceivedEvent(8);
unskippedRulesTracker.markRuleAsUsed(ruleD, eventBus);
assertNoNewEvents();
unskippedRulesTracker.markRuleAsUsed(ruleA, eventBus);
assertNoNewEvents();
unskippedRulesTracker.markRuleAsUsed(ruleC, eventBus);
assertReceivedEvent(5);
unskippedRulesTracker.markRuleAsUsed(ruleB, eventBus);
assertNoNewEvents();
}
private void assertReceivedEvent(int numRules) throws InterruptedException {
BuckEvent event = events.take();
assertThat(event, is(instanceOf(BuildEvent.UnskippedRuleCountUpdated.class)));
BuildEvent.UnskippedRuleCountUpdated countEvent = (BuildEvent.UnskippedRuleCountUpdated) event;
assertThat(countEvent.getNumRules(), is(equalTo(numRules)));
}
private void assertNoNewEvents() throws InterruptedException {
// BuckEventBus is asynchronous so it is not enough to check that the events queue is empty.
// Instead, post a fake event and check that there were no pending events before it.
BuckEvent sentinel = new FakeEvent();
eventBus.post(sentinel);
assertThat(
"An event was received but no event was expected",
events.take(),
is(sameInstance(sentinel)));
}
private BuildRule createRule(String buildTarget) {
return new FakeBuildRule(
BuildTargetFactory.newInstance(buildTarget), sourcePathResolver, ImmutableSortedSet.of());
}
private BuildRule createRule(String buildTarget, ImmutableSet<BuildRule> deps) {
return new FakeBuildRule(
BuildTargetFactory.newInstance(buildTarget),
sourcePathResolver,
ImmutableSortedSet.copyOf(deps));
}
private BuildRule createRule(
String buildTarget, ImmutableSet<BuildRule> deps, ImmutableSet<BuildRule> runtimeDeps) {
return new FakeBuildRuleWithRuntimeDeps(
BuildTargetFactory.newInstance(buildTarget),
sourcePathResolver,
ImmutableSortedSet.copyOf(deps),
ImmutableSortedSet.copyOf(runtimeDeps));
}
private static class FakeBuildRuleWithRuntimeDeps extends FakeBuildRule
implements HasRuntimeDeps {
private final ImmutableSortedSet<BuildRule> runtimeDeps;
public FakeBuildRuleWithRuntimeDeps(
BuildTarget target,
SourcePathResolver resolver,
ImmutableSortedSet<BuildRule> deps,
ImmutableSortedSet<BuildRule> runtimeDeps) {
super(target, resolver, deps);
this.runtimeDeps = runtimeDeps;
}
@Override
public Stream<BuildTarget> getRuntimeDeps() {
return runtimeDeps.stream().map(BuildRule::getBuildTarget);
}
}
private static class FakeEvent extends AbstractBuckEvent {
public FakeEvent() {
super(EventKey.unique());
}
@Override
protected String getValueString() {
return "Fake event";
}
@Override
public String getEventName() {
return "FakeEvent";
}
}
}