blob: 412a74e6c4334821cf63f174ff48e44ac9bbc3cd [file] [log] [blame]
// 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.Iterables;
import com.google.devtools.build.docgen.skylark.SkylarkBuiltinMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkJavaMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkMethodDoc;
import com.google.devtools.build.docgen.skylark.SkylarkModuleDoc;
import com.google.devtools.build.lib.analysis.skylark.SkylarkRuleContext;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.skylark.util.SkylarkTestCase;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkGlobalLibrary;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.syntax.SkylarkSemantics;
import com.google.devtools.build.lib.syntax.util.EvaluationTestCase;
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.TreeMap;
import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* 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(SkylarkSemantics semantics) {
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) throws Exception {
Map<String, String> docMap = new HashMap<>();
Map<String, SkylarkModuleDoc> modules = SkylarkDocumentationCollector.collectModules();
SkylarkModuleDoc topLevel =
modules.remove(SkylarkDocumentationCollector.getTopLevelModule().name());
for (Map.Entry<String, SkylarkBuiltinMethodDoc> entry :
topLevel.getBuiltinMethods().entrySet()) {
docMap.put(entry.getKey(), entry.getValue().getDocumentation());
}
for (Map.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();
}
/** MockClassA */
@SkylarkModule(name = "MockClassA", doc = "MockClassA")
private static class MockClassA {
@SkylarkCallable(name = "get", doc = "MockClassA#get")
public Integer get() {
return 0;
}
}
/** MockClassB */
@SkylarkModule(name = "MockClassB", doc = "MockClassB")
private static class MockClassB {
@SkylarkCallable(name = "get", doc = "MockClassB#get")
public MockClassA get() {
return new MockClassA();
}
}
/** MockClassC */
@SkylarkModule(name = "MockClassC", doc = "MockClassC")
private static class MockClassC extends MockClassA {
@SkylarkCallable(name = "get2", doc = "MockClassC#get2")
public Integer get2() {
return 0;
}
}
/** MockClassD */
@SkylarkModule(name = "MockClassD", doc = "MockClassD")
private static class MockClassD {
@SkylarkCallable(
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 */
@SkylarkModule(name = "MockClassE", doc = "MockClassE")
private static class MockClassE extends MockClassA {
@Override
public Integer get() {
return 1;
}
}
/** MockClassF */
@SkylarkModule(name = "MockClassF", doc = "MockClassF")
private static class MockClassF {
@SkylarkCallable(
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, SkylarkList<?> args) {
return 0;
}
}
/** MockClassG */
@SkylarkModule(name = "MockClassG", doc = "MockClassG")
private static class MockClassG {
@SkylarkCallable(
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, SkylarkDict<?, ?> kwargs) {
return 0;
}
}
/** MockClassH */
@SkylarkModule(name = "MockClassH", doc = "MockClassH")
private static class MockClassH {
@SkylarkCallable(
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, SkylarkList<?> args, SkylarkDict<?, ?> kwargs) {
return 0;
}
}
/** MockGlobalLibrary */
@SkylarkGlobalLibrary
private static class MockGlobalLibrary {
@SkylarkCallable(
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, SkylarkList<?> args, SkylarkDict<?, ?> kwargs) {
return 0;
}
}
/** MockClassWithContainerReturnValues */
@SkylarkModule(name = "MockClassWithContainerReturnValues",
doc = "MockClassWithContainerReturnValues")
private static class MockClassWithContainerReturnValues {
@SkylarkCallable(name = "depset", doc = "depset")
public NestedSet<Integer> getNestedSet() {
return null;
}
@SkylarkCallable(name = "tuple", doc = "tuple")
public Tuple<Integer> getTuple() {
return null;
}
@SkylarkCallable(name = "immutable", doc = "immutable")
public ImmutableList<Integer> getImmutableList() {
return null;
}
@SkylarkCallable(name = "mutable", doc = "mutable")
public MutableList<Integer> getMutableList() {
return null;
}
@SkylarkCallable(name = "skylark", doc = "skylark")
public SkylarkList<Integer> getSkylarkList() {
return null;
}
}
/** MockClassCommonNameOne */
@SkylarkModule(name = "MockClassCommonName",
doc = "MockClassCommonName")
private static class MockClassCommonNameOne {
@SkylarkCallable(name = "one", doc = "one")
public Integer one() {
return 1;
}
}
/** SubclassOfMockClassCommonNameOne */
@SkylarkModule(name = "MockClassCommonName",
doc = "MockClassCommonName")
private static class SubclassOfMockClassCommonNameOne extends MockClassCommonNameOne {
@SkylarkCallable(name = "two", doc = "two")
public Integer two() {
return 1;
}
}
/** PointsToCommonNameOneWithSubclass */
@SkylarkModule(name = "PointsToCommonNameOneWithSubclass",
doc = "PointsToCommonNameOneWithSubclass")
private static class PointsToCommonNameOneWithSubclass {
@SkylarkCallable(name = "one", doc = "one")
public MockClassCommonNameOne getOne() {
return null;
}
@SkylarkCallable(name = "one_subclass", doc = "one_subclass")
public SubclassOfMockClassCommonNameOne getOneSubclass() {
return null;
}
}
/** MockClassCommonNameOneUndocumented */
@SkylarkModule(name = "MockClassCommonName",
documented = false,
doc = "")
private static class MockClassCommonNameUndocumented {
@SkylarkCallable(name = "two", doc = "two")
public Integer two() {
return 1;
}
}
/** PointsToCommonNameAndUndocumentedModule */
@SkylarkModule(name = "PointsToCommonNameAndUndocumentedModule",
doc = "PointsToCommonNameAndUndocumentedModule")
private static class PointsToCommonNameAndUndocumentedModule {
@SkylarkCallable(name = "one", doc = "one")
public MockClassCommonNameOne getOne() {
return null;
}
@SkylarkCallable(name = "undocumented_module", doc = "undocumented_module")
public MockClassCommonNameUndocumented getUndocumented() {
return null;
}
}
@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"));
}
@Test
public void testSkylarkCallableParameters() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassD.class);
assertThat(objects).hasSize(1);
assertThat(objects).containsKey("MockClassD");
SkylarkModuleDoc moduleDoc = objects.get("MockClassD");
assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassD");
assertThat(moduleDoc.getMethods()).hasSize(1);
SkylarkMethodDoc 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 testSkylarkCallableParametersAndArgs() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassF.class);
assertThat(objects).hasSize(1);
assertThat(objects).containsKey("MockClassF");
SkylarkModuleDoc moduleDoc = objects.get("MockClassF");
assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassF");
assertThat(moduleDoc.getMethods()).hasSize(1);
SkylarkMethodDoc 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 testSkylarkCallableParametersAndKwargs() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassG.class);
assertThat(objects).hasSize(1);
assertThat(objects).containsKey("MockClassG");
SkylarkModuleDoc moduleDoc = objects.get("MockClassG");
assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassG");
assertThat(moduleDoc.getMethods()).hasSize(1);
SkylarkMethodDoc 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 testSkylarkCallableParametersAndArgsAndKwargs() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassH.class);
assertThat(objects).hasSize(1);
assertThat(objects).containsKey("MockClassH");
SkylarkModuleDoc moduleDoc = objects.get("MockClassH");
assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassH");
assertThat(moduleDoc.getMethods()).hasSize(1);
SkylarkMethodDoc 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 testSkylarkGlobalLibraryCallable() throws Exception {
Map<String, SkylarkModuleDoc> modules = SkylarkDocumentationCollector.collectModules();
SkylarkModuleDoc topLevel =
modules.remove(SkylarkDocumentationCollector.getTopLevelModule().name());
boolean foundGlobalLibrary = false;
for (SkylarkMethodDoc 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 testSkylarkCallableOverriding() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassE.class);
assertThat(objects).hasSize(1);
assertThat(objects).containsKey("MockClassE");
SkylarkModuleDoc moduleDoc = objects.get("MockClassE");
assertThat(moduleDoc.getDocumentation()).isEqualTo("MockClassE");
assertThat(moduleDoc.getMethods()).hasSize(1);
SkylarkMethodDoc 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 testSkylarkContainerReturnTypesWithoutAnnotations() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(MockClassWithContainerReturnValues.class);
assertThat(objects).containsKey("MockClassWithContainerReturnValues");
Collection<SkylarkMethodDoc> 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=\"list.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.skylark()");
}
@Test
public void testDocumentedModuleTakesPrecedence() throws Exception {
Map<String, SkylarkModuleDoc> objects = collect(PointsToCommonNameAndUndocumentedModule.class);
Collection<SkylarkMethodDoc> 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, SkylarkModuleDoc> objects = collect(PointsToCommonNameOneWithSubclass.class);
Collection<SkylarkMethodDoc> methods =
objects.get("MockClassCommonName").getMethods();
List<String> methodNames =
methods.stream().map(m -> m.getName()).collect(Collectors.toList());
assertThat(methodNames).containsExactly("one", "two");
}
private Iterable<Method> extractMethods(Collection<SkylarkMethodDoc> methods) {
return methods.stream()
.filter(methodDoc -> methodDoc instanceof SkylarkJavaMethodDoc)
.map(methodDoc -> ((SkylarkJavaMethodDoc) methodDoc).getMethod())
.collect(Collectors.toList());
}
private Map<String, SkylarkModuleDoc> collect(Class<?> classObject) {
Map<String, SkylarkModuleDoc> modules = new TreeMap<>();
SkylarkDocumentationCollector.collectJavaObjects(
classObject.getAnnotation(SkylarkModule.class), classObject, modules);
return modules;
}
}