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