Open source most of our docgen tests.

--
MOS_MIGRATED_REVID=110746503
diff --git a/src/test/java/com/google/devtools/build/docgen/BUILD b/src/test/java/com/google/devtools/build/docgen/BUILD
new file mode 100644
index 0000000..81c2fa6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/BUILD
@@ -0,0 +1,59 @@
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])  # Apache 2.0
+
+test_suite(
+    name = "all_tests",
+    tags = ["docgen"],
+)
+
+java_test(
+    name = "DocumentationTests",
+    size = "medium",
+    srcs = ["DocumentationTests.java"],
+    shard_count = 1,
+    tags = ["docgen"],
+    deps = [
+        ":documentation-tests",
+        "//src/test/java/com/google/devtools/build/lib:testutil",
+        "//third_party:junit4",
+    ],
+)
+
+java_library(
+    name = "documentation-tests",
+    testonly = 1,
+    srcs = [
+        "DocCheckerUtilsTest.java",
+        "RuleDocumentationAttributeTest.java",
+        "RuleDocumentationTest.java",
+        "SkylarkDocumentationTest.java",
+    ],
+    deps = [
+        ":testutil",
+        "//src/main/java/com/google/devtools/build/docgen:docgen_javalib",
+        "//src/main/java/com/google/devtools/build/lib:build-base",
+        "//src/main/java/com/google/devtools/build/lib:packages-internal",
+        "//src/main/java/com/google/devtools/build/lib:skylarkinterface",
+        "//src/main/java/com/google/devtools/build/lib:syntax",
+        "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+        "//src/test/java/com/google/devtools/build/lib:skylark_testutil",
+        "//src/test/java/com/google/devtools/build/lib:syntax_testutil",
+        "//src/test/java/com/google/devtools/build/lib:testutil",
+        "//third_party:guava",
+        "//third_party:junit4",
+        "//third_party:truth",
+    ],
+)
+
+java_library(
+    name = "testutil",
+    testonly = 1,
+    srcs = glob(["testutil/*.java"]),
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib:build-base",
+        "//src/main/java/com/google/devtools/build/lib:packages-internal",
+    ],
+)
diff --git a/src/test/java/com/google/devtools/build/docgen/DocCheckerUtilsTest.java b/src/test/java/com/google/devtools/build/docgen/DocCheckerUtilsTest.java
new file mode 100644
index 0000000..6e40bac
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/DocCheckerUtilsTest.java
@@ -0,0 +1,44 @@
+// 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.docgen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for DocCheckerUtils.
+ */
+@RunWith(JUnit4.class)
+public class DocCheckerUtilsTest {
+
+  @Test
+  public void testUnclosedTags() {
+    assertNull(DocCheckerUtils.getFirstUnclosedTag("<html></html>"));
+    assertEquals("ol", DocCheckerUtils.getFirstUnclosedTag("<html><ol></html>"));
+    assertEquals("ol", DocCheckerUtils.getFirstUnclosedTag("<html><ol><li>foo</li></html>"));
+    assertEquals("ol", DocCheckerUtils.getFirstUnclosedTag("<html><ol><li/>foo<li/>bar</html>"));
+  }
+
+  @Test
+  public void testUncheckedTagsDontFire() {
+    assertNull(DocCheckerUtils.getFirstUnclosedTag("<html><br></html>"));
+    assertNull(DocCheckerUtils.getFirstUnclosedTag("<html><li></html>"));
+    assertNull(DocCheckerUtils.getFirstUnclosedTag("<html><ul></html>"));
+    assertNull(DocCheckerUtils.getFirstUnclosedTag("<html><p></html>"));
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/docgen/DocumentationTests.java b/src/test/java/com/google/devtools/build/docgen/DocumentationTests.java
new file mode 100644
index 0000000..c61090e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/DocumentationTests.java
@@ -0,0 +1,25 @@
+// 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.docgen;
+
+import com.google.devtools.build.lib.testutil.ClasspathSuite;
+
+import org.junit.runner.RunWith;
+
+/**
+ * Test suite for options parsing framework.
+ */
+@RunWith(ClasspathSuite.class)
+public class DocumentationTests {
+}
\ No newline at end of file
diff --git a/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java
new file mode 100644
index 0000000..eec4611
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java
@@ -0,0 +1,157 @@
+// 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.docgen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.docgen.testutil.TestData.BaseRule;
+import com.google.devtools.build.docgen.testutil.TestData.IntermediateRule;
+import com.google.devtools.build.docgen.testutil.TestData.TestRule;
+import com.google.devtools.build.lib.packages.Attribute;
+import com.google.devtools.build.lib.packages.BuildType;
+import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
+import com.google.devtools.build.lib.syntax.Type;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * A test class for RuleDocumentationAttribute.
+ */
+@RunWith(JUnit4.class)
+public class RuleDocumentationAttributeTest {
+  private static final ImmutableSet<String> NO_FLAGS = ImmutableSet.<String>of();
+
+  @Test
+  public void testDirectChild() {
+    RuleDocumentationAttribute attr1 = RuleDocumentationAttribute.create(
+        IntermediateRule.class, "", "", 0, NO_FLAGS);
+    assertEquals(1, attr1.getDefinitionClassAncestryLevel(TestRule.class));
+  }
+
+  @Test
+  public void testTransitiveChild() {
+    RuleDocumentationAttribute attr2 = RuleDocumentationAttribute.create(
+        BaseRule.class, "", "", 0, NO_FLAGS);
+    assertEquals(2, attr2.getDefinitionClassAncestryLevel(TestRule.class));
+  }
+
+  @Test
+  public void testClassIsNotChild() {
+    RuleDocumentationAttribute attr2 = RuleDocumentationAttribute.create(
+        IntermediateRule.class, "", "", 0, NO_FLAGS);
+    assertEquals(-1, attr2.getDefinitionClassAncestryLevel(BaseRule.class));
+  }
+
+  @Test
+  public void testClassIsSame() {
+    RuleDocumentationAttribute attr3 = RuleDocumentationAttribute.create(
+        TestRule.class, "", "", 0, NO_FLAGS);
+    assertEquals(0, attr3.getDefinitionClassAncestryLevel(TestRule.class));
+  }
+
+  @Test
+  public void testHasFlags() {
+    RuleDocumentationAttribute attr = RuleDocumentationAttribute.create(
+        TestRule.class, "", "", 0, ImmutableSet.<String>of("SOME_FLAG"));
+    assertTrue(attr.hasFlag("SOME_FLAG"));
+  }
+
+  @Test
+  public void testCompareTo() {
+    assertTrue(
+        RuleDocumentationAttribute.create(TestRule.class, "a", "", 0, NO_FLAGS)
+        .compareTo(
+            RuleDocumentationAttribute.create(TestRule.class, "b", "", 0, NO_FLAGS))
+            == -1);
+  }
+
+  @Test
+  public void testCompareToWithPriorityAttributeName() {
+    assertTrue(
+        RuleDocumentationAttribute.create(TestRule.class, "a", "", 0, NO_FLAGS)
+        .compareTo(
+            RuleDocumentationAttribute.create(TestRule.class, "name", "", 0, NO_FLAGS))
+            == 1);
+  }
+
+  @Test
+  public void testEquals() {
+    assertEquals(
+        RuleDocumentationAttribute.create(TestRule.class, "a", "", 0, NO_FLAGS),
+        RuleDocumentationAttribute.create(IntermediateRule.class, "a", "", 0, NO_FLAGS));
+  }
+
+  @Test
+  public void testHashCode() {
+    assertEquals(
+        RuleDocumentationAttribute.create(TestRule.class, "a", "", 0, NO_FLAGS)
+        .hashCode(),
+        RuleDocumentationAttribute.create(IntermediateRule.class, "a", "", 0, NO_FLAGS)
+        .hashCode());
+  }
+
+  @Test
+  public void testSynopsisForStringAttribute() {
+    final String defaultValue = "9";
+    Attribute attribute = Attribute.attr("foo_version", Type.STRING)
+        .value(defaultValue).build();
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(
+        TestRule.class, "testrule", "${SYNOPSIS}", 0, NO_FLAGS);
+    attributeDoc.setAttribute(attribute);
+    String doc = attributeDoc.getSynopsis();
+    assertEquals("String; optional; default is \"" + defaultValue + "\"", doc);
+  }
+
+  @Test
+  public void testSynopsisForIntegerAttribute() {
+    final int defaultValue = 384;
+    Attribute attribute = Attribute.attr("bar_limit", Type.INTEGER)
+        .value(defaultValue).build();
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(
+        TestRule.class, "testrule", "${SYNOPSIS}", 0, NO_FLAGS);
+    attributeDoc.setAttribute(attribute);
+    String doc = attributeDoc.getSynopsis();
+    assertEquals("Integer; optional; default is " + defaultValue, doc);
+  }
+
+  @Test
+  public void testSynopsisForLabelListAttribute() {
+    Attribute attribute = Attribute.attr("some_labels", BuildType.LABEL_LIST)
+        .allowedRuleClasses("foo_rule")
+        .allowedFileTypes()
+        .build();
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(
+        TestRule.class, "testrule", "${SYNOPSIS}", 0, NO_FLAGS);
+    attributeDoc.setAttribute(attribute);
+    String doc = attributeDoc.getSynopsis();
+    assertEquals("List of <a href=\"../build-ref.html#labels\">labels</a>; optional", doc);
+  }
+
+  @Test
+  public void testSynopsisForMandatoryAttribute() {
+    Attribute attribute = Attribute.attr("baz_labels", BuildType.LABEL)
+        .mandatory()
+        .allowedFileTypes(CppFileTypes.CPP_HEADER)
+        .build();
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(
+        TestRule.class, "testrule", "${SYNOPSIS}", 0, NO_FLAGS);
+    attributeDoc.setAttribute(attribute);
+    String doc = attributeDoc.getSynopsis();
+    assertEquals("<a href=\"../build-ref.html#labels\">Label</a>; required", doc);
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/docgen/RuleDocumentationTest.java b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationTest.java
new file mode 100644
index 0000000..549224a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationTest.java
@@ -0,0 +1,201 @@
+// 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.docgen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.docgen.testutil.TestData.TestRule;
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * A test class for RuleDocumentation.
+ */
+@RunWith(JUnit4.class)
+public class RuleDocumentationTest {
+
+  private static final ImmutableSet<String> NO_FLAGS = ImmutableSet.<String>of();
+  private static final ConfiguredRuleClassProvider provider =
+      TestRuleClassProvider.getRuleClassProvider();
+
+  private static void assertContains(String base, String value) {
+    assertTrue(base + " is expected to contain " + value, base.contains(value));
+  }
+
+  private static void assertNotContains(String base, String value) {
+    assertFalse(base + " is not expected to contain " + value, base.contains(value));
+  }
+
+  private void checkAttributeForRule(RuleDocumentation rule, RuleDocumentationAttribute attr,
+      boolean isCommonAttribute) {
+    rule.addAttribute(attr);
+    String signature = rule.getAttributeSignature();
+    StringBuilder sb = new StringBuilder();
+    if (isCommonAttribute) {
+      sb.append("<a href=\"common-definitions.html#");
+    } else {
+      sb.append("<a href=\"#");
+    }
+    sb.append(attr.getGeneratedInRule(rule.getRuleName())).append(".");
+    sb.append(attr.getAttributeName()).append("\">").append(attr.getAttributeName()).append("</a>");
+    assertContains(signature, sb.toString());
+  }
+
+  @Test
+  public void testVariableSubstitution() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "rule", "OTHER", "FOO", Joiner.on("\n").join(new String[] {
+          "x",
+          "${VAR}",
+          "z"}),
+        0, "", ImmutableSet.<String>of(), provider);
+    ruleDoc.addDocVariable("VAR", "y");
+    assertEquals("x\ny\nz", ruleDoc.getHtmlDocumentation());
+  }
+
+  @Test
+  public void testVariablesAreHidden() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "foo_binary", "OTHER", "FOO", Joiner.on("\n").join(new String[] {
+            "x",
+            "${ATTRIBUTE_SIGNATURE}",
+            "y",
+            "${ATTRIBUTE_DEFINITION}",
+            "z"}),
+        0, "", ImmutableSet.<String>of(), provider);
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(TestRule.class,
+        "srcs", "attribute doc", 0, NO_FLAGS);
+    ruleDoc.addAttribute(attributeDoc);
+    String htmlDoc = ruleDoc.getHtmlDocumentation();
+    assertContains(htmlDoc, "x\n");
+    assertContains(htmlDoc, "y\n");
+    assertContains(htmlDoc, "z");
+    assertNotContains(htmlDoc, "${ATTRIBUTE_SIGNATURE}");
+    assertNotContains(htmlDoc, "${ATTRIBUTE_DEFINITION}");
+  }
+
+  @Test
+  public void testSignatureContainsCommonAttribute() throws Exception {
+    RuleDocumentationAttribute licensesAttr = RuleDocumentationAttribute.create(
+        "licenses", "common", "attribute doc");
+    checkAttributeForRule(new RuleDocumentation(
+        "java_binary", "BINARY", "JAVA", Joiner.on("\n").join(new String[] {
+            "${ATTRIBUTE_SIGNATURE}",
+            "${ATTRIBUTE_DEFINITION}"}),
+        0, "", ImmutableSet.<String>of(), provider), licensesAttr, true);
+  }
+
+  @Test
+  public void testInheritedAttributeGeneratesSignature() throws Exception {
+    RuleDocumentationAttribute runtimeDepsAttr = RuleDocumentationAttribute.create(TestRule.class,
+        "runtime_deps", "attribute doc", 0, NO_FLAGS);
+    checkAttributeForRule(new RuleDocumentation(
+        "java_binary", "BINARY", "JAVA", Joiner.on("\n").join(new String[] {
+            "${ATTRIBUTE_SIGNATURE}",
+            "${ATTRIBUTE_DEFINITION}"}),
+        0, "", ImmutableSet.<String>of(), provider), runtimeDepsAttr, false);
+    checkAttributeForRule(new RuleDocumentation(
+        "java_library", "LIBRARY", "JAVA", Joiner.on("\n").join(new String[] {
+            "${ATTRIBUTE_SIGNATURE}",
+            "${ATTRIBUTE_DEFINITION}"}),
+        0, "", ImmutableSet.<String>of(), provider), runtimeDepsAttr, false);
+  }
+
+  @Test
+  public void testRuleDocFlagSubstitution() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "rule", "OTHER", "FOO", "x", 0, "", ImmutableSet.<String>of("DEPRECATED"), provider);
+    ruleDoc.addDocVariable("VAR", "y");
+    assertEquals("x", ruleDoc.getHtmlDocumentation());
+  }
+
+  @Test
+  public void testCommandLineDocumentation() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "foo_binary", "OTHER", "FOO", Joiner.on("\n").join(new String[] {
+            "x",
+            "${ATTRIBUTE_SIGNATURE}",
+            "y",
+            "${ATTRIBUTE_DEFINITION}",
+            "z",
+            "${VAR}"}),
+        0, "", ImmutableSet.<String>of(), provider);
+    ruleDoc.addDocVariable("VAR", "w");
+    RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(TestRule.class,
+        "srcs", "attribute doc", 0, NO_FLAGS);
+    ruleDoc.addAttribute(attributeDoc);
+    assertEquals("\nx\n\ny\n\nz\n\n", ruleDoc.getCommandLineDocumentation());
+  }
+
+  @Test
+  public void testExtractExamples() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "rule", "OTHER", "FOO", Joiner.on("\n").join(new String[] {
+            "x",
+            "<!-- #BLAZE_RULE.EXAMPLE -->",
+            "a",
+            "<!-- #BLAZE_RULE.END_EXAMPLE -->",
+            "y",
+            "<!-- #BLAZE_RULE.EXAMPLE -->",
+            "b",
+            "<!-- #BLAZE_RULE.END_EXAMPLE -->",
+            "z"}),
+        0, "", ImmutableSet.<String>of(), provider);
+    assertEquals(ImmutableSet.<String>of("a\n", "b\n"), ruleDoc.extractExamples());
+  }
+
+  @Test
+  public void testCreateExceptions() throws BuildEncyclopediaDocException {
+    RuleDocumentation ruleDoc = new RuleDocumentation(
+        "foo_binary", "OTHER", "FOO", "", 10, "foo.txt", NO_FLAGS, provider);
+    BuildEncyclopediaDocException e = ruleDoc.createException("msg");
+    assertEquals("Error in foo.txt:10: msg", e.getMessage());
+  }
+
+  @Test
+  public void testEquals() throws BuildEncyclopediaDocException {
+    assertEquals(
+        new RuleDocumentation("rule", "OTHER", "FOO", "x", 0, "", NO_FLAGS, provider),
+        new RuleDocumentation("rule", "OTHER", "FOO", "y", 0, "", NO_FLAGS, provider));
+  }
+
+  @Test
+  public void testNotEquals() throws BuildEncyclopediaDocException {
+    assertFalse(
+        new RuleDocumentation("rule1", "OTHER", "FOO", "x", 0, "", NO_FLAGS, provider).equals(
+        new RuleDocumentation("rule2", "OTHER", "FOO", "y", 0, "", NO_FLAGS, provider)));
+  }
+
+  @Test
+  public void testCompareTo() throws BuildEncyclopediaDocException {
+    assertEquals(-1,
+        new RuleDocumentation("rule1", "OTHER", "FOO", "x", 0, "", NO_FLAGS, provider).compareTo(
+        new RuleDocumentation("rule2", "OTHER", "FOO", "x", 0, "", NO_FLAGS, provider)));
+  }
+
+  @Test
+  public void testHashCode() throws BuildEncyclopediaDocException {
+    assertEquals(
+        new RuleDocumentation("rule", "OTHER", "FOO", "x", 0, "", NO_FLAGS, provider).hashCode(),
+        new RuleDocumentation("rule", "OTHER", "FOO", "y", 0, "", NO_FLAGS, provider).hashCode());
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java b/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java
new file mode 100644
index 0000000..04a3376
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/SkylarkDocumentationTest.java
@@ -0,0 +1,173 @@
+// 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.docgen;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.docgen.skylark.SkylarkBuiltinMethodDoc;
+import com.google.devtools.build.docgen.skylark.SkylarkJavaMethodDoc;
+import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc;
+import com.google.devtools.build.lib.rules.SkylarkRuleContext;
+import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+/**
+ * Tests for Skylark documentation.
+ */
+@RunWith(JUnit4.class)
+public class SkylarkDocumentationTest extends SkylarkTestCase {
+
+  @Before
+  public final void createBuildFile() throws Exception {
+    scratch.file("foo/BUILD",
+        "genrule(name = 'foo',",
+        "  cmd = 'dummy_cmd',",
+        "  srcs = ['a.txt', 'b.img'],",
+        "  tools = ['t.exe'],",
+        "  outs = ['c.txt'])");
+  }
+
+  @Override
+  protected EvaluationTestCase createEvaluationTestCase() {
+    return new EvaluationTestCase();
+  }
+
+  @Test
+  public void testSkylarkRuleClassBuiltInItemsAreDocumented() throws Exception {
+    checkSkylarkTopLevelEnvItemsAreDocumented(ev.getEnvironment());
+  }
+
+  @Test
+  public void testSkylarkRuleImplementationBuiltInItemsAreDocumented() throws Exception {
+    // TODO(bazel-team): fix documentation for built in java objects coming from modules.
+    checkSkylarkTopLevelEnvItemsAreDocumented(ev.getEnvironment());
+  }
+
+  @SuppressWarnings("unchecked")
+  private void checkSkylarkTopLevelEnvItemsAreDocumented(Environment env) {
+    Map<String, String> docMap = new HashMap<>();
+    Map<String, SkylarkModuleDoc> modules = SkylarkDocumentationCollector.collectModules();
+    SkylarkModuleDoc topLevel =
+        modules.remove(SkylarkDocumentationCollector.getTopLevelModule().name());
+    for (Entry<String, SkylarkBuiltinMethodDoc> entry : topLevel.getBuiltinMethods().entrySet()) {
+      docMap.put(entry.getKey(), entry.getValue().getDocumentation());
+    }
+    for (Entry<String, SkylarkModuleDoc> entry : modules.entrySet()) {
+      docMap.put(entry.getKey(), entry.getValue().getDocumentation());
+    }
+
+    List<String> undocumentedItems = new ArrayList<>();
+    // All built in variables are registered in the Skylark global environment.
+    for (String varname : env.getVariableNames()) {
+      if (docMap.containsKey(varname)) {
+        if (docMap.get(varname).isEmpty()) {
+          undocumentedItems.add(varname);
+        }
+      } else {
+        undocumentedItems.add(varname);
+      }
+    }
+    assertWithMessage("Undocumented Skylark Environment items: " + undocumentedItems)
+        .that(undocumentedItems).isEmpty();
+  }
+
+  // TODO(bazel-team): come up with better Skylark specific tests.
+  @Test
+  public void testDirectJavaMethodsAreGenerated() throws Exception {
+    assertThat(collect(SkylarkRuleContext.class)).isNotEmpty();
+  }
+
+  @SkylarkModule(name = "MockClassA", doc = "")
+  public static class MockClassA {
+    @SkylarkCallable(doc = "")
+    public Integer get() {
+      return 0;
+    }
+  }
+
+  @SkylarkModule(name = "MockClassB", doc = "")
+  public static class MockClassB {
+    @SkylarkCallable(doc = "")
+    public MockClassA get() {
+      return new MockClassA();
+    }
+  }
+
+  @SkylarkModule(name = "MockClassC", doc = "")
+  public static class MockClassC extends MockClassA {
+    @SkylarkCallable(doc = "")
+    public Integer get2() {
+      return 0;
+    }
+  }
+
+  @Test
+  public void testSkylarkJavaInterfaceExplorerOnSimpleClass() throws Exception {
+    Map<String, SkylarkModuleDoc> objects = collect(MockClassA.class);
+    assertThat(extractMethods(Iterables.getOnlyElement(objects.values())
+        .getJavaMethods())).containsExactly(MockClassA.class.getMethod("get"));
+  }
+
+  @Test
+  public void testSkylarkJavaInterfaceExplorerFindsClassFromReturnValue() throws Exception {
+    Map<String, SkylarkModuleDoc> objects = collect(MockClassB.class);
+    assertThat(extractMethods(
+        objects.get("MockClassA").getJavaMethods())).containsExactly(
+        MockClassA.class.getMethod("get"));
+  }
+
+  @Test
+  public void testSkylarkJavaInterfaceExplorerFindsAllMethodsOnSubClass() throws Exception {
+    Map<String, SkylarkModuleDoc> objects = collect(MockClassC.class);
+    assertThat(extractMethods(Iterables.getOnlyElement(objects.values())
+        .getJavaMethods())).containsExactly(
+        MockClassA.class.getMethod("get"), MockClassC.class.getMethod("get2"));
+  }
+
+  private Iterable<Method> extractMethods(Collection<SkylarkJavaMethodDoc> methods) {
+    return Iterables.transform(methods, new Function<SkylarkJavaMethodDoc, Method>() {
+      @Override
+      public Method apply(SkylarkJavaMethodDoc input) {
+        return input.getMethod();
+      }
+    });
+  }
+
+  private Map<String, SkylarkModuleDoc> collect(Class<?> classObject) {
+    Map<String, SkylarkModuleDoc> modules = new TreeMap<>();
+    SkylarkDocumentationCollector.collectJavaObjects(
+        classObject.getAnnotation(SkylarkModule.class), classObject, modules);
+    return modules;
+  }
+}
diff --git a/src/test/java/com/google/devtools/build/docgen/testutil/TestData.java b/src/test/java/com/google/devtools/build/docgen/testutil/TestData.java
new file mode 100644
index 0000000..34d7d17
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/docgen/testutil/TestData.java
@@ -0,0 +1,78 @@
+// 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.docgen.testutil;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
+
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+
+/**
+ * Rule definitions that can be used for testing.
+ */
+public class TestData {
+  public static class TestRule implements RuleDefinition {
+    @Override
+    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
+      return builder
+          .add(attr("foo", LABEL_LIST))
+          .build();
+    }
+
+    @Override
+    public Metadata getMetadata() {
+      return RuleDefinition.Metadata.builder().name("testrule")
+          .factoryClass(DummyRuleFactory.class).ancestors(IntermediateRule.class).build();
+    }
+  }
+
+  public static class IntermediateRule implements RuleDefinition {
+    @Override
+    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
+      return builder.build();
+    }
+
+    @Override
+    public Metadata getMetadata() {
+      return RuleDefinition.Metadata.builder().name("testrule")
+          .factoryClass(DummyRuleFactory.class).ancestors(BaseRule.class).build();
+    }
+  }
+
+  public static class BaseRule implements RuleDefinition {
+    @Override
+    public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment environment) {
+      return builder.build();
+    }
+
+    @Override
+    public Metadata getMetadata() {
+      return RuleDefinition.Metadata.builder().name("base_rule")
+          .factoryClass(DummyRuleFactory.class).build();
+    }
+  }
+
+  public static class DummyRuleFactory implements RuleConfiguredTargetFactory {
+    @Override
+    public ConfiguredTarget create(RuleContext ruleContext) {
+      throw new IllegalStateException();
+    }
+  }
+}
+
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index d3bae45..24ceae3 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -792,6 +792,7 @@
     srcs = glob([
         "syntax/util/*.java",
     ]),
+    visibility = ["//visibility:public"],
     deps = [
         ":foundations_testutil",
         ":test_runner",
@@ -818,6 +819,7 @@
     srcs = glob([
         "skylark/util/*.java",
     ]),
+    visibility = ["//visibility:public"],
     deps = [
         ":analysis_testutil",
         ":foundations_testutil",
diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
index 9278dd3..701487f 100644
--- a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
+++ b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java
@@ -72,4 +72,7 @@
   public static final String TOOLS_REPOSITORY = "@bazel_tools";
 
   public static final String TOOLS_REPOSITORY_PATH = "tools/cpp";
+
+  public static final ImmutableList<String> DOCS_RULES_PATHS = ImmutableList.of(
+      "src/main/java/com/google/devtools/build/lib/rules");
 }