| // Copyright 2021 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.packages; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.util.Pair; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Encapsulates logic for gathering tests for {@code test_suite}'s {@code $implicit_tests} |
| * attribute. |
| * |
| * <p>Usage is tightly coupled with the package loading process. Expected flow is roughly |
| * |
| * <ol> |
| * <li>{@link #getTestSuiteImplicitTestsRefForTags} for all relevant {@code test_suite}s |
| * <li>Then, after all all targets have been added to the package... |
| * <ol> |
| * <li>{@link #clearAccumulatedTests()} |
| * <li>{@link #processRule} for every rule in the package |
| * <li>{@link #sortTests()} |
| * </ol> |
| * <li>Repeat the previous step(s) as necessary, eg due to skyframe restarts from missing deps |
| * </ol> |
| */ |
| class TestSuiteImplicitTestsAccumulator { |
| |
| private final Map<ImmutableSet<String>, ImplicitTestsAccumulator> testSuiteImplicitTests = |
| new HashMap<>(); |
| |
| /** |
| * Returns a reference to the list of tests matching tags (or all tests if empty), to be populated |
| * by {@link #processRule}. |
| */ |
| List<Label> getTestSuiteImplicitTestsRefForTags(List<String> tags) { |
| ImplicitTestsAccumulator accumulatorForTags = |
| testSuiteImplicitTests.computeIfAbsent( |
| ImmutableSet.copyOf(tags), ImplicitTestsAccumulator::new); |
| return Collections.unmodifiableList(accumulatorForTags.tests); |
| } |
| |
| /** Clears all accumulated tests. */ |
| void clearAccumulatedTests() { |
| testSuiteImplicitTests.values().forEach(acc -> acc.tests.clear()); |
| } |
| |
| /** |
| * Processes a rule from the package, adding it to the necessary {@code $implicit_test} values |
| * returned by {@link #getTestSuiteImplicitTestsRefForTags}. |
| */ |
| void processRule(Rule rule) { |
| if (testSuiteImplicitTests.isEmpty()) { |
| // No test suites requiring implicit test accumulation encountered. |
| return; |
| } |
| |
| if (TargetUtils.isTestRule(rule) && !TargetUtils.hasManualTag(rule)) { |
| NonconfigurableAttributeMapper mapper = NonconfigurableAttributeMapper.of(rule); |
| Set<String> testSuiteTags = |
| ImmutableSet.<String>builder() |
| .addAll(mapper.get("tags", Type.STRING_LIST)) |
| .add(mapper.get("size", Type.STRING)) |
| .build(); |
| for (ImplicitTestsAccumulator acc : testSuiteImplicitTests.values()) { |
| if (TestTargetUtils.testMatchesFilters(testSuiteTags, acc.requiredTags, acc.excludedTags)) { |
| acc.tests.add(rule.getLabel()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sorts all of accumulated test lists returned by {@link #getTestSuiteImplicitTestsRefForTags}. |
| */ |
| void sortTests() { |
| testSuiteImplicitTests.values().forEach(acc -> Collections.sort(acc.tests)); |
| } |
| |
| private static class ImplicitTestsAccumulator { |
| private final Collection<String> requiredTags; |
| private final Collection<String> excludedTags; |
| private final List<Label> tests = new ArrayList<>(); |
| |
| private ImplicitTestsAccumulator(Set<String> testTags) { |
| Pair<Collection<String>, Collection<String>> requiredAndExcludedTags = |
| TestTargetUtils.sortTagsBySense(testTags); |
| this.requiredTags = requiredAndExcludedTags.first; |
| this.excludedTags = requiredAndExcludedTags.second; |
| } |
| } |
| } |