blob: 40deba3f9cbe2662e228fe2f19a0715b8988db13 [file] [log] [blame]
// Copyright 2023 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.rules.starlarkdocextract;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.cmdline.BazelModuleContext;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.cmdline.RepositoryName;
import com.google.devtools.build.lib.skyframe.BzlLoadFunction;
import com.google.devtools.build.lib.starlark.util.BazelEvaluationTestCase;
import com.google.devtools.build.skydoc.rendering.LabelRenderer;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AspectInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeType;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionDeprecationInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionParamInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionReturnInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ModuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.OriginKey;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderFieldInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderNameGroup;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.StarlarkFunctionInfo;
import java.util.Optional;
import java.util.function.Predicate;
import net.starlark.java.eval.Module;
import net.starlark.java.syntax.FileOptions;
import net.starlark.java.syntax.ParserInput;
import net.starlark.java.syntax.Program;
import net.starlark.java.syntax.StarlarkFile;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class ModuleInfoExtractorTest {
private String fakeLabelString = null; // set by exec()
private Module exec(String... lines) throws Exception {
return execWithOptions(ImmutableList.of(), lines);
}
private Module execWithOptions(ImmutableList<String> options, String... lines) throws Exception {
BazelEvaluationTestCase ev = new BazelEvaluationTestCase();
ev.setSemantics(options.toArray(new String[0]));
Module module = ev.getModule();
Label fakeLabel = BazelModuleContext.of(module).label();
fakeLabelString = fakeLabel.getCanonicalForm();
ParserInput input = ParserInput.fromLines(lines);
StarlarkFile file = StarlarkFile.parse(input, FileOptions.DEFAULT);
Program program = Program.compileFile(file, module);
BzlLoadFunction.execAndExport(
program, fakeLabel, ev.getEventHandler(), module, ev.getStarlarkThread());
return ev.getModule();
}
private static ModuleInfoExtractor getExtractor() {
RepositoryMapping repositoryMapping = RepositoryMapping.ALWAYS_FALLBACK;
return new ModuleInfoExtractor(
name -> true, new LabelRenderer(repositoryMapping, Optional.empty()));
}
private static ModuleInfoExtractor getExtractor(Predicate<String> isWantedQualifiedName) {
RepositoryMapping repositoryMapping = RepositoryMapping.ALWAYS_FALLBACK;
return new ModuleInfoExtractor(
isWantedQualifiedName, new LabelRenderer(repositoryMapping, Optional.empty()));
}
private static ModuleInfoExtractor getExtractor(
RepositoryMapping repositoryMapping, String mainRepoName) {
return new ModuleInfoExtractor(
name -> true, new LabelRenderer(repositoryMapping, Optional.of(mainRepoName)));
}
@Test
public void moduleDocstring() throws Exception {
Module moduleWithDocstring = exec("'''This is my docstring'''", "foo = 1");
assertThat(getExtractor().extractFrom(moduleWithDocstring).getModuleDocstring())
.isEqualTo("This is my docstring");
Module moduleWithoutDocstring = exec("foo = 1");
assertThat(getExtractor().extractFrom(moduleWithoutDocstring).getModuleDocstring()).isEmpty();
}
@Test
public void extractOnlyWantedLoadablePublicNames() throws Exception {
Module module =
exec(
"def loadable_unwanted():",
" pass",
"def loadable_wanted():",
" pass",
"def _nonloadable():",
" pass",
"def _nonloadable_matches_wanted_predicate():",
" pass",
"def _f():",
" pass",
"def _g():",
" pass",
"def _h():",
" pass",
"namespace = struct(",
" public_field_wanted = _f,",
" public_field_unwanted = _g,",
" _hidden_field_matches_wanted_predicate = _h,",
")");
ModuleInfo moduleInfo = getExtractor(name -> name.contains("_wanted")).extractFrom(module);
assertThat(moduleInfo.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("loadable_wanted", "namespace.public_field_wanted");
}
@Test
public void namespacedEntities() throws Exception {
Module module =
exec(
"def _my_func(**kwargs):",
" pass",
"_my_binary = rule(implementation = _my_func)",
"_my_aspect = aspect(implementation = _my_func)",
"_MyInfo = provider()",
"name = struct(",
" spaced = struct(",
" my_func = _my_func,",
" my_binary = _my_binary,",
" my_aspect = _my_aspect,",
" MyInfo = _MyInfo,",
" ),",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("name.spaced.my_func");
assertThat(
moduleInfo.getFuncInfoList().stream()
.map(StarlarkFunctionInfo::getOriginKey)
.map(OriginKey::getName))
.containsExactly("_my_func");
assertThat(moduleInfo.getRuleInfoList().stream().map(RuleInfo::getRuleName))
.containsExactly("name.spaced.my_binary");
assertThat(
moduleInfo.getRuleInfoList().stream()
.map(RuleInfo::getOriginKey)
.map(OriginKey::getName))
.containsExactly("_my_binary");
assertThat(moduleInfo.getAspectInfoList().stream().map(AspectInfo::getAspectName))
.containsExactly("name.spaced.my_aspect");
assertThat(
moduleInfo.getAspectInfoList().stream()
.map(AspectInfo::getOriginKey)
.map(OriginKey::getName))
.containsExactly("_my_aspect");
assertThat(moduleInfo.getProviderInfoList().stream().map(ProviderInfo::getProviderName))
.containsExactly("name.spaced.MyInfo");
assertThat(
moduleInfo.getProviderInfoList().stream()
.map(ProviderInfo::getOriginKey)
.map(OriginKey::getName))
.containsExactly("_MyInfo");
}
@Test
public void isWantedQualifiedName_appliesToQualifiedNamePrefixes() throws Exception {
Module module =
exec(
"def _f(): pass", //
"def _g(): pass",
"def _h(): pass",
"def _i(): pass",
"def _j(): pass",
"foo = struct(",
" bar = struct(",
" f = _f,",
" ),",
" baz = struct(",
" g = _g,",
" ),",
" h = _h,",
")",
"baz = struct(",
" qux = struct(",
" i = _i,",
" ),",
" j = _j,",
")");
ModuleInfo moduleInfo =
getExtractor(name -> name.equals("foo.bar") || name.equals("baz")).extractFrom(module);
assertThat(moduleInfo.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("foo.bar.f", "baz.qux.i", "baz.j");
}
@Test
public void functionDocstring() throws Exception {
Module module =
exec(
"def with_detailed_docstring():",
" '''My function",
" ",
" This function does things.",
" '''",
" pass",
"def with_one_line_docstring():",
" '''My function'''",
" pass",
"def without_docstring():",
" pass");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getFuncInfoList())
.containsExactly(
StarlarkFunctionInfo.newBuilder()
.setFunctionName("with_detailed_docstring")
.setDocString("My function\n\nThis function does things.")
.setOriginKey(
OriginKey.newBuilder()
.setName("with_detailed_docstring")
.setFile(fakeLabelString))
.build(),
StarlarkFunctionInfo.newBuilder()
.setFunctionName("with_one_line_docstring")
.setDocString("My function")
.setOriginKey(
OriginKey.newBuilder()
.setName("with_one_line_docstring")
.setFile(fakeLabelString))
.build(),
StarlarkFunctionInfo.newBuilder()
.setFunctionName("without_docstring")
.setOriginKey(
OriginKey.newBuilder().setName("without_docstring").setFile(fakeLabelString))
.build());
}
@Test
public void functionParams() throws Exception {
Module module =
exec(
"def my_func(documented, undocumented, has_default = {'foo': 'bar'}, *args, **kwargs):",
" '''My function",
" ",
" Args:",
" documented: Documented param",
" '''",
" pass");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getFuncInfoList().get(0).getParameterList())
.containsExactly(
FunctionParamInfo.newBuilder()
.setName("documented")
.setDocString("Documented param")
.setMandatory(true)
.build(),
FunctionParamInfo.newBuilder().setName("undocumented").setMandatory(true).build(),
FunctionParamInfo.newBuilder()
.setName("has_default")
.setDefaultValue("{\"foo\": \"bar\"}")
.build(),
FunctionParamInfo.newBuilder().setName("args").build(),
FunctionParamInfo.newBuilder().setName("kwargs").build());
}
@Test
public void functionReturn() throws Exception {
Module module =
exec(
"def with_return():",
" '''My doc",
" ",
" Returns:",
" None",
" '''",
" return None",
"def without_return():",
" '''My doc'''",
" pass");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getFuncInfoList())
.ignoringFields(StarlarkFunctionInfo.ORIGIN_KEY_FIELD_NUMBER)
.containsExactly(
StarlarkFunctionInfo.newBuilder()
.setFunctionName("with_return")
.setDocString("My doc")
.setReturn(FunctionReturnInfo.newBuilder().setDocString("None").build())
.build(),
StarlarkFunctionInfo.newBuilder()
.setFunctionName("without_return")
.setDocString("My doc")
.build());
}
@Test
public void functionDeprecated() throws Exception {
Module module =
exec(
"def with_deprecated():",
" '''My doc",
" ",
" Deprecated:",
" This is deprecated",
" '''",
" pass",
"def without_deprecated():",
" '''My doc'''",
" pass");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getFuncInfoList())
.ignoringFields(StarlarkFunctionInfo.ORIGIN_KEY_FIELD_NUMBER)
.containsExactly(
StarlarkFunctionInfo.newBuilder()
.setFunctionName("with_deprecated")
.setDocString("My doc")
.setDeprecated(
FunctionDeprecationInfo.newBuilder().setDocString("This is deprecated").build())
.build(),
StarlarkFunctionInfo.newBuilder()
.setFunctionName("without_deprecated")
.setDocString("My doc")
.build());
}
@Test
public void providerDocstring() throws Exception {
Module module =
exec(
"DocumentedInfo = provider(doc = 'My doc')", //
"UndocumentedInfo = provider()");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getProviderInfoList())
.containsExactly(
ProviderInfo.newBuilder()
.setProviderName("DocumentedInfo")
.setDocString("My doc")
.setOriginKey(
OriginKey.newBuilder().setName("DocumentedInfo").setFile(fakeLabelString))
.build(),
ProviderInfo.newBuilder()
.setProviderName("UndocumentedInfo")
.setOriginKey(
OriginKey.newBuilder().setName("UndocumentedInfo").setFile(fakeLabelString))
.build());
}
@Test
public void providerFields() throws Exception {
Module module =
exec(
// Note fields below are not alphabetized
"DocumentedInfo = provider(fields = {'c': 'C', 'a': 'A', 'b': 'B', '_hidden':"
+ " 'Hidden'})",
"UndocumentedInfo = provider(fields = ['c', 'a', 'b', '_hidden'])");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getProviderInfoList())
.ignoringFields(ProviderInfo.ORIGIN_KEY_FIELD_NUMBER)
.containsExactly(
ProviderInfo.newBuilder()
.setProviderName("DocumentedInfo")
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("c").setDocString("C"))
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("a").setDocString("A"))
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("b").setDocString("B"))
.build(),
ProviderInfo.newBuilder()
.setProviderName("UndocumentedInfo")
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("c"))
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("a"))
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("b"))
.build());
}
@Test
public void providerInit() throws Exception {
Module module =
exec(
"def _my_info_init(x_value, y_value = 0):",
" '''MyInfo constructor",
"",
" Args:",
" x_value: my x value",
" y_value: my y value",
" '''",
" return {'x': x_value, 'y': y_value}",
"",
"_MyInfo, _new_my_info = provider(",
" doc = '''My provider''',",
" fields = ['x', 'y'],",
" init = _my_info_init,",
")",
"",
"namespace = struct(",
" MyInfo = _MyInfo,",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getProviderInfoList())
.containsExactly(
ProviderInfo.newBuilder()
.setProviderName("namespace.MyInfo")
.setDocString("My provider")
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("x"))
.addFieldInfo(ProviderFieldInfo.newBuilder().setName("y"))
.setInit(
StarlarkFunctionInfo.newBuilder()
.setFunctionName("namespace.MyInfo")
.setDocString("MyInfo constructor")
.addParameter(
FunctionParamInfo.newBuilder()
.setName("x_value")
.setDocString("my x value")
.setMandatory(true)
.build())
.addParameter(
FunctionParamInfo.newBuilder()
.setName("y_value")
.setDocString("my y value")
.setDefaultValue("0")
.build())
.setOriginKey(
OriginKey.newBuilder()
.setName("_my_info_init")
.setFile(fakeLabelString)))
.setOriginKey(OriginKey.newBuilder().setName("_MyInfo").setFile(fakeLabelString))
.build());
}
@Test
public void ruleDocstring() throws Exception {
Module module =
exec(
"def _my_impl(ctx):",
" pass",
"documented_lib = rule(doc = 'My doc', implementation = _my_impl)",
"undocumented_lib = rule(implementation = _my_impl)");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList())
.ignoringFields(RuleInfo.ATTRIBUTE_FIELD_NUMBER) // ignore implicit attributes
.containsExactly(
RuleInfo.newBuilder()
.setRuleName("documented_lib")
.setDocString("My doc")
.setOriginKey(
OriginKey.newBuilder().setName("documented_lib").setFile(fakeLabelString))
.build(),
RuleInfo.newBuilder()
.setRuleName("undocumented_lib")
.setOriginKey(
OriginKey.newBuilder().setName("undocumented_lib").setFile(fakeLabelString))
.build());
}
@Test
public void ruleAdvertisedProviders() throws Exception {
Module module =
exec(
"MyInfo = provider()",
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" provides = [MyInfo, DefaultInfo, 'LegacyStructInfo']",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList())
.ignoringFields(RuleInfo.ATTRIBUTE_FIELD_NUMBER) // ignore implicit attributes
.containsExactly(
RuleInfo.newBuilder()
.setRuleName("my_lib")
.setOriginKey(OriginKey.newBuilder().setName("my_lib").setFile(fakeLabelString))
.setAdvertisedProviders(
ProviderNameGroup.newBuilder()
.addProviderName("MyInfo")
.addProviderName("DefaultInfo")
.addProviderName("LegacyStructInfo")
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo").setFile(fakeLabelString))
.addOriginKey(
OriginKey.newBuilder().setName("DefaultInfo").setFile("<native>"))
.addOriginKey(OriginKey.newBuilder().setName("LegacyStructInfo")))
.build());
}
@Test
public void ruleTest() throws Exception {
Module module =
exec(
"MyInfo = provider()",
"def _my_impl(ctx):",
" pass",
"my_test = rule(",
" implementation = _my_impl,",
" test = True",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList())
.ignoringFields(RuleInfo.ATTRIBUTE_FIELD_NUMBER) // ignore implicit attributes
.containsExactly(
RuleInfo.newBuilder()
.setRuleName("my_test")
.setOriginKey(OriginKey.newBuilder().setName("my_test").setFile(fakeLabelString))
.setTest(true)
.setExecutable(true)
.build());
}
@Test
public void ruleExecutable() throws Exception {
Module module =
exec(
"MyInfo = provider()",
"def _my_impl(ctx):",
" pass",
"my_binary = rule(",
" implementation = _my_impl,",
" executable = True",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList())
.ignoringFields(RuleInfo.ATTRIBUTE_FIELD_NUMBER) // ignore implicit attributes
.containsExactly(
RuleInfo.newBuilder()
.setRuleName("my_binary")
.setOriginKey(OriginKey.newBuilder().setName("my_binary").setFile(fakeLabelString))
.setExecutable(true)
.build());
}
@Test
public void ruleAttributes() throws Exception {
Module module =
execWithOptions(
// TODO(https://github.com/bazelbuild/bazel/issues/6420): attr.license() is deprecated,
// and will eventually be removed from Bazel.
ImmutableList.of("--noincompatible_no_attr_license"),
"MyInfo1 = provider()",
"MyInfo2 = provider()",
"MyInfo3 = provider()",
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" attrs = {",
" 'a': attr.string(doc = 'My doc', default = 'foo'),",
" 'b': attr.string(mandatory = True),",
" 'c': attr.label(providers = [MyInfo1, MyInfo2]),",
" 'd': attr.label(providers = [[MyInfo1, MyInfo2], [MyInfo3]]),",
" '_e': attr.string(doc = 'Hidden attribute'),",
" 'deprecated_license': attr.license(),",
" }",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList().get(0).getAttributeList())
.containsExactly(
ModuleInfoExtractor.IMPLICIT_NAME_ATTRIBUTE_INFO,
AttributeInfo.newBuilder()
.setName("a")
.setType(AttributeType.STRING)
.setDocString("My doc")
.setDefaultValue("\"foo\"")
.build(),
AttributeInfo.newBuilder()
.setName("b")
.setType(AttributeType.STRING)
.setMandatory(true)
.build(),
AttributeInfo.newBuilder()
.setName("c")
.setType(AttributeType.LABEL)
.setDefaultValue("None")
.addProviderNameGroup(
ProviderNameGroup.newBuilder()
.addProviderName("MyInfo1")
.addProviderName("MyInfo2")
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo1").setFile(fakeLabelString))
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo2").setFile(fakeLabelString)))
.build(),
AttributeInfo.newBuilder()
.setName("d")
.setType(AttributeType.LABEL)
.setDefaultValue("None")
.addProviderNameGroup(
ProviderNameGroup.newBuilder()
.addProviderName("MyInfo1")
.addProviderName("MyInfo2")
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo1").setFile(fakeLabelString))
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo2").setFile(fakeLabelString)))
.addProviderNameGroup(
ProviderNameGroup.newBuilder()
.addProviderName("MyInfo3")
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo3").setFile(fakeLabelString)))
.build(),
AttributeInfo.newBuilder()
.setName("deprecated_license")
.setType(AttributeType.STRING_LIST)
.setDefaultValue("[\"none\"]")
.setNonconfigurable(true)
.build());
}
@Test
public void attributeOrder() throws Exception {
Module module =
exec(
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" attrs = {",
" 'foo': attr.int(),",
" 'bar': attr.int(),",
" 'baz': attr.int(),",
" }",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(
moduleInfo.getRuleInfoList().get(0).getAttributeList().stream()
.map(AttributeInfo::getName))
.containsExactly("name", "foo", "bar", "baz")
.inOrder();
}
@Test
public void attributeTypes() throws Exception {
Module module =
exec(
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" attrs = {",
" 'a': attr.int(),",
" 'b': attr.label(),",
" 'c': attr.string(),",
" 'd': attr.string_list(),",
" 'e': attr.int_list(),",
" 'f': attr.label_list(),",
" 'g': attr.bool(),",
" 'h': attr.label_keyed_string_dict(),",
" 'i': attr.string_dict(),",
" 'j': attr.string_list_dict(),",
" 'k': attr.output(),",
" 'l': attr.output_list(),",
" }",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList().get(0).getAttributeList())
.containsExactly(
ModuleInfoExtractor.IMPLICIT_NAME_ATTRIBUTE_INFO,
AttributeInfo.newBuilder()
.setName("a")
.setType(AttributeType.INT)
.setDefaultValue("0")
.build(),
AttributeInfo.newBuilder()
.setName("b")
.setType(AttributeType.LABEL)
.setDefaultValue("None")
.build(),
AttributeInfo.newBuilder()
.setName("c")
.setType(AttributeType.STRING)
.setDefaultValue("\"\"")
.build(),
AttributeInfo.newBuilder()
.setName("d")
.setType(AttributeType.STRING_LIST)
.setDefaultValue("[]")
.build(),
AttributeInfo.newBuilder()
.setName("e")
.setType(AttributeType.INT_LIST)
.setDefaultValue("[]")
.build(),
AttributeInfo.newBuilder()
.setName("f")
.setType(AttributeType.LABEL_LIST)
.setDefaultValue("[]")
.build(),
AttributeInfo.newBuilder()
.setName("g")
.setType(AttributeType.BOOLEAN)
.setDefaultValue("False")
.build(),
AttributeInfo.newBuilder()
.setName("h")
.setType(AttributeType.LABEL_STRING_DICT)
.setDefaultValue("{}")
.build(),
AttributeInfo.newBuilder()
.setName("i")
.setType(AttributeType.STRING_DICT)
.setDefaultValue("{}")
.build(),
AttributeInfo.newBuilder()
.setName("j")
.setType(AttributeType.STRING_LIST_DICT)
.setDefaultValue("{}")
.build(),
AttributeInfo.newBuilder()
.setName("k")
.setType(AttributeType.OUTPUT)
.setDefaultValue("None")
.setNonconfigurable(true)
.build(),
AttributeInfo.newBuilder()
.setName("l")
.setType(AttributeType.OUTPUT_LIST)
.setDefaultValue("[]")
.setNonconfigurable(true)
.build());
}
@Test
public void providerNameGroups_useFirstDocumentableProviderName() throws Exception {
Module module =
exec(
"_MyInfo = provider()",
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" attrs = {",
" 'foo': attr.label(providers = [_MyInfo]),",
" },",
" provides = [_MyInfo],",
")",
"namespace1 = struct(_MyUndocumentedInfo = _MyInfo)",
"namespace2 = struct(MyInfoB = _MyInfo, MyInfoA = _MyInfo)",
"namespace3 = struct(MyInfo = _MyInfo)");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getRuleInfoList().get(0).getAdvertisedProviders().getProviderName(0))
// Struct fields are extracted in field name alphabetical order, so namespace2.MyInfoA
// (despite being declared after namespace2.MyInfoB) wins.
.isEqualTo("namespace2.MyInfoA");
assertThat(
moduleInfo
.getRuleInfoList()
.get(0)
.getAttribute(1) // 0 is the implicit name attribute
.getProviderNameGroup(0)
.getProviderName(0))
.isEqualTo("namespace2.MyInfoA");
assertThat(moduleInfo.getProviderInfoList().stream().map(ProviderInfo::getProviderName))
.containsExactly("namespace2.MyInfoA", "namespace2.MyInfoB", "namespace3.MyInfo");
// TODO(arostovtsev): instead of producing a separate ProviderInfo message per each alias, add a
// repeated alias name field, and produce a single ProviderInfo message listing its aliases.
}
@Test
public void labelStringification() throws Exception {
Module module =
exec(
"def _my_impl(ctx):",
" pass",
"my_lib = rule(",
" implementation = _my_impl,",
" attrs = {",
" 'label': attr.label(default = '//test:foo'),",
" 'label_list': attr.label_list(",
" default = ['//x', '@canonical//y', '@canonical//y:z'],",
" ),",
" 'label_keyed_string_dict': attr.label_keyed_string_dict(",
" default = {'//x': 'label_in_main', '@canonical//y': 'label_in_dep'}",
" ),",
" }",
")");
RepositoryName canonicalName = RepositoryName.create("canonical");
RepositoryMapping repositoryMapping =
RepositoryMapping.create(ImmutableMap.of("local", canonicalName), RepositoryName.MAIN);
ModuleInfo moduleInfo = getExtractor(repositoryMapping, "my_repo").extractFrom(module);
assertThat(
moduleInfo.getRuleInfoList().get(0).getAttributeList().stream()
.filter(attr -> !attr.equals(ModuleInfoExtractor.IMPLICIT_NAME_ATTRIBUTE_INFO))
.map(AttributeInfo::getDefaultValue))
.containsExactly(
"\"@my_repo//test:foo\"",
"[\"@my_repo//x\", \"@local//y\", \"@local//y:z\"]",
"{\"@my_repo//x\": \"label_in_main\", \"@local//y\": \"label_in_dep\"}");
}
@Test
public void aspectDocstring() throws Exception {
Module module =
exec(
"def _my_impl(target, ctx):",
" pass",
"documented_aspect = aspect(doc = 'My doc', implementation = _my_impl)",
"undocumented_aspect = aspect(implementation = _my_impl)");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getAspectInfoList())
.ignoringFields(AspectInfo.ATTRIBUTE_FIELD_NUMBER) // ignore implicit attributes
.containsExactly(
AspectInfo.newBuilder()
.setAspectName("documented_aspect")
.setDocString("My doc")
.setOriginKey(
OriginKey.newBuilder().setName("documented_aspect").setFile(fakeLabelString))
.build(),
AspectInfo.newBuilder()
.setAspectName("undocumented_aspect")
.setOriginKey(
OriginKey.newBuilder().setName("undocumented_aspect").setFile(fakeLabelString))
.build());
}
@Test
public void aspectAttributes() throws Exception {
Module module =
exec(
"def _my_impl(target, ctx):",
" pass",
"my_aspect = aspect(",
" implementation = _my_impl,",
" attr_aspects = ['deps', 'srcs', '_private'],",
" attrs = {",
" 'a': attr.string(doc = 'My doc', default = 'foo'),",
" 'b': attr.string(mandatory = True),",
" '_c': attr.string(doc = 'Hidden attribute'),",
" }",
")");
ModuleInfo moduleInfo = getExtractor().extractFrom(module);
assertThat(moduleInfo.getAspectInfoList())
.containsExactly(
AspectInfo.newBuilder()
.setAspectName("my_aspect")
.setOriginKey(OriginKey.newBuilder().setName("my_aspect").setFile(fakeLabelString))
.addAspectAttribute("deps")
.addAspectAttribute("srcs")
.addAttribute(ModuleInfoExtractor.IMPLICIT_NAME_ATTRIBUTE_INFO)
.addAttribute(
AttributeInfo.newBuilder()
.setName("a")
.setType(AttributeType.STRING)
.setDocString("My doc")
.setDefaultValue("\"foo\""))
.addAttribute(
AttributeInfo.newBuilder()
.setName("b")
.setType(AttributeType.STRING)
.setMandatory(true)
.build())
.build());
}
}