blob: 8b20ec5a2c22449f3185d8847fa9b059e9787ff1 [file] [log] [blame]
// Copyright 2017 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.analysis;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.AspectCollection.AspectCycleOnPathException;
import com.google.devtools.build.lib.analysis.AspectCollection.AspectDeps;
import com.google.devtools.build.lib.packages.Aspect;
import com.google.devtools.build.lib.packages.AspectDefinition;
import com.google.devtools.build.lib.packages.AspectDescriptor;
import com.google.devtools.build.lib.packages.AspectParameters;
import com.google.devtools.build.lib.packages.NativeAspectClass;
import com.google.devtools.build.lib.packages.StarlarkProviderIdentifier;
import com.google.devtools.build.lib.util.Pair;
import java.util.HashMap;
import java.util.HashSet;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link AspectCollection}
*/
@RunWith(JUnit4.class)
public class AspectCollectionTest {
/**
* a3 wants a1 and a2, a1 and a2 want no one, path is a1, a2, a3.
*/
@Test
public void linearAspectPath1() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3", "a1", "a2");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
expectDeps(a3, a1, a2),
expectDeps(a1),
expectDeps(a2));
}
/**
* a3 wants a2, a2 wants a1, a1 wants no one, path is a1, a2, a3.
*/
@Test
public void linearAspectPath2() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a2");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
expectDeps(a3, a2),
expectDeps(a2, a1),
expectDeps(a1));
}
/**
* a3 wants a1, a1 wants a2, path is a1, a2, a3, so a2 comes after a1.
*/
@Test
public void validateOrder() throws Exception {
Aspect a1 = createAspect("a1", "a2");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3", "a1");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
expectDeps(a1),
expectDeps(a2),
expectDeps(a3, a1));
}
/**
* a3 wants a1, a1 wants a2, a2 wants a1, path is a1, a2, a3, so a2 comes after a1.
*/
@Test
public void validateOrder2() throws Exception {
Aspect a1 = createAspect("a1", "a2");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a1");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
expectDeps(a1),
expectDeps(a2, a1),
expectDeps(a3, a1));
}
/**
* a3 wants itself.
*/
@Test
public void recursive() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3", "a3");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection, ImmutableList.of(a1, a2, a3), expectDeps(a1), expectDeps(a2), expectDeps(a3));
}
/** a2 wants a1, a3 wants nothing. */
@Test
public void threeAspects() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
expectDeps(a3),
expectDeps(a2, a1),
expectDeps(a1));
}
/**
* a2 wants a1, a3 wants a1 and a2, the path is [a2, a1, a2, a3], so a2 occurs twice.
*
* First occurrence of a2 would not see a1, but the second would: that is an error.
*/
@Test
public void duplicateAspect() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a2", "a1");
AspectCycleOnPathException e =
assertThrows(
AspectCycleOnPathException.class,
() -> AspectCollection.create(ImmutableList.of(a2, a1, a2, a3)));
assertThat(e.getAspect()).isEqualTo(a2.getDescriptor());
assertThat(e.getPreviousAspect()).isEqualTo(a1.getDescriptor());
}
/**
* a2 wants a1, a3 wants a2, the path is [a2, a1, a2, a3], so a2 occurs twice.
*
* First occurrence of a2 would not see a1, but the second would: that is an error.
*/
@Test
public void duplicateAspect2() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a2");
AspectCycleOnPathException e =
assertThrows(
AspectCycleOnPathException.class,
() -> AspectCollection.create(ImmutableList.of(a2, a1, a2, a3)));
assertThat(e.getAspect()).isEqualTo(a2.getDescriptor());
assertThat(e.getPreviousAspect()).isEqualTo(a1.getDescriptor());
}
/**
* a3 wants a1 and a2, a2 does not want a1.
* The path is [a2, a1, a2, a3], so a2 occurs twice.
* Second occurrence of a2 is consistent with the first.
*/
@Test
public void duplicateAspect2a() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3", "a1", "a2");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a2, a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a2, a1, a3),
expectDeps(a2),
expectDeps(a1),
expectDeps(a3, a2, a1));
}
/**
* a2 wants a1, a3 wants a1 and a2, a1 wants a2. the path is [a2, a1, a2, a3], so a2 occurs twice.
* First occurrence of a2 does not see a1, but the second does => error.
*/
@Test
public void duplicateAspect3() throws Exception {
Aspect a1 = createAspect("a1", "a2");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a1", "a2");
AspectCycleOnPathException e =
assertThrows(
AspectCycleOnPathException.class,
() -> AspectCollection.create(ImmutableList.of(a2, a1, a2, a3)));
assertThat(e.getAspect()).isEqualTo(a2.getDescriptor());
assertThat(e.getPreviousAspect()).isEqualTo(a1.getDescriptor());
}
/**
* a2 wants a1, a3 wants a2, a1 wants a2. the path is [a2, a1, a2, a3], so a2 occurs twice.
* First occurrence of a2 does not see a1, but the second does => error.
* a1 disappears.
*/
@Test
public void duplicateAspect4() throws Exception {
Aspect a1 = createAspect("a1", "a2");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3", "a2");
AspectCycleOnPathException e =
assertThrows(
AspectCycleOnPathException.class,
() -> AspectCollection.create(ImmutableList.of(a2, a1, a2, a3)));
assertThat(e.getAspect()).isEqualTo(a2.getDescriptor());
assertThat(e.getPreviousAspect()).isEqualTo(a1.getDescriptor());
}
/**
* a3 wants a2, a1 wants a2. The path is [a2, a1, a2, a3], so a2 occurs twice. First occurrence of
* a2 is consistent with the second. a1 disappears.
*/
@Test
public void duplicateAspect5() throws Exception {
Aspect a1 = createAspect("a1", "a2");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3", "a2");
AspectCollection collection = AspectCollection.create(ImmutableList.of(a2, a1, a2, a3));
validateAspectCollection(
collection,
ImmutableList.of(a2, a1, a3),
expectDeps(a2),
expectDeps(a1, a2),
expectDeps(a3, a2));
}
private static Pair<Aspect, ImmutableList<Aspect>> expectDeps(Aspect a, Aspect... deps) {
return Pair.of(a, ImmutableList.copyOf(deps));
}
@SafeVarargs
private static void validateAspectCollection(
AspectCollection collection,
ImmutableList<Aspect> expectedUsedAspects,
Pair<Aspect, ImmutableList<Aspect>>... expectedPaths) {
assertThat(Iterables.transform(collection.getUsedAspects(), AspectDeps::getAspect))
.containsExactlyElementsIn(Iterables.transform(expectedUsedAspects, Aspect::getDescriptor))
.inOrder();
validateAspectPaths(
collection,
ImmutableList.copyOf(expectedPaths)
);
}
private static void validateAspectPaths(AspectCollection collection,
ImmutableList<Pair<Aspect, ImmutableList<Aspect>>> expectedList) {
HashMap<AspectDescriptor, AspectDeps> allPaths = new HashMap<>();
for (AspectDeps aspectPath : collection.getUsedAspects()) {
collectAndValidateAspectDeps(aspectPath, allPaths);
}
HashSet<AspectDescriptor> expectedKeys = new HashSet<>();
for (Pair<Aspect, ImmutableList<Aspect>> expected : expectedList) {
assertThat(allPaths).containsKey(expected.first.getDescriptor());
AspectDeps aspectPath = allPaths.get(expected.first.getDescriptor());
assertThat(Iterables.transform(aspectPath.getUsedAspects(), AspectDeps::getAspect))
.containsExactlyElementsIn(Iterables.transform(expected.second, Aspect::getDescriptor))
.inOrder();
expectedKeys.add(expected.first.getDescriptor());
}
assertThat(allPaths.keySet())
.containsExactlyElementsIn(expectedKeys);
}
/**
* Collects all aspect paths transitively visible from {@code aspectDeps}.
* Validates that {@link AspectDeps} instance corresponding to a given {@link AspectDescriptor}
* is unique.
*/
private static void collectAndValidateAspectDeps(AspectDeps aspectDeps,
HashMap<AspectDescriptor, AspectDeps> allDeps) {
if (allDeps.containsKey(aspectDeps.getAspect())) {
assertWithMessage(String.format("Two different deps for aspect %s", aspectDeps.getAspect()))
.that(allDeps.get(aspectDeps.getAspect()))
.isSameInstanceAs(aspectDeps);
return;
}
allDeps.put(aspectDeps.getAspect(), aspectDeps);
for (AspectDeps path : aspectDeps.getUsedAspects()) {
collectAndValidateAspectDeps(path, allDeps);
}
}
/**
* Creates an aspect with a class named {@code className} advertizing a provider {@code className}
* that requires any of providers {@code requiredAspects}.
*/
private Aspect createAspect(final String className, String... requiredAspects) {
ImmutableList.Builder<ImmutableSet<StarlarkProviderIdentifier>> requiredProvidersBuilder =
ImmutableList.builder();
for (String requiredAspect : requiredAspects) {
requiredProvidersBuilder.add(
ImmutableSet.of((StarlarkProviderIdentifier.forLegacy(requiredAspect))));
}
final ImmutableList<ImmutableSet<StarlarkProviderIdentifier>> requiredProviders =
requiredProvidersBuilder.build();
return Aspect.forNative(
new NativeAspectClass() {
@Override
public String getName() {
return className;
}
@Override
public AspectDefinition getDefinition(AspectParameters aspectParameters) {
return AspectDefinition.builder(this)
.requireAspectsWithProviders(requiredProviders)
.advertiseProvider(
ImmutableList.of(StarlarkProviderIdentifier.forLegacy(className)))
.build();
}
});
}
}