Open source 'CircularDependencyTest'.
--
MOS_MIGRATED_REVID=108514346
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java b/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java
new file mode 100644
index 0000000..18481c5
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/analysis/CircularDependencyTest.java
@@ -0,0 +1,175 @@
+// Copyright 2014 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 com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.events.Event;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.packages.NoSuchTargetException;
+import com.google.devtools.build.lib.packages.Package;
+
+/**
+ * Tests that check that dependency cycles are reported correctly.
+ */
+public class CircularDependencyTest extends BuildViewTestCase {
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public void testOneRuleCycle() throws Exception {
+ checkError(
+ "cycle",
+ "foo.g",
+ //error message
+ selfEdgeMsg("//cycle:foo.g"),
+ // Rule
+ "genrule(name = 'foo.g',",
+ " outs = ['Foo.java'],",
+ " srcs = ['foo.g'],",
+ " cmd = 'cat $(SRCS) > $<' )");
+ }
+
+ public void testDirectPackageGroupCycle() throws Exception {
+ checkError(
+ "cycle",
+ "melon",
+ selfEdgeMsg("//cycle:moebius"),
+ "package_group(name='moebius', packages=[], includes=['//cycle:moebius'])",
+ "sh_library(name='melon', visibility=[':moebius'])");
+ }
+
+ public void testThreeLongPackageGroupCycle() throws Exception {
+ String expectedEvent =
+ "cycle in dependency graph:\n"
+ + " //cycle:superman\n"
+ + " * //cycle:rock\n"
+ + " //cycle:paper\n"
+ + " //cycle:scissors";
+ checkError(
+ "cycle",
+ "superman",
+ expectedEvent,
+ "# dummy line",
+ "package_group(name='paper', includes=['//cycle:scissors'])",
+ "package_group(name='rock', includes=['//cycle:paper'])",
+ "package_group(name='scissors', includes=['//cycle:rock'])",
+ "sh_library(name='superman', visibility=[':rock'])");
+
+ Event foundEvent = null;
+ for (Event event : eventCollector) {
+ if (event.getMessage().contains(expectedEvent)) {
+ foundEvent = event;
+ break;
+ }
+ }
+
+ assertNotNull(foundEvent);
+ Location location = foundEvent.getLocation();
+ assertEquals(3, location.getStartLineAndColumn().getLine());
+ assertEquals("/workspace/cycle/BUILD", location.getPath().toString());
+ }
+
+ /**
+ * Test to detect implicit input/output file overlap in rules.
+ */
+ public void testOneRuleImplicitCycleJava() throws Exception {
+ Package pkg =
+ createScratchPackageForImplicitCycle(
+ "cycle", "java_library(name='jcyc',", " srcs = ['libjcyc.jar', 'foo.java'])");
+ try {
+ pkg.getTarget("jcyc");
+ fail();
+ } catch (NoSuchTargetException e) {
+ /* ok */
+ }
+ assertTrue(pkg.containsErrors());
+ assertContainsEvent("rule 'jcyc' has file 'libjcyc.jar' as both an" + " input and an output");
+ }
+
+ /**
+ * Test not to detect implicit input/output file overlap in rules,
+ * when coming from a different package.
+ */
+ public void testInputOutputConflictDifferentPackage() throws Exception {
+ Package pkg =
+ createScratchPackageForImplicitCycle(
+ "googledata/xxx",
+ "genrule(name='geo',",
+ " srcs = ['//googledata/geo:geo_info.txt'],",
+ " outs = ['geoinfo.txt'],",
+ " cmd = '$(SRCS) > $@')");
+ assertFalse(pkg.containsErrors());
+ }
+
+ public void testTwoRuleCycle() throws Exception {
+ scratchRule("b", "rule2", "cc_library(name='rule2',", " deps=['//a:rule1'])");
+
+ checkError(
+ "a",
+ "rule1",
+ "in cc_library rule //a:rule1: cycle in dependency graph:\n"
+ + " * //a:rule1\n"
+ + " //b:rule2",
+ "cc_library(name='rule1',",
+ " deps=['//b:rule2'])");
+ }
+
+ public void testTwoRuleCycle2() throws Exception {
+ reporter.removeHandler(failFastHandler); // expect errors
+ scratch.file(
+ "x/BUILD", "java_library(name='x', deps=['y'])", "java_library(name='y', deps=['x'])");
+ getConfiguredTarget("//x");
+ assertContainsEvent("in java_library rule //x:x: cycle in dependency graph");
+ }
+
+ public void testIndirectOneRuleCycle() throws Exception {
+ scratchRule(
+ "cycle",
+ "foo.h",
+ "genrule(name = 'foo.h',",
+ " outs = ['bar.h'],",
+ " srcs = ['foo.h'],",
+ " cmd = 'cp $< $@')");
+ checkError(
+ "main",
+ "mygenrule",
+ //error message
+ selfEdgeMsg("//cycle:foo.h"),
+ // Rule
+ "genrule(name='mygenrule',",
+ " outs = ['baz.h'],",
+ " srcs = ['//cycle:foo.h'],",
+ " cmd = 'cp $< $@')");
+ }
+
+ private String selfEdgeMsg(String label) {
+ return label + " [self-edge]";
+ }
+
+ // Regression test for: "IllegalStateException in
+ // AbstractConfiguredTarget.initialize()".
+ // Failure to mark all cycle-forming nodes when there are *two* cycles led to
+ // an attempt to initialise a node we'd already visited.
+ public void testTwoCycles() throws Exception {
+ reporter.removeHandler(failFastHandler); // expect errors
+ scratch.file(
+ "x/BUILD",
+ "genrule(name='b', srcs=['c'], tools=['c'], outs=['b.out'], cmd=':')",
+ "genrule(name='c', srcs=['b.out'], outs=[], cmd=':')");
+ getConfiguredTarget("//x:b"); // doesn't crash!
+ assertContainsEvent("in genrule rule //x:b: cycle in dependency graph");
+ }
+}