| // 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.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.docgen.annot.DocumentMethods; |
| import com.google.devtools.build.docgen.annot.StarlarkConstructor; |
| import com.google.devtools.build.docgen.starlark.StarlarkBuiltinDoc; |
| import com.google.devtools.build.docgen.starlark.StarlarkConstructorMethodDoc; |
| import com.google.devtools.build.docgen.starlark.StarlarkMethodDoc; |
| import com.google.devtools.build.lib.analysis.starlark.StarlarkModules; |
| import com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext; |
| import com.google.devtools.build.lib.collect.nestedset.Depset; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.stream.Collectors; |
| import net.starlark.java.annot.Param; |
| import net.starlark.java.annot.StarlarkBuiltin; |
| import net.starlark.java.annot.StarlarkMethod; |
| import net.starlark.java.eval.Dict; |
| import net.starlark.java.eval.Sequence; |
| import net.starlark.java.eval.StarlarkList; |
| import net.starlark.java.eval.StarlarkValue; |
| import net.starlark.java.eval.Tuple; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for Starlark documentation. */ |
| @RunWith(JUnit4.class) |
| public class StarlarkDocumentationTest { |
| |
| private static final ImmutableList<String> DEPRECATED_UNDOCUMENTED_TOP_LEVEL_SYMBOLS = |
| ImmutableList.of("Actions"); |
| |
| @Test |
| public void testStarlarkRuleClassBuiltInItemsAreDocumented() throws Exception { |
| ImmutableMap.Builder<String, Object> env = ImmutableMap.builder(); |
| StarlarkModules.addPredeclared(env); |
| checkStarlarkTopLevelEnvItemsAreDocumented(env.build()); |
| } |
| |
| private void checkStarlarkTopLevelEnvItemsAreDocumented(Map<String, Object> globals) |
| throws Exception { |
| Map<String, String> docMap = new HashMap<>(); |
| Map<String, StarlarkBuiltinDoc> modules = |
| new TreeMap<>(StarlarkDocumentationCollector.getAllModules()); |
| StarlarkBuiltinDoc topLevel = |
| modules.remove(StarlarkDocumentationCollector.getTopLevelModule().name()); |
| for (StarlarkMethodDoc method : topLevel.getMethods()) { |
| docMap.put(method.getName(), method.getDocumentation()); |
| } |
| for (Map.Entry<String, StarlarkBuiltinDoc> entry : modules.entrySet()) { |
| docMap.put(entry.getKey(), entry.getValue().getDocumentation()); |
| } |
| |
| List<String> undocumentedItems = new ArrayList<>(); |
| // All built in variables are registered in the Starlark global environment. |
| for (String varname : globals.keySet()) { |
| if (docMap.containsKey(varname)) { |
| if (docMap.get(varname).isEmpty()) { |
| undocumentedItems.add(varname); |
| } |
| } else { |
| undocumentedItems.add(varname); |
| } |
| } |
| |
| // These constants are currently undocumented. |
| // If they need documentation, the easiest approach would be |
| // to hard-code it in StarlarkDocumentationCollector. |
| undocumentedItems.remove("True"); |
| undocumentedItems.remove("False"); |
| undocumentedItems.remove("None"); |
| |
| assertWithMessage("Undocumented items: " + undocumentedItems) |
| .that(undocumentedItems) |
| .containsExactlyElementsIn(DEPRECATED_UNDOCUMENTED_TOP_LEVEL_SYMBOLS); |
| } |
| |
| // TODO(bazel-team): come up with better Starlark specific tests. |
| @Test |
| public void testDirectJavaMethodsAreGenerated() throws Exception { |
| assertThat(collect(StarlarkRuleContext.class)).isNotEmpty(); |
| } |
| |
| /** MockClassA */ |
| @StarlarkBuiltin(name = "MockClassA", doc = "MockClassA") |
| private static class MockClassA implements StarlarkValue { |
| @StarlarkMethod(name = "get", doc = "MockClassA#get") |
| public Integer get() { |
| return 0; |
| } |
| } |
| |
| /** MockClassD */ |
| @StarlarkBuiltin(name = "MockClassD", doc = "MockClassD") |
| private static class MockClassD implements StarlarkValue { |
| @StarlarkMethod( |
| name = "test", |
| doc = "MockClassD#test", |
| parameters = { |
| @Param(name = "a"), |
| @Param(name = "b"), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| }) |
| public Integer test(int a, int b, int c, int d) { |
| return 0; |
| } |
| } |
| |
| /** MockClassE */ |
| @StarlarkBuiltin(name = "MockClassE", doc = "MockClassE") |
| private static class MockClassE extends MockClassA { |
| @Override |
| public Integer get() { |
| return 1; |
| } |
| } |
| |
| /** MockClassF */ |
| @StarlarkBuiltin(name = "MockClassF", doc = "MockClassF") |
| private static class MockClassF implements StarlarkValue { |
| @StarlarkMethod( |
| name = "test", |
| doc = "MockClassF#test", |
| parameters = { |
| @Param(name = "a", named = false, positional = true), |
| @Param(name = "b", named = true, positional = true), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| }, |
| extraPositionals = @Param(name = "myArgs")) |
| public Integer test(int a, int b, int c, int d, Sequence<?> args) { |
| return 0; |
| } |
| } |
| |
| /** MockClassG */ |
| @StarlarkBuiltin(name = "MockClassG", doc = "MockClassG") |
| private static class MockClassG implements StarlarkValue { |
| @StarlarkMethod( |
| name = "test", |
| doc = "MockClassG#test", |
| parameters = { |
| @Param(name = "a", named = false, positional = true), |
| @Param(name = "b", named = true, positional = true), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| }, |
| extraKeywords = @Param(name = "myKwargs")) |
| public Integer test(int a, int b, int c, int d, Dict<?, ?> kwargs) { |
| return 0; |
| } |
| } |
| |
| /** MockClassH */ |
| @StarlarkBuiltin(name = "MockClassH", doc = "MockClassH") |
| private static class MockClassH implements StarlarkValue { |
| @StarlarkMethod( |
| name = "test", |
| doc = "MockClassH#test", |
| parameters = { |
| @Param(name = "a", named = false, positional = true), |
| @Param(name = "b", named = true, positional = true), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| }, |
| extraPositionals = @Param(name = "myArgs"), |
| extraKeywords = @Param(name = "myKwargs")) |
| public Integer test(int a, int b, int c, int d, Sequence<?> args, Dict<?, ?> kwargs) { |
| return 0; |
| } |
| } |
| |
| /** MockClassI */ |
| @StarlarkBuiltin(name = "MockClassI", doc = "MockClassI") |
| private static class MockClassI implements StarlarkValue { |
| @StarlarkMethod( |
| name = "test", |
| doc = "MockClassI#test", |
| parameters = { |
| @Param(name = "a", named = false, positional = true), |
| @Param(name = "b", named = true, positional = true), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| @Param( |
| name = "e", |
| named = true, |
| positional = false, |
| documented = false, |
| defaultValue = "2"), |
| }, |
| extraPositionals = @Param(name = "myArgs")) |
| public Integer test(int a, int b, int c, int d, int e, Sequence<?> args) { |
| return 0; |
| } |
| } |
| |
| /** |
| * MockGlobalLibrary. While nothing directly depends on it, a test method in |
| * StarlarkDocumentationTest checks all of the classes under a wide classpath and ensures this one |
| * shows up. |
| */ |
| @DocumentMethods |
| @SuppressWarnings("unused") |
| private static class MockGlobalLibrary { |
| @StarlarkMethod( |
| name = "MockGlobalCallable", |
| doc = "GlobalCallable documentation", |
| parameters = { |
| @Param(name = "a", named = false, positional = true), |
| @Param(name = "b", named = true, positional = true), |
| @Param(name = "c", named = true, positional = false), |
| @Param(name = "d", named = true, positional = false, defaultValue = "1"), |
| }, |
| extraPositionals = @Param(name = "myArgs"), |
| extraKeywords = @Param(name = "myKwargs")) |
| public Integer test(int a, int b, int c, int d, Sequence<?> args, Dict<?, ?> kwargs) { |
| return 0; |
| } |
| } |
| |
| /** MockClassWithContainerReturnValues */ |
| @StarlarkBuiltin( |
| name = "MockClassWithContainerReturnValues", |
| doc = "MockClassWithContainerReturnValues") |
| private static class MockClassWithContainerReturnValues implements StarlarkValue { |
| |
| @StarlarkMethod(name = "depset", doc = "depset") |
| public Depset /*<Integer>*/ getNestedSet() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "tuple", doc = "tuple") |
| public Tuple getTuple() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "immutable", doc = "immutable") |
| public ImmutableList<Integer> getImmutableList() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "mutable", doc = "mutable") |
| public StarlarkList<Integer> getMutableList() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "starlark", doc = "starlark") |
| public Sequence<Integer> getStarlarkList() { |
| return null; |
| } |
| } |
| |
| /** MockClassCommonNameOne */ |
| @StarlarkBuiltin(name = "MockClassCommonName", doc = "MockClassCommonName") |
| private static class MockClassCommonNameOne implements StarlarkValue { |
| |
| @StarlarkMethod(name = "one", doc = "one") |
| public Integer one() { |
| return 1; |
| } |
| } |
| |
| /** SubclassOfMockClassCommonNameOne */ |
| @StarlarkBuiltin(name = "MockClassCommonName", doc = "MockClassCommonName") |
| private static class SubclassOfMockClassCommonNameOne extends MockClassCommonNameOne { |
| |
| @StarlarkMethod(name = "two", doc = "two") |
| public Integer two() { |
| return 1; |
| } |
| } |
| |
| /** PointsToCommonNameOneWithSubclass */ |
| @StarlarkBuiltin( |
| name = "PointsToCommonNameOneWithSubclass", |
| doc = "PointsToCommonNameOneWithSubclass") |
| private static class PointsToCommonNameOneWithSubclass implements StarlarkValue { |
| @StarlarkMethod(name = "one", doc = "one") |
| public MockClassCommonNameOne getOne() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "one_subclass", doc = "one_subclass") |
| public SubclassOfMockClassCommonNameOne getOneSubclass() { |
| return null; |
| } |
| } |
| |
| /** MockClassCommonNameOneUndocumented */ |
| @StarlarkBuiltin(name = "MockClassCommonName", documented = false, doc = "") |
| private static class MockClassCommonNameUndocumented implements StarlarkValue { |
| |
| @StarlarkMethod(name = "two", doc = "two") |
| public Integer two() { |
| return 1; |
| } |
| } |
| |
| /** PointsToCommonNameAndUndocumentedModule */ |
| @StarlarkBuiltin( |
| name = "PointsToCommonNameAndUndocumentedModule", |
| doc = "PointsToCommonNameAndUndocumentedModule") |
| private static class PointsToCommonNameAndUndocumentedModule implements StarlarkValue { |
| @StarlarkMethod(name = "one", doc = "one") |
| public MockClassCommonNameOne getOne() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "undocumented_module", doc = "undocumented_module") |
| public MockClassCommonNameUndocumented getUndocumented() { |
| return null; |
| } |
| } |
| |
| /** A module which has a selfCall method which constructs copies of MockClassA. */ |
| @StarlarkBuiltin( |
| name = "MockClassWithSelfCallConstructor", |
| doc = "MockClassWithSelfCallConstructor") |
| private static class MockClassWithSelfCallConstructor implements StarlarkValue { |
| @StarlarkMethod(name = "one", doc = "one") |
| public MockClassCommonNameOne getOne() { |
| return null; |
| } |
| |
| @StarlarkMethod(name = "makeMockClassA", selfCall = true, doc = "makeMockClassA") |
| @StarlarkConstructor |
| public MockClassA makeMockClassA() { |
| return new MockClassA(); |
| } |
| } |
| |
| @Test |
| public void testStarlarkCallableParameters() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassD.class); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassD"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassD"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassD#test"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> MockClassD.test(a, b, *, c, d=1)"); |
| assertThat(methodDoc.getParams()).hasSize(4); |
| } |
| |
| @Test |
| public void testStarlarkCallableParametersAndArgs() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassF.class); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassF"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassF"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassF#test"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> " |
| + "MockClassF.test(a, b, *, c, d=1, *myArgs)"); |
| assertThat(methodDoc.getParams()).hasSize(5); |
| } |
| |
| @Test |
| public void testStarlarkCallableParametersAndKwargs() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassG.class); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassG"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassG"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassG#test"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> " |
| + "MockClassG.test(a, b, *, c, d=1, **myKwargs)"); |
| assertThat(methodDoc.getParams()).hasSize(5); |
| } |
| |
| @Test |
| public void testStarlarkCallableParametersAndArgsAndKwargs() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassH.class); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassH"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassH"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassH#test"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> " |
| + "MockClassH.test(a, b, *, c, d=1, *myArgs, **myKwargs)"); |
| assertThat(methodDoc.getParams()).hasSize(6); |
| } |
| |
| @Test |
| public void testStarlarkUndocumentedParameters() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassI.class); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassI"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassI"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassI#test"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> " |
| + "MockClassI.test(a, b, *, c, d=1, *myArgs)"); |
| assertThat(methodDoc.getParams()).hasSize(5); |
| } |
| |
| @Test |
| public void testStarlarkGlobalLibraryCallable() throws Exception { |
| StarlarkBuiltinDoc topLevel = |
| StarlarkDocumentationCollector.getAllModules() |
| .get(StarlarkDocumentationCollector.getTopLevelModule().name()); |
| |
| boolean foundGlobalLibrary = false; |
| for (StarlarkMethodDoc methodDoc : topLevel.getMethods()) { |
| if (methodDoc.getName().equals("MockGlobalCallable")) { |
| assertThat(methodDoc.getDocumentation()).isEqualTo("GlobalCallable documentation"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo( |
| "<a class=\"anchor\" href=\"int.html\">int</a> " |
| + "MockGlobalCallable(a, b, *, c, d=1, *myArgs, **myKwargs)"); |
| foundGlobalLibrary = true; |
| break; |
| } |
| } |
| assertThat(foundGlobalLibrary).isTrue(); |
| } |
| |
| @Test |
| public void testStarlarkCallableOverriding() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = |
| collect(ImmutableList.of(MockClassA.class, MockClassE.class)); |
| StarlarkBuiltinDoc moduleDoc = objects.get("MockClassE"); |
| assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassE"); |
| assertThat(moduleDoc.getMethods()).hasSize(1); |
| StarlarkMethodDoc methodDoc = moduleDoc.getMethods().iterator().next(); |
| assertThat(methodDoc.getDocumentation()).isEqualTo("MockClassA#get"); |
| assertThat(methodDoc.getSignature()) |
| .isEqualTo("<a class=\"anchor\" href=\"int.html\">int</a> MockClassE.get()"); |
| } |
| |
| @Test |
| public void testStarlarkContainerReturnTypesWithoutAnnotations() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = collect(MockClassWithContainerReturnValues.class); |
| assertThat(objects).containsKey("MockClassWithContainerReturnValues"); |
| Collection<? extends StarlarkMethodDoc> methods = |
| objects.get("MockClassWithContainerReturnValues").getMethods(); |
| |
| List<String> signatures = |
| methods.stream().map(m -> m.getSignature()).collect(Collectors.toList()); |
| assertThat(signatures).hasSize(5); |
| assertThat(signatures) |
| .contains( |
| "<a class=\"anchor\" href=\"depset.html\">depset</a> " |
| + "MockClassWithContainerReturnValues.depset()"); |
| assertThat(signatures) |
| .contains( |
| "<a class=\"anchor\" href=\"tuple.html\">tuple</a> " |
| + "MockClassWithContainerReturnValues.tuple()"); |
| assertThat(signatures) |
| .contains( |
| "<a class=\"anchor\" href=\"list.html\">list</a> " |
| + "MockClassWithContainerReturnValues.immutable()"); |
| assertThat(signatures) |
| .contains( |
| "<a class=\"anchor\" href=\"list.html\">list</a> " |
| + "MockClassWithContainerReturnValues.mutable()"); |
| assertThat(signatures) |
| .contains( |
| "<a class=\"anchor\" href=\"list.html\">sequence</a> " |
| + "MockClassWithContainerReturnValues.starlark()"); |
| } |
| |
| @Test |
| public void testDocumentedModuleTakesPrecedence() throws Exception { |
| Map<String, StarlarkBuiltinDoc> objects = |
| collect( |
| ImmutableList.of( |
| PointsToCommonNameAndUndocumentedModule.class, |
| MockClassCommonNameOne.class, |
| MockClassCommonNameUndocumented.class)); |
| Collection<? extends StarlarkMethodDoc> methods = |
| objects.get("MockClassCommonName").getMethods(); |
| List<String> methodNames = |
| methods.stream().map(m -> m.getName()).collect(Collectors.toList()); |
| assertThat(methodNames).containsExactly("one"); |
| } |
| |
| @Test |
| public void testDocumentModuleSubclass() { |
| Map<String, StarlarkBuiltinDoc> objects = |
| collect( |
| ImmutableList.of( |
| PointsToCommonNameOneWithSubclass.class, |
| MockClassCommonNameOne.class, |
| SubclassOfMockClassCommonNameOne.class)); |
| Collection<? extends StarlarkMethodDoc> methods = |
| objects.get("MockClassCommonName").getMethods(); |
| List<String> methodNames = |
| methods.stream().map(m -> m.getName()).collect(Collectors.toList()); |
| assertThat(methodNames).containsExactly("one", "two"); |
| } |
| |
| @Test |
| public void testDocumentSelfcallConstructor() { |
| Map<String, StarlarkBuiltinDoc> objects = |
| collect(ImmutableList.of(MockClassA.class, MockClassWithSelfCallConstructor.class)); |
| Collection<? extends StarlarkMethodDoc> methods = objects.get("MockClassA").getMethods(); |
| StarlarkMethodDoc firstMethod = methods.iterator().next(); |
| assertThat(firstMethod).isInstanceOf(StarlarkConstructorMethodDoc.class); |
| |
| List<String> methodNames = methods.stream().map(m -> m.getName()).collect(Collectors.toList()); |
| assertThat(methodNames).containsExactly("MockClassA", "get"); |
| } |
| |
| private Map<String, StarlarkBuiltinDoc> collect(Iterable<Class<?>> classObjects) { |
| return StarlarkDocumentationCollector.collectModules(classObjects); |
| } |
| |
| private Map<String, StarlarkBuiltinDoc> collect(Class<?> classObject) { |
| return collect(ImmutableList.of(classObject)); |
| } |
| } |