| // 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 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<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(); |
| } |
| } |
| ); |
| } |
| |
| |
| } |