diff options
author | 2018-06-14 14:03:23 -0700 | |
---|---|---|
committer | 2018-06-14 14:04:52 -0700 | |
commit | 0412a9f92d00eda3b9a1df7da6c2ba059767c108 (patch) | |
tree | 910a1dc7485894f699ca0d5d1c0c0f99606cbb7b /src/test/java | |
parent | 2b015c53c89815472923d8ea0c94640b7db2fa20 (diff) |
Enable automatic trimming of test configuration when entering non-test rules.
Note that this is not sufficient to see caching between builds on its own;
currently when any flag changes, the analysis cache is reset. A follow-up
will turn off this behavior when only test flags change while
trim_test_configuration is on.
config_settings which examine test options are treated the same as test rules;
that is, they can only be successfully analyzed at the top level or when
connected to the top level by an unbroken chain of test rules.
RELNOTES: None.
PiperOrigin-RevId: 200614584
Diffstat (limited to 'src/test/java')
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/analysis/test/TestTrimmingTransitionTest.java | 587 |
1 files changed, 587 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/test/TestTrimmingTransitionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/test/TestTrimmingTransitionTest.java new file mode 100644 index 0000000000..74d8cdba85 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/test/TestTrimmingTransitionTest.java @@ -0,0 +1,587 @@ +// Copyright 2018 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.test; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableMultiset.toImmutableMultiset; +import static com.google.common.truth.Truth.assertThat; +import static com.google.devtools.build.lib.packages.Attribute.attr; +import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultiset; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException; +import com.google.devtools.build.lib.analysis.BaseRuleClasses; +import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; +import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.RuleDefinition; +import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.RunfilesProvider; +import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.ViewCreationFailedException; +import com.google.devtools.build.lib.analysis.actions.FileWriteAction; +import com.google.devtools.build.lib.analysis.config.HostTransition; +import com.google.devtools.build.lib.analysis.util.AnalysisTestCase; +import com.google.devtools.build.lib.analysis.util.MockRule; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey; +import com.google.devtools.build.skyframe.SkyKey; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for the trimming of the test configuration fragment. */ +@RunWith(JUnit4.class) +public final class TestTrimmingTransitionTest extends AnalysisTestCase { + + /** Simple native test rule. */ + public static final class NativeTest implements RuleConfiguredTargetFactory { + @Override + public ConfiguredTarget create(RuleContext context) throws ActionConflictException { + Artifact executable = context.getBinArtifact(context.getLabel().getName()); + context.registerAction(FileWriteAction.create(context, executable, "#!/bin/true", true)); + Runfiles runfiles = + new Runfiles.Builder(context.getWorkspaceName()).addArtifact(executable).build(); + return new RuleConfiguredTargetBuilder(context) + .setFilesToBuild(NestedSetBuilder.create(Order.STABLE_ORDER, executable)) + .add(RunfilesProvider.class, RunfilesProvider.simple(runfiles)) + .setRunfilesSupport( + RunfilesSupport.withExecutable(context, runfiles, executable), executable) + .build(); + } + } + + private static final RuleDefinition NATIVE_TEST_RULE = + (MockRule) + () -> + MockRule.ancestor(BaseRuleClasses.TestBaseRule.class, BaseRuleClasses.BaseRule.class) + .factory(NativeTest.class) + .type(RuleClassType.TEST) + .define( + "native_test", + attr("deps", LABEL_LIST).allowedFileTypes(), + attr("host_deps", LABEL_LIST) + .cfg(HostTransition.INSTANCE) + .allowedFileTypes()); + + private static final RuleDefinition NATIVE_LIB_RULE = + (MockRule) + () -> + MockRule.ancestor(BaseRuleClasses.BaseRule.class) + .define( + "native_lib", + attr("deps", LABEL_LIST).allowedFileTypes(), + attr("host_deps", LABEL_LIST) + .cfg(HostTransition.INSTANCE) + .allowedFileTypes()); + + @Before + public void setUp() throws Exception { + setRulesAvailableInTests(NATIVE_TEST_RULE, NATIVE_LIB_RULE); + scratch.file( + "test/test.bzl", + "def _skylark_test_impl(ctx):", + " executable = ctx.actions.declare_file(ctx.label.name)", + " ctx.actions.write(executable, '#!/bin/true', is_executable=True)", + " return DefaultInfo(", + " executable=executable,", + " )", + "skylark_test = rule(", + " implementation = _skylark_test_impl,", + " test = True,", + " executable = True,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'host_deps': attr.label_list(cfg='host'),", + " },", + ")"); + scratch.file( + "test/lib.bzl", + "def _skylark_lib_impl(ctx):", + " pass", + "skylark_lib = rule(", + " implementation = _skylark_lib_impl,", + " attrs = {", + " 'deps': attr.label_list(),", + " 'host_deps': attr.label_list(cfg='host'),", + " },", + ")"); + } + + private void assertNumberOfConfigurationsOfTargets( + Set<SkyKey> keys, Map<String, Integer> targetsWithCounts) { + ImmutableMultiset<Label> actualSet = + keys.stream() + .filter(key -> key instanceof ConfiguredTargetKey) + .map(key -> ((ConfiguredTargetKey) key).getLabel()) + .collect(toImmutableMultiset()); + ImmutableMap<Label, Integer> expected = + targetsWithCounts + .entrySet() + .stream() + .collect( + toImmutableMap( + entry -> Label.parseAbsoluteUnchecked(entry.getKey()), + entry -> entry.getValue())); + ImmutableMap<Label, Integer> actual = + expected + .keySet() + .stream() + .collect(toImmutableMap(label -> label, label -> actualSet.count(label))); + assertThat(actual).containsExactlyEntriesIn(expected); + } + + @Test + public void flagOffDifferentTestOptions_ResultsInDifferentCTs() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "test_suite(", + " name = 'suite',", + " tests = [':native_test', ':skylark_test'],", + ")", + "native_test(", + " name = 'native_test',", + " deps = [':native_dep', ':skylark_dep'],", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = [':native_dep', ':skylark_dep'],", + ")", + "native_lib(", + " name = 'native_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "native_lib(", + " name = 'native_shared_dep',", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--notrim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + update( + "//test:suite", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + LinkedHashSet<SkyKey> visitedTargets = new LinkedHashSet<>(getSkyframeEvaluatedTargetKeys()); + // asserting that the top-level targets are the same as the ones in the diamond starting at + // //test:suite + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + .put("//test:suite", 1) + .put("//test:native_test", 1) + .put("//test:skylark_test", 1) + .put("//test:native_dep", 1) + .put("//test:skylark_dep", 1) + .put("//test:native_shared_dep", 1) + .put("//test:skylark_shared_dep", 1) + .build()); + + useConfiguration("--notrim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeB"); + update( + "//test:suite", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + visitedTargets.addAll(getSkyframeEvaluatedTargetKeys()); + // asserting that we got no overlap between the two runs, we had to build different versions of + // all seven targets + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + .put("//test:suite", 2) + .put("//test:native_test", 2) + .put("//test:skylark_test", 2) + .put("//test:native_dep", 2) + .put("//test:skylark_dep", 2) + .put("//test:native_shared_dep", 2) + .put("//test:skylark_shared_dep", 2) + .build()); + } + + @Test + public void flagOnDifferentTestOptions_SharesCTsForNonTestRules() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "test_suite(", + " name = 'suite',", + " tests = [':native_test', ':skylark_test'],", + ")", + "native_test(", + " name = 'native_test',", + " deps = [':native_dep', ':skylark_dep'],", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = [':native_dep', ':skylark_dep'],", + ")", + "native_lib(", + " name = 'native_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "native_lib(", + " name = 'native_shared_dep',", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--trim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + update( + "//test:suite", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + LinkedHashSet<SkyKey> visitedTargets = new LinkedHashSet<>(getSkyframeEvaluatedTargetKeys()); + // asserting that the top-level targets are the same as the ones in the diamond starting at + // //test:suite + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + .put("//test:suite", 1) + .put("//test:native_test", 1) + .put("//test:skylark_test", 1) + .put("//test:native_dep", 1) + .put("//test:skylark_dep", 1) + .put("//test:native_shared_dep", 1) + .put("//test:skylark_shared_dep", 1) + .build()); + + useConfiguration("--trim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeB"); + update( + "//test:suite", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + visitedTargets.addAll(getSkyframeEvaluatedTargetKeys()); + // asserting that our non-test rules matched between the two runs, we had to build different + // versions of the three test targets but not the four non-test targets + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + .put("//test:suite", 2) + .put("//test:native_test", 2) + .put("//test:skylark_test", 2) + .put("//test:native_dep", 1) + .put("//test:skylark_dep", 1) + .put("//test:native_shared_dep", 1) + .put("//test:skylark_shared_dep", 1) + .build()); + } + + @Test + public void flagOnDynamicConfigsNotrimHostDeps_AreNotAnalyzedAnyExtraTimes() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "native_test(", + " name = 'native_outer_test',", + " deps = [':native_test', ':skylark_test'],", + " host_deps = [':native_test', ':skylark_test'],", + ")", + "skylark_test(", + " name = 'skylark_outer_test',", + " deps = [':native_test', ':skylark_test'],", + " host_deps = [':native_test', ':skylark_test'],", + ")", + "native_test(", + " name = 'native_test',", + " deps = [':native_dep', ':skylark_dep'],", + " host_deps = [':native_dep', ':skylark_dep'],", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = [':native_dep', ':skylark_dep'],", + " host_deps = [':native_dep', ':skylark_dep'],", + ")", + "native_lib(", + " name = 'native_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + " host_deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + " host_deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "native_lib(", + " name = 'native_shared_dep',", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--trim_test_configuration", "--experimental_dynamic_configs=notrim"); + update( + "//test:native_outer_test", + "//test:skylark_outer_test", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + LinkedHashSet<SkyKey> visitedTargets = new LinkedHashSet<>(getSkyframeEvaluatedTargetKeys()); + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + // each target should be analyzed in two and only two configurations: target and host + // there should not be a "host trimmed" and "host untrimmed" version + .put("//test:native_test", 2) + .put("//test:skylark_test", 2) + .put("//test:native_dep", 2) + .put("//test:skylark_dep", 2) + .put("//test:native_shared_dep", 2) + .put("//test:skylark_shared_dep", 2) + .build()); + } + + @Test + public void flagOnDynamicConfigsTrimHostDeps_AreNotAnalyzedAnyExtraTimes() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "native_test(", + " name = 'native_outer_test',", + " deps = [':native_test', ':skylark_test'],", + " host_deps = [':native_test', ':skylark_test'],", + ")", + "skylark_test(", + " name = 'skylark_outer_test',", + " deps = [':native_test', ':skylark_test'],", + " host_deps = [':native_test', ':skylark_test'],", + ")", + "native_test(", + " name = 'native_test',", + " deps = [':native_dep', ':skylark_dep'],", + " host_deps = [':native_dep', ':skylark_dep'],", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = [':native_dep', ':skylark_dep'],", + " host_deps = [':native_dep', ':skylark_dep'],", + ")", + "native_lib(", + " name = 'native_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + " host_deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':native_shared_dep', 'skylark_shared_dep'],", + " host_deps = [':native_shared_dep', 'skylark_shared_dep'],", + ")", + "native_lib(", + " name = 'native_shared_dep',", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--trim_test_configuration", "--experimental_dynamic_configs=on"); + update( + "//test:native_outer_test", + "//test:skylark_outer_test", + "//test:native_test", + "//test:skylark_test", + "//test:native_dep", + "//test:skylark_dep", + "//test:native_shared_dep", + "//test:skylark_shared_dep"); + LinkedHashSet<SkyKey> visitedTargets = new LinkedHashSet<>(getSkyframeEvaluatedTargetKeys()); + assertNumberOfConfigurationsOfTargets( + visitedTargets, + new ImmutableMap.Builder<String, Integer>() + // each target should be analyzed in two and only two configurations: target and host + // there should not be a "host trimmed" and "host untrimmed" version + .put("//test:native_test", 2) + .put("//test:skylark_test", 2) + .put("//test:native_dep", 2) + .put("//test:skylark_dep", 2) + .put("//test:native_shared_dep", 2) + .put("//test:skylark_shared_dep", 2) + .build()); + } + + @Test + public void flagOffConfigSetting_CanInspectTestOptions() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "config_setting(", + " name = 'test_mode',", + " values = {'test_arg': 'TypeA'},", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = select({':test_mode': [':skylark_shared_dep'], '//conditions:default': []})", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = select({':test_mode': [':skylark_shared_dep'], '//conditions:default': []})", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--notrim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + update("//test:test_mode", "//test:skylark_test", "//test:skylark_dep"); + // All 3 targets (top level, under a test, under a non-test) should successfully analyze. + assertThat(getAnalysisResult().getTargetsToBuild()).hasSize(3); + } + + @Test + public void flagOnConfigSetting_FailsTryingToInspectTestOptions() throws Exception { + reporter.removeHandler(failFastHandler); + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "config_setting(", + " name = 'test_mode',", + " values = {'test_arg': 'TypeA'},", + ")", + "skylark_test(", + " name = 'skylark_test',", + " deps = select({':test_mode': [':skylark_shared_dep'], '//conditions:default': []})", + ")", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = select({':test_mode': [':skylark_shared_dep'], '//conditions:default': []})", + ")", + "skylark_lib(", + " name = 'skylark_shared_dep',", + ")"); + useConfiguration("--trim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + try { + // When reached through a non-test target analysis should fail + update("//test:skylark_dep"); + fail(); + } catch (ViewCreationFailedException expected) { + } + assertContainsEvent("unknown option: 'test_arg'"); + + update("//test:test_mode", "//test:skylark_test"); + // When reached through only test targets (top level, under a test) analysis should succeed + assertThat(getAnalysisResult().getTargetsToBuild()).hasSize(2); + } + + @Test + public void flagOffNonTestTargetWithTestDependencies_IsPermitted() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':skylark_test'],", + " testonly = 1,", + ")", + "skylark_test(", + " name = 'skylark_test',", + ")"); + useConfiguration("--notrim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + update("//test:skylark_dep"); + assertThat(getAnalysisResult().getTargetsToBuild()).isNotEmpty(); + } + + @Test + public void flagOnNonTestTargetWithTestDependencies_FailsAnalysis() throws Exception { + reporter.removeHandler(failFastHandler); + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "skylark_lib(", + " name = 'skylark_dep',", + " deps = [':skylark_test'],", + " testonly = 1,", + ")", + "skylark_test(", + " name = 'skylark_test',", + ")"); + useConfiguration("--trim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + try { + update("//test:skylark_dep"); + fail(); + } catch (ViewCreationFailedException expected) { + } + assertContainsEvent( + "all rules of type skylark_test require the presence of all of " + + "[TestConfiguration], but these were all disabled"); + } + + @Test + public void flagOnTestSuiteWithTestDependencies_CanBeAnalyzed() throws Exception { + scratch.file( + "test/BUILD", + "load(':test.bzl', 'skylark_test')", + "load(':lib.bzl', 'skylark_lib')", + "test_suite(", + " name = 'suite',", + " tests = [':skylark_test', ':suite_2'],", + ")", + "test_suite(", + " name = 'suite_2',", + " tests = [':skylark_test_2', ':skylark_test_3'],", + ")", + "skylark_test(", + " name = 'skylark_test',", + ")", + "skylark_test(", + " name = 'skylark_test_2',", + ")", + "skylark_test(", + " name = 'skylark_test_3',", + ")"); + useConfiguration("--trim_test_configuration", "--noexpand_test_suites", "--test_arg=TypeA"); + update("//test:suite", "//test:suite_2"); + assertThat(getAnalysisResult().getTargetsToBuild()).hasSize(2); + } +} |