diff options
author | Dmitry Lomov <dslomov@google.com> | 2015-11-23 13:19:02 +0000 |
---|---|---|
committer | Philipp Wollermann <philwo@google.com> | 2015-11-24 14:40:45 +0000 |
commit | 1b2e3e3152b0f2311606db25fbefc5ce78b9db83 (patch) | |
tree | e37ed55eb59f1312d782eba7a77732c372c8cafc /src | |
parent | 2419e6469b6c1f1e4ebbc0f05e1b5f33292f692d (diff) |
Open-source AnalysisCachingTest.
--
MOS_MIGRATED_REVID=108496188
Diffstat (limited to 'src')
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java | 455 | ||||
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisCachingTestBase.java | 41 |
2 files changed, 496 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java new file mode 100644 index 0000000000..124e09c441 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/AnalysisCachingTest.java @@ -0,0 +1,455 @@ +// 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; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.analysis.util.AnalysisCachingTestBase; +import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider; +import com.google.devtools.build.lib.testutil.Suite; +import com.google.devtools.build.lib.testutil.TestSpec; + +import java.util.Set; + +/** + * Analysis caching tests. + */ +@TestSpec(size = Suite.SMALL_TESTS) +public class AnalysisCachingTest extends AnalysisCachingTestBase { + + public void testSimpleCleanAnalysis() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + update("//java/a:A"); + ConfiguredTarget javaTest = getConfiguredTarget("//java/a:A"); + assertNotNull(javaTest); + assertNotNull(javaTest.getProvider(JavaSourceJarsProvider.class)); + } + + public void testTickTock() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])", + "java_test(name = 'B',", + " srcs = ['B.java'])"); + update("//java/a:A"); + update("//java/a:B"); + update("//java/a:A"); + } + + public void testFullyCached() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + update("//java/a:A"); + ConfiguredTarget old = getConfiguredTarget("//java/a:A"); + update("//java/a:A"); + ConfiguredTarget current = getConfiguredTarget("//java/a:A"); + assertSame(old, current); + } + + public void testSubsetCached() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])", + "java_test(name = 'B',", + " srcs = ['B.java'])"); + update("//java/a:A", "//java/a:B"); + ConfiguredTarget old = getConfiguredTarget("//java/a:A"); + update("//java/a:A"); + ConfiguredTarget current = getConfiguredTarget("//java/a:A"); + assertSame(old, current); + } + + public void testDependencyChanged() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'],", + " deps = ['//java/b'])"); + scratch.file("java/b/BUILD", + "java_library(name = 'b',", + " srcs = ['B.java'])"); + update("//java/a:A"); + ConfiguredTarget old = getConfiguredTarget("//java/a:A"); + scratch.overwriteFile("java/b/BUILD", + "java_library(name = 'b',", + " srcs = ['C.java'])"); + update("//java/a:A"); + ConfiguredTarget current = getConfiguredTarget("//java/a:A"); + assertNotSame(old, current); + } + + public void testTopLevelChanged() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'],", + " deps = ['//java/b'])"); + scratch.file("java/b/BUILD", + "java_library(name = 'b',", + " srcs = ['B.java'])"); + update("//java/a:A"); + ConfiguredTarget old = getConfiguredTarget("//java/a:A"); + scratch.overwriteFile("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + update("//java/a:A"); + ConfiguredTarget current = getConfiguredTarget("//java/a:A"); + assertNotSame(old, current); + } + + // Regression test for: + // "action conflict detection is incorrect if conflict is in non-top-level configured targets". + public void testActionConflictInDependencyImpliesTopLevelTargetFailure() throws Exception { + useConfiguration("--force_pic"); + scratch.file("conflict/BUILD", + "cc_library(name='x', srcs=['foo.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])", + "cc_binary(name='foo', deps=['x'], data=['_objs/x/conflict/foo.pic.o'])"); + reporter.removeHandler(failFastHandler); // expect errors + update(defaultFlags().with(Flag.KEEP_GOING), "//conflict:foo"); + assertContainsEvent("file 'conflict/_objs/x/conflict/foo.pic.o' " + CONFLICT_MSG); + assertThat(getAnalysisResult().getTargetsToBuild()).isEmpty(); + } + + /** + * Generating the same output from two targets is ok if we build them on successive builds + * and invalidate the first target before we build the second target. This is a strictly weaker + * test than if we didn't invalidate the first target, but since Skyframe can't pass then, this + * test could be useful for it. Actually, since Skyframe makes multiple update calls, it manages + * to unregister actions even when it shouldn't, and so this test can incorrectly pass. However, + * {@code SkyframeExecutorTest#testNoActionConflictWithInvalidatedTarget} tests it more + * rigorously. + */ + public void testNoActionConflictWithInvalidatedTarget() throws Exception { + useConfiguration("--force_pic"); + scratch.file("conflict/BUILD", + "cc_library(name='x', srcs=['foo.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + update("//conflict:x"); + ConfiguredTarget conflict = getConfiguredTarget("//conflict:x"); + Action oldAction = getGeneratingAction(getBinArtifact("_objs/x/conflict/foo.pic.o", conflict)); + assertEquals("//conflict:x", oldAction.getOwner().getLabel().toString()); + scratch.overwriteFile("conflict/BUILD", + "cc_library(name='newx', srcs=['foo.cc'])", // Rename target. + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + update(defaultFlags(), "//conflict:_objs/x/conflict/foo.pic.o"); + ConfiguredTarget objsConflict = getConfiguredTarget("//conflict:_objs/x/conflict/foo.pic.o"); + Action newAction = + getGeneratingAction(getBinArtifact("_objs/x/conflict/foo.pic.o", objsConflict)); + assertEquals("//conflict:_objs/x/conflict/foo.pic.o", + newAction.getOwner().getLabel().toString()); + } + + /** + * Generating the same output from multiple actions is causing an error. + */ + public void testActionConflictCausesError() throws Exception { + useConfiguration("--force_pic"); + scratch.file("conflict/BUILD", + "cc_library(name='x', srcs=['foo.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + reporter.removeHandler(failFastHandler); // expect errors + update(defaultFlags().with(Flag.KEEP_GOING), + "//conflict:x", "//conflict:_objs/x/conflict/foo.pic.o"); + assertContainsEvent("file 'conflict/_objs/x/conflict/foo.pic.o' " + CONFLICT_MSG); + } + + public void testNoActionConflictErrorAfterClearedAnalysis() throws Exception { + useConfiguration("--force_pic"); + scratch.file("conflict/BUILD", + "cc_library(name='x', srcs=['foo.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + reporter.removeHandler(failFastHandler); // expect errors + update(defaultFlags().with(Flag.KEEP_GOING), + "//conflict:x", "//conflict:_objs/x/conflict/foo.pic.o"); + // We want to force a "dropConfiguredTargetsNow" operation, which won't inform the + // invalidation receiver about the dropped configured targets. + getView().clearAnalysisCache(ImmutableList.<ConfiguredTarget>of()); + assertContainsEvent("file 'conflict/_objs/x/conflict/foo.pic.o' " + CONFLICT_MSG); + eventCollector.clear(); + scratch.overwriteFile("conflict/BUILD", + "cc_library(name='x', srcs=['baz.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + update(defaultFlags().with(Flag.KEEP_GOING), + "//conflict:x", "//conflict:_objs/x/conflict/foo.pic.o"); + assertNoEvents(); + } + + /** + * The current action conflict detection code will only mark one of the targets as having an + * error, and with multi-threaded analysis it is not deterministic which one that will be. + */ + public void testActionConflictMarksTargetInvalid() throws Exception { + useConfiguration("--force_pic"); + scratch.file("conflict/BUILD", + "cc_library(name='x', srcs=['foo.cc'])", + "cc_binary(name='_objs/x/conflict/foo.pic.o', srcs=['bar.cc'])"); + reporter.removeHandler(failFastHandler); // expect errors + update(defaultFlags().with(Flag.KEEP_GOING), + "//conflict:x", "//conflict:_objs/x/conflict/foo.pic.o"); + ConfiguredTarget a = getConfiguredTarget("//conflict:x"); + ConfiguredTarget b = getConfiguredTarget("//conflict:_objs/x/conflict/foo.pic.o"); + assertTrue(hasTopLevelAnalysisError(a) ^ hasTopLevelAnalysisError(b)); + } + + /** + * BUILD file involved in BUILD-file cycle is changed + */ + public void testBuildFileInCycleChanged() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'],", + " deps = ['//java/b'])"); + scratch.file("java/b/BUILD", + "java_library(name = 'b',", + " srcs = ['B.java'],", + " deps = ['//java/c'])"); + scratch.file("java/c/BUILD", + "java_library(name = 'c',", + " srcs = ['C.java'],", + " deps = ['//java/b'])"); + // expect error + reporter.removeHandler(failFastHandler); + update(defaultFlags().with(Flag.KEEP_GOING), "//java/a:A"); + ConfiguredTarget old = getConfiguredTarget("//java/a:A"); + // drop dependency on from b to c + scratch.overwriteFile("java/b/BUILD", + "java_library(name = 'b',", + " srcs = ['B.java'])"); + eventCollector.clear(); + reporter.addHandler(failFastHandler); + update("//java/a:A"); + ConfiguredTarget current = getConfiguredTarget("//java/a:A"); + assertNotSame(old, current); + } + + private void assertNoTargetsVisited() { + Set<?> analyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertEquals(analyzedTargets.toString(), 0, analyzedTargets.size()); + } + + public void testSecondRunAllCacheHits() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + update("//java/a:A"); + update("//java/a:A"); + assertNoTargetsVisited(); + } + + public void testDependencyAllCacheHits() throws Exception { + scratch.file("java/a/BUILD", + "java_library(name = 'x', srcs = ['A.java'], deps = ['y'])", + "java_library(name = 'y', srcs = ['B.java'])"); + update("//java/a:x"); + Set<?> oldAnalyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertTrue(oldAnalyzedTargets.size() >= 2); // could be greater due to implicit deps + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:x")); + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:y")); + update("//java/a:y"); + assertNoTargetsVisited(); + } + + public void testSupersetNotAllCacheHits() throws Exception { + scratch.file("java/a/BUILD", + // It's important that all targets are of the same rule class, otherwise the second update + // call might analyze more than one extra target because of potential implicit dependencies. + "java_library(name = 'x', srcs = ['A.java'], deps = ['y'])", + "java_library(name = 'y', srcs = ['B.java'], deps = ['z'])", + "java_library(name = 'z', srcs = ['C.java'])"); + update("//java/a:y"); + Set<?> oldAnalyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertTrue(oldAnalyzedTargets.size() >= 3); // could be greater due to implicit deps + assertEquals(0, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:x")); + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:y")); + update("//java/a:x"); + Set<?> newAnalyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertTrue(newAnalyzedTargets.size() >= 1); // could be greater due to implicit deps + assertEquals(1, countObjectsPartiallyMatchingRegex(newAnalyzedTargets, "//java/a:x")); + assertEquals(0, countObjectsPartiallyMatchingRegex(newAnalyzedTargets, "//java/a:y")); + } + + public void testExtraActions() throws Exception { + scratch.file("java/com/google/a/BUILD", "java_library(name='a', srcs=['A.java'])"); + scratch.file("java/com/google/b/BUILD", "java_library(name='b', srcs=['B.java'])"); + scratch.file("extra/BUILD", + "extra_action(name = 'extra',", + " out_templates = ['$(OWNER_LABEL_DIGEST)_$(ACTION_ID).tst'],", + " cmd = '')", + "action_listener(name = 'listener',", + " mnemonics = ['Javac'],", + " extra_actions = [':extra'])"); + + useConfiguration("--experimental_action_listener=//extra:listener"); + update("//java/com/google/a:a"); + update("//java/com/google/b:b"); + } + + public void testExtraActionsCaching() throws Exception { + scratch.file("java/a/BUILD", "java_library(name='a', srcs=['A.java'])"); + scratch.file("extra/BUILD", + "extra_action(name = 'extra',", + " out_templates = ['$(OWNER_LABEL_DIGEST)_$(ACTION_ID).tst'],", + " cmd = 'echo $(EXTRA_ACTION_FILE)')", + "action_listener(name = 'listener',", + " mnemonics = ['Javac'],", + " extra_actions = [':extra'])"); + useConfiguration("--experimental_action_listener=//extra:listener"); + + update("//java/a:a"); + getConfiguredTarget("//java/a:a"); + + scratch.overwriteFile("extra/BUILD", + "extra_action(name = 'extra',", + " out_templates = ['$(OWNER_LABEL_DIGEST)_$(ACTION_ID).tst'],", + " cmd = 'echo $(BUG)')", // <-- change here + "action_listener(name = 'listener',", + " mnemonics = ['Javac'],", + " extra_actions = [':extra'])"); + reporter.removeHandler(failFastHandler); + try { + update("//java/a:a"); + fail(); + } catch (ViewCreationFailedException e) { + assertThat(e.getMessage()).contains("Analysis of target '//java/a:a' failed"); + assertContainsEvent("Unable to expand make variables: $(BUG)"); + } + } + + public void testConfigurationCachingWithWarningReplay() throws Exception { + useConfiguration("--test_sharding_strategy=experimental_heuristic"); + update(); + assertContainsEvent("Heuristic sharding is intended as a one-off experimentation tool"); + eventCollector.clear(); + update(); + assertContainsEvent("Heuristic sharding is intended as a one-off experimentation tool"); + } + + public void testWorkspaceStatusCommandIsNotCachedForNullBuild() throws Exception { + update(); + WorkspaceStatusAction actionA = getView().getLastWorkspaceBuildInfoActionForTesting(); + assertEquals("DummyBuildInfoAction", actionA.getMnemonic()); + + workspaceStatusActionFactory.setKey("Second"); + update(); + WorkspaceStatusAction actionB = getView().getLastWorkspaceBuildInfoActionForTesting(); + assertEquals("DummyBuildInfoActionSecond", actionB.getMnemonic()); + } + + public void testSkyframeCacheInvalidationBuildFileChange() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + String aTarget = "//java/a:A"; + update(aTarget); + ConfiguredTarget firstCT = getConfiguredTarget(aTarget); + + scratch.overwriteFile("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['B.java'])"); + + update(aTarget); + ConfiguredTarget updatedCT = getConfiguredTarget(aTarget); + assertNotSame(firstCT, updatedCT); + + update(aTarget); + ConfiguredTarget updated2CT = getConfiguredTarget(aTarget); + assertSame(updatedCT, updated2CT); + } + + public void testSkyframeDifferentPackagesInvalidation() throws Exception { + scratch.file("java/a/BUILD", + "java_test(name = 'A',", + " srcs = ['A.java'])"); + + scratch.file("java/b/BUILD", + "java_test(name = 'B',", + " srcs = ['B.java'])"); + + String aTarget = "//java/a:A"; + update(aTarget); + ConfiguredTarget oldAConfTarget = getConfiguredTarget(aTarget); + String bTarget = "//java/b:B"; + update(bTarget); + ConfiguredTarget oldBConfTarget = getConfiguredTarget(bTarget); + + scratch.overwriteFile("java/b/BUILD", + "java_test(name = 'B',", + " srcs = ['C.java'])"); + + update(aTarget); + // Check that 'A' was not invalidated because 'B' was modified and invalidated. + ConfiguredTarget newAConfTarget = getConfiguredTarget(aTarget); + ConfiguredTarget newBConfTarget = getConfiguredTarget(bTarget); + + assertSame(oldAConfTarget, newAConfTarget); + assertNotSame(oldBConfTarget, newBConfTarget); + } + + private int countObjectsPartiallyMatchingRegex(Iterable<? extends Object> elements, + String toStringMatching) { + toStringMatching = ".*" + toStringMatching + ".*"; + int result = 0; + for (Object o : elements) { + if (o.toString().matches(toStringMatching)) { + ++result; + } + } + return result; + } + + public void testGetSkyframeEvaluatedTargetKeysOmitsCachedTargets() throws Exception { + scratch.file("java/a/BUILD", + "java_library(name = 'x', srcs = ['A.java'], deps = ['z', 'w'])", + "java_library(name = 'y', srcs = ['B.java'], deps = ['z', 'w'])", + "java_library(name = 'z', srcs = ['C.java'])", + "java_library(name = 'w', srcs = ['D.java'])"); + + update("//java/a:x"); + Set<?> oldAnalyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertTrue(oldAnalyzedTargets.size() >= 2); // could be greater due to implicit deps + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:x")); + assertEquals(0, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:y")); + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:z")); + assertEquals(1, countObjectsPartiallyMatchingRegex(oldAnalyzedTargets, "//java/a:w")); + + // Unless the build is not fully cached, we get notified about newly evaluated targets, as well + // as cached top-level targets. For the two tests above to work correctly, we need to ensure + // that getSkyframeEvaluatedTargetKeys() doesn't return these. + update("//java/a:x", "//java/a:y", "//java/a:z"); + Set<?> newAnalyzedTargets = getSkyframeEvaluatedTargetKeys(); + assertThat(newAnalyzedTargets).hasSize(2); + assertEquals(1, countObjectsPartiallyMatchingRegex(newAnalyzedTargets, "//java/a:B.java")); + assertEquals(1, countObjectsPartiallyMatchingRegex(newAnalyzedTargets, "//java/a:y")); + } + + /** + * {link AnalysisCachingTest} without loading phase. + */ + public static class AnalysisCachingTestWithoutLoading extends AnalysisCachingTest { + @Override + public void setUp() throws Exception { + disableLoading(); + super.setUp(); + } + + // Error processing without loading phase is not working properly yet. + @Override + public void testBuildFileInCycleChanged() {} + } +} diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisCachingTestBase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisCachingTestBase.java new file mode 100644 index 0000000000..6db4202067 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisCachingTestBase.java @@ -0,0 +1,41 @@ +// 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 com.google.devtools.build.lib.analysis.ConfiguredTarget; + +/** + * Base class for analysis caching tests. + */ +public abstract class AnalysisCachingTestBase extends AnalysisTestCase { + + protected static final String CONFLICT_MSG = "is generated by these conflicting actions:"; + + // In skyframe if configuredTarget contains error then null is returned. + protected boolean hasTopLevelAnalysisError(ConfiguredTarget configuredTarget) { + return !getAnalysisResult().getTargetsToBuild().contains(configuredTarget); + } + + protected void assertEventCached(String target, String expectedWarning) throws Exception { + reporter.removeHandler(failFastHandler); + // Run with keep_going, so this method can also be used for errors (which otherwise throw an + // exception). + update(defaultFlags().with(Flag.KEEP_GOING), target); + assertContainsEvent(expectedWarning); + eventCollector.clear(); + update(defaultFlags().with(Flag.KEEP_GOING), target); + assertContainsEvent(expectedWarning); + } +} |