| // Copyright 2015 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.util; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.Action; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.Actions; |
| import java.util.ArrayList; |
| import java.util.BitSet; |
| import java.util.EnumSet; |
| import java.util.List; |
| import javax.annotation.CheckReturnValue; |
| |
| /** |
| * Test helper for testing {@link Action} implementations. |
| */ |
| public class ActionTester { |
| |
| /** A generator for action instances. */ |
| public interface ActionCombinationFactory<E extends Enum<E>> { |
| |
| /** |
| * Returns a new action instance. The parameter {@code attributesToFlip} is used to vary the |
| * parameters used to create the action. Implementations should do something like this: <code> |
| * <pre> |
| * private enum KeyAttributes { ATTR_1, ATTR_2, ATTR_3, ATTR_4 } |
| * return new MyAction(owner, inputs, outputs, configuration, |
| * attributesToFlip.contains(ATTR_0) ? a1 : a2, |
| * attributesToFlip.contains(ATTR_1) ? b1 : b2, |
| * attributesToFlip.contains(ATTR_2) ? c1 : c2, |
| * attributesToFlip.contains(ATTR_3) ? d1 : d2); |
| * </pre> |
| * </code> |
| * |
| * <p>To reduce the combinatorial complexity of testing an action class, all elements that are |
| * only used to change the executed command line should go into a single parameter, and the key |
| * computation should take the generated command line into account. |
| * |
| * <p>Furthermore, when called with identical parameters, this method should return different |
| * instances (i.e. according to {@code ==}), but they should have the same key. |
| * |
| * @param attributesToFlip |
| */ |
| Action generate(ImmutableSet<E> attributesToFlip) throws Exception; |
| } |
| |
| private final ActionKeyContext actionKeyContext; |
| private final List<Action> actions = new ArrayList<>(); |
| |
| public ActionTester(ActionKeyContext actionKeyContext) { |
| this.actionKeyContext = actionKeyContext; |
| } |
| |
| public ActionTester() { |
| this(new ActionKeyContext()); |
| } |
| |
| /** |
| * Creates all possible combinations of actions given a set of flags which can be either on or |
| * off. This requires that all combinations result in different actions, i.e., all flags must be |
| * orthogonal. The generated actions are added to a local list for a subsequent call to {@link |
| * #runTest}. This method can be called multiple times to generate different sets of actions. |
| */ |
| @CheckReturnValue |
| public <E extends Enum<E>> ActionTester combinations( |
| Class<E> attributeClass, ActionCombinationFactory<E> factory) throws Exception { |
| int attributesCount = attributeClass.getEnumConstants().length; |
| Preconditions.checkArgument( |
| attributesCount <= 30, |
| "Maximum attribute count is 30, more will overflow the max array size."); |
| Preconditions.checkArgument(attributesCount > 0, "Minimum attribute count is 1"); |
| int count = (int) Math.pow(2, attributesCount); |
| Action firstAction = null; |
| for (int i = 0; i < count; i++) { |
| Action action = factory.generate(makeEnumSetInitializedTo(attributeClass, i)); |
| actions.add(action); |
| // Check that creating the same action twice results in equal actions. |
| assertThat( |
| Actions.canBeShared( |
| actionKeyContext, |
| action, |
| factory.generate(makeEnumSetInitializedTo(attributeClass, i)))) |
| .isTrue(); |
| if (i == 0) { |
| firstAction = action; |
| } |
| } |
| // Sanity check that the count is correct. |
| assertThat( |
| Actions.canBeShared( |
| actionKeyContext, |
| firstAction, |
| factory.generate(makeEnumSetInitializedTo(attributeClass, count)))) |
| .isTrue(); |
| return this; |
| } |
| |
| /** Checks that all actions are different. */ |
| public void runTest() { |
| assertThat(actions).isNotEmpty(); |
| for (int i = 0; i < actions.size(); i++) { |
| for (int j = i + 1; j < actions.size(); j++) { |
| assertWithMessage(i + " and " + j) |
| .that(Actions.canBeShared(actionKeyContext, actions.get(i), actions.get(j))) |
| .isFalse(); |
| } |
| } |
| } |
| |
| /** |
| * Tests that different actions have different keys. The attributeCount should specify how many |
| * different permutations the {@link ActionCombinationFactory} should generate. |
| */ |
| public static <E extends Enum<E>> void runTest( |
| Class<E> attributeClass, |
| ActionCombinationFactory<E> factory, |
| ActionKeyContext actionKeyContext) |
| throws Exception { |
| new ActionTester(actionKeyContext).combinations(attributeClass, factory).runTest(); |
| } |
| |
| private static <E extends Enum<E>> ImmutableSet<E> makeEnumSetInitializedTo( |
| Class<E> attributeClass, int seed) { |
| EnumSet<E> result = EnumSet.<E>noneOf(attributeClass); |
| BitSet b = BitSet.valueOf(new long[] {seed}); |
| E[] attributes = attributeClass.getEnumConstants(); |
| for (int i = 0; i < attributes.length; i++) { |
| if (b.get(i)) { |
| result.add(attributes[i]); |
| } |
| } |
| return Sets.immutableEnumSet(result); |
| } |
| } |