blob: 059bfea03bfd77911d2c2b8315433236f64341b4 [file] [log] [blame]
// Copyright 2020 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.bazel.rules.ninja;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.bazel.rules.ninja.actions.NinjaPhonyTargetsUtil;
import com.google.devtools.build.lib.bazel.rules.ninja.actions.PhonyTarget;
import com.google.devtools.build.lib.bazel.rules.ninja.file.ByteBufferFragment;
import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException;
import com.google.devtools.build.lib.bazel.rules.ninja.lexer.NinjaLexer;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaParserStep;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaScope;
import com.google.devtools.build.lib.bazel.rules.ninja.parser.NinjaTarget;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link com.google.devtools.build.lib.bazel.rules.ninja.actions.NinjaPhonyTargetsUtil}.
*/
@RunWith(JUnit4.class)
public class NinjaPhonyTargetsUtilTest {
@Test
public void testPathsTree() throws Exception {
ImmutableList<String> targetTexts =
ImmutableList.of(
"build alias9: phony alias2 alias3 direct1 direct2",
"build alias2: phony direct3 direct4",
"build alias3: phony alias4 direct4 direct5",
"build alias4: phony alias2");
ImmutableSortedMap<PathFragment, PhonyTarget> pathsMap =
NinjaPhonyTargetsUtil.getPhonyPathsMap(buildPhonyTargets(targetTexts));
assertThat(pathsMap).hasSize(4);
checkMapping(pathsMap, "alias9", "direct1", "direct2", "direct3", "direct4", "direct5");
checkMapping(pathsMap, "alias2", "direct3", "direct4");
checkMapping(pathsMap, "alias3", "direct3", "direct4", "direct5");
checkMapping(pathsMap, "alias4", "direct3", "direct4");
}
@Test
public void testDag() throws Exception {
ImmutableList<String> targetTexts =
ImmutableList.of(
"build _alias9: phony alias1 alias2",
"build alias1: phony deep1",
"build alias2: phony deep2",
"build deep1: phony leaf1",
"build deep2: phony leaf2 alias1");
ImmutableSortedMap<PathFragment, PhonyTarget> pathsMap =
NinjaPhonyTargetsUtil.getPhonyPathsMap(buildPhonyTargets(targetTexts));
assertThat(pathsMap).hasSize(5);
checkMapping(pathsMap, "_alias9", "leaf1", "leaf2");
checkMapping(pathsMap, "alias1", "leaf1");
checkMapping(pathsMap, "alias2", "leaf1", "leaf2");
checkMapping(pathsMap, "deep1", "leaf1");
checkMapping(pathsMap, "deep2", "leaf1", "leaf2");
}
@Test
public void testAlwaysDirty() throws Exception {
ImmutableList<String> targetTexts =
ImmutableList.of(
"build alias9: phony alias2 alias3 direct1 direct2",
"build alias2: phony direct3 direct4",
"build alias3: phony alias4 direct4 direct5",
// alias4 is always-dirty
"build alias4: phony");
ImmutableSortedMap<PathFragment, PhonyTarget> pathsMap =
NinjaPhonyTargetsUtil.getPhonyPathsMap(buildPhonyTargets(targetTexts));
assertThat(pathsMap).hasSize(4);
// alias4 and its transitive closure is always-dirty
checkMapping(pathsMap, "alias9", true, "direct1", "direct2", "direct3", "direct4", "direct5");
checkMapping(pathsMap, "alias2", false, "direct3", "direct4");
checkMapping(pathsMap, "alias3", true, "direct4", "direct5");
checkMapping(pathsMap, "alias4", true);
}
private static void checkMapping(
ImmutableSortedMap<PathFragment, PhonyTarget> pathsMap, String key, String... values) {
checkMapping(pathsMap, key, false, values);
}
private static void checkMapping(
ImmutableSortedMap<PathFragment, PhonyTarget> pathsMap,
String key,
boolean isAlwaysDirty,
String... values) {
Set<PathFragment> expectedPaths =
Arrays.stream(values).map(PathFragment::create).collect(Collectors.toSet());
PhonyTarget phonyTarget = pathsMap.get(PathFragment.create(key));
assertThat(phonyTarget).isNotNull();
assertThat(phonyTarget.isAlwaysDirty()).isEqualTo(isAlwaysDirty);
ImmutableSortedSet.Builder<PathFragment> paths = ImmutableSortedSet.naturalOrder();
pathsMap.get(PathFragment.create(key)).visitUsualInputs(pathsMap, paths::addAll);
assertThat(paths.build()).containsExactlyElementsIn(expectedPaths);
}
private static ImmutableSortedMap<PathFragment, NinjaTarget> buildPhonyTargets(
ImmutableList<String> targetTexts) throws Exception {
ImmutableSortedMap.Builder<PathFragment, NinjaTarget> builder =
ImmutableSortedMap.naturalOrder();
for (String text : targetTexts) {
NinjaTarget ninjaTarget = parseNinjaTarget(text);
builder.put(Iterables.getOnlyElement(ninjaTarget.getAllOutputs()), ninjaTarget);
}
return builder.build();
}
@Test
public void testEmptyMap() throws Exception {
assertThat(NinjaPhonyTargetsUtil.getPhonyPathsMap(ImmutableSortedMap.of())).isEmpty();
}
@Test
public void testCycle() {
ImmutableList<String> targetTexts =
ImmutableList.of(
"build alias1: phony alias2 direct1", "build alias2: phony alias1 direct2");
GenericParsingException exception =
assertThrows(
GenericParsingException.class,
() -> NinjaPhonyTargetsUtil.getPhonyPathsMap(buildPhonyTargets(targetTexts)));
assertThat(exception)
.hasMessageThat()
.isEqualTo("Detected a dependency cycle involving the phony target 'alias1'");
}
private static NinjaTarget parseNinjaTarget(String text) throws Exception {
NinjaScope fileScope = new NinjaScope();
return createParser(text).parseNinjaTarget(fileScope, 0);
}
private static NinjaParserStep createParser(String text) {
ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.ISO_8859_1));
NinjaLexer lexer = new NinjaLexer(new ByteBufferFragment(buffer, 0, buffer.limit()));
return new NinjaParserStep(lexer);
}
}