/*
* Copyright 2015-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.event.listener;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import com.facebook.buck.event.LeafEvent;
import com.facebook.buck.event.TestEventConfigurator;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetFactory;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleDurationTracker;
import com.facebook.buck.rules.BuildRuleEvent;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.DefaultTargetNodeToBuildRuleTransformer;
import com.facebook.buck.rules.FakeBuildRule;
import com.facebook.buck.rules.RuleKey;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.keys.FakeRuleKeyFactory;
import com.facebook.buck.step.StepEvent;
import com.facebook.buck.timing.ClockDuration;
import com.facebook.buck.util.Ansi;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.hash.HashCode;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class BuildThreadStateRendererTest {
private static final Ansi ANSI = Ansi.withoutTty();
private static final Function<Long, String> FORMAT_TIME_FUNCTION =
timeMs -> String.format(Locale.US, "%.1fs", timeMs / 1000.0);
private static final SourcePathResolver PATH_RESOLVER =
new SourcePathResolver(
new SourcePathRuleFinder(
new BuildRuleResolver(
TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer())));
private static final BuildTarget TARGET1 = BuildTargetFactory.newInstance("//:target1");
private static final BuildTarget TARGET2 = BuildTargetFactory.newInstance("//:target2");
private static final BuildTarget TARGET3 = BuildTargetFactory.newInstance("//:target3");
private static final BuildTarget TARGET4 = BuildTargetFactory.newInstance("//:target4");
private static final BuildRule RULE1 = createFakeRule(TARGET1);
private static final BuildRule RULE2 = createFakeRule(TARGET2);
private static final BuildRule RULE3 = createFakeRule(TARGET3);
private static final BuildRule RULE4 = createFakeRule(TARGET4);
@Test
public void emptyInput() {
BuildThreadStateRenderer renderer = createRenderer(2100, ImmutableMap.of(), ImmutableMap.of());
assertThat(renderLines(renderer, true), is(equalTo(ImmutableList.<String>of())));
assertThat(renderLines(renderer, false), is(equalTo(ImmutableList.<String>of())));
assertThat(renderShortStatus(renderer, true), is(equalTo(ImmutableList.<String>of())));
assertThat(renderShortStatus(renderer, false), is(equalTo(ImmutableList.<String>of())));
}
@Test
public void commonCase() {
BuildThreadStateRenderer renderer =
createRenderer(
4200,
ImmutableMap.of(
1L, createRuleBeginningEventOptional(1, 1200, 1400, RULE2),
3L, createRuleBeginningEventOptional(3, 2300, 700, RULE3),
4L, createRuleBeginningEventOptional(4, 1100, 200, RULE1),
5L, Optional.empty(),
8L, createRuleBeginningEventOptional(6, 3000, 0, RULE4)),
ImmutableMap.of(
1L, createStepStartedEventOptional(1, 1500, "step A"),
3L, Optional.empty(),
4L, Optional.empty(),
5L, Optional.empty(),
8L, createStepStartedEventOptional(1, 3700, "step B")));
assertThat(
renderLines(renderer, true),
is(
equalTo(
ImmutableList.of(
" |=> //:target2... 4.4s (running step A[2.7s])",
" |=> //:target1... 3.3s (checking_cache)",
" |=> //:target3... 2.6s (checking_cache)",
" |=> //:target4... 1.2s (running step B[0.5s])",
" |=> IDLE"))));
assertThat(
renderLines(renderer, false),
is(
equalTo(
ImmutableList.of(
" |=> //:target2... 4.4s (running step A[2.7s])",
" |=> //:target3... 2.6s (checking_cache)",
" |=> //:target1... 3.3s (checking_cache)",
" |=> IDLE",
" |=> //:target4... 1.2s (running step B[0.5s])"))));
assertThat(
renderShortStatus(renderer, true),
is(equalTo(ImmutableList.of("[:]", "[:]", "[:]", "[:]", "[ ]"))));
assertThat(
renderShortStatus(renderer, false),
is(equalTo(ImmutableList.of("[:]", "[:]", "[:]", "[ ]", "[:]"))));
}
@Test
public void withMissingInformation() {
// SuperConsoleEventBusListener stores the data it passes to the renderer in a map that might
// be concurrently modified from other threads. It is important that the renderer can handle
// data containing inconsistencies.
BuildThreadStateRenderer renderer =
createRenderer(
4200,
ImmutableMap.of(
3L, createRuleBeginningEventOptional(3, 2300, 700, RULE3),
5L, Optional.empty(),
8L, createRuleBeginningEventOptional(6, 3000, 0, RULE4)),
ImmutableMap.of(
1L, createStepStartedEventOptional(1, 1500, "step A"),
4L, Optional.empty(),
5L, Optional.empty(),
8L, createStepStartedEventOptional(1, 3700, "step B")));
assertThat(
renderLines(renderer, true),
is(
equalTo(
ImmutableList.of(
// one missing build rule - no output
" |=> //:target3... 2.6s (checking_cache)", // missing step information
" |=> //:target4... 1.2s (running step B[0.5s])",
" |=> IDLE")))); // missing accumulated time - show as IDLE
assertThat(
renderShortStatus(renderer, true), is(equalTo(ImmutableList.of("[:]", "[:]", "[ ]"))));
}
private static BuildRule createFakeRule(BuildTarget target) {
return new FakeBuildRule(target, PATH_RESOLVER, ImmutableSortedSet.of());
}
private static Optional<? extends BuildRuleEvent.BeginningBuildRuleEvent>
createRuleBeginningEventOptional(
long threadId, long timeMs, long durationMs, BuildRule rule) {
BuildRuleDurationTracker durationTracker = new BuildRuleDurationTracker();
durationTracker.setDuration(rule, new ClockDuration(durationMs, 0, 0));
RuleKey ruleKey = new RuleKey(HashCode.fromString("aa"));
return Optional.of(
TestEventConfigurator.configureTestEventAtTime(
BuildRuleEvent.resumed(
rule,
durationTracker,
new FakeRuleKeyFactory(ImmutableMap.of(rule.getBuildTarget(), ruleKey))),
timeMs,
TimeUnit.MILLISECONDS,
threadId));
}
private static Optional<? extends LeafEvent> createStepStartedEventOptional(
long threadId, long timeMs, String name) {
return Optional.of(
TestEventConfigurator.configureTestEventAtTime(
StepEvent.started(name, name + " description", UUID.randomUUID()),
timeMs,
TimeUnit.MILLISECONDS,
threadId));
}
private BuildThreadStateRenderer createRenderer(
long timeMs,
Map<Long, Optional<? extends BuildRuleEvent.BeginningBuildRuleEvent>> buildEvents,
Map<Long, Optional<? extends LeafEvent>> runningSteps) {
return new BuildThreadStateRenderer(
ANSI,
FORMAT_TIME_FUNCTION,
timeMs,
runningSteps,
new BuildRuleThreadTracker(buildEvents, ImmutableMap.of()));
}
private ImmutableList<String> renderLines(BuildThreadStateRenderer renderer, boolean sortByTime) {
ImmutableList.Builder<String> lines = ImmutableList.builder();
StringBuilder lineBuilder = new StringBuilder();
for (long threadId : renderer.getSortedExecutorIds(sortByTime)) {
lineBuilder.delete(0, lineBuilder.length());
lines.add(renderer.renderStatusLine(threadId, lineBuilder));
}
return lines.build();
}
private ImmutableList<String> renderShortStatus(
BuildThreadStateRenderer renderer, boolean sortByTime) {
ImmutableList.Builder<String> status = ImmutableList.builder();
for (long threadId : renderer.getSortedExecutorIds(sortByTime)) {
status.add(renderer.renderShortStatus(threadId));
}
return status.build();
}
}