blob: a53a20db2166a6e79bb34596c2d8eb80d68bc7b1 [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.fail;
import com.google.common.base.Function;
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.SkylarkProviderIdentifier;
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 {
private static final Function<Aspect, AspectDescriptor> ASPECT_TO_DESCRIPTOR =
new Function<Aspect, AspectDescriptor>() {
@Override
public AspectDescriptor apply(Aspect aspect) {
return aspect.getDescriptor();
}
};
private static final Function<AspectDeps, AspectDescriptor> ASPECT_PATH_TO_DESCRIPTOR =
new Function<AspectDeps, AspectDescriptor>() {
@Override
public AspectDescriptor apply(AspectDeps aspectPath) {
return aspectPath.getAspect();
}
};
/**
* 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), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
ImmutableList.of(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), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
ImmutableList.of(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), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a1, a3),
ImmutableList.of(a3),
expectDeps(a3, a1),
expectDeps(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), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a1, a3),
ImmutableList.of(a3),
expectDeps(a3, a1),
expectDeps(a1)
);
}
/**
* a3 wants no one => a1 and a2 must be removed.
*/
@Test
public void unneededRemoved() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2");
Aspect a3 = createAspect("a3");
AspectCollection collection = AspectCollection
.create(ImmutableList.of(a1, a2, a3), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a3),
ImmutableList.of(a3),
expectDeps(a3)
);
}
/**
* 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), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a3),
ImmutableList.of(a3),
expectDeps(a3)
);
}
/**
* a2 (non-visible aspect) wants itself, a3 wants a2.
*/
@Test
public void recursiveNonVisible() throws Exception{
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a2");
Aspect a3 = createAspect("a3", "a2");
AspectCollection collection = AspectCollection
.create(ImmutableList.of(a1, a2, a3), ImmutableSet.of(a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a2, a3),
ImmutableList.of(a3),
expectDeps(a3, a2),
expectDeps(a2)
);
}
/**
* Both a2 and a3 are visible, a2 wants a1, a3 wants nothing.
*/
@Test
public void twoVisibleAspects() throws Exception {
Aspect a1 = createAspect("a1");
Aspect a2 = createAspect("a2", "a1");
Aspect a3 = createAspect("a3");
AspectCollection collection = AspectCollection
.create(
ImmutableList.of(a1, a2, a3),
ImmutableSet.of(a2.getDescriptor(), a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a1, a2, a3),
ImmutableList.of(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");
try {
AspectCollection
.create(
ImmutableList.of(a2, a1, a2, a3),
ImmutableSet.of(a3.getDescriptor()));
fail();
} catch (AspectCycleOnPathException e) {
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");
try {
AspectCollection
.create(
ImmutableList.of(a2, a1, a2, a3),
ImmutableSet.of(a3.getDescriptor()));
fail();
} catch (AspectCycleOnPathException e) {
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),
ImmutableSet.of(a3.getDescriptor())
);
validateAspectCollection(
collection,
ImmutableList.of(a2, a1, a3),
ImmutableList.of(a3),
expectDeps(a3, a2, a1),
expectDeps(a2),
expectDeps(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");
try {
AspectCollection
.create(
ImmutableList.of(a2, a1, a2, a3),
ImmutableSet.of(a3.getDescriptor()));
fail();
} catch (AspectCycleOnPathException e) {
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");
try {
AspectCollection
.create(
ImmutableList.of(a2, a1, a2, a3),
ImmutableSet.of(a3.getDescriptor()));
fail();
} catch (AspectCycleOnPathException e) {
assertThat(e.getAspect()).isEqualTo(a2.getDescriptor());
assertThat(e.getPreviousAspect()).isEqualTo(a1.getDescriptor());
}
}
/**
* a2 and a3 are visible.
* 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 duplicateAspectVisible() 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),
ImmutableSet.of(a2.getDescriptor(), a3.getDescriptor()));
validateAspectCollection(
collection,
ImmutableList.of(a2, a3),
ImmutableList.of(a2, a3),
expectDeps(a3, a2),
expectDeps(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> allAspects,
ImmutableList<Aspect> visibleAspects,
Pair<Aspect, ImmutableList<Aspect>>... expectedPaths) {
assertThat(collection.getAllAspects())
.containsExactlyElementsIn(Iterables.transform(allAspects, ASPECT_TO_DESCRIPTOR))
.inOrder();
assertThat(Iterables.transform(collection.getVisibleAspects(), ASPECT_PATH_TO_DESCRIPTOR))
.containsExactlyElementsIn(Iterables.transform(visibleAspects, ASPECT_TO_DESCRIPTOR))
.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.getVisibleAspects()) {
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.getDependentAspects(), ASPECT_PATH_TO_DESCRIPTOR))
.containsExactlyElementsIn(Iterables.transform(expected.second, ASPECT_TO_DESCRIPTOR))
.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()))
.isSameAs(aspectDeps);
return;
}
allDeps.put(aspectDeps.getAspect(), aspectDeps);
for (AspectDeps path : aspectDeps.getDependentAspects()) {
collectAndValidateAspectDeps(path, allDeps);
}
}
/**
* Creates an aspect wiht 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<SkylarkProviderIdentifier>> requiredProvidersBuilder =
ImmutableList.builder();
for (String requiredAspect : requiredAspects) {
requiredProvidersBuilder.add(
ImmutableSet.of((SkylarkProviderIdentifier.forLegacy(requiredAspect))));
}
final ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> 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(SkylarkProviderIdentifier.forLegacy(className)))
.build();
}
}
);
}
}