blob: 230ee4e9ab323073cdda82e5e111fbbeff2a5b34 [file] [log] [blame]
# Copyright 2016 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.
import os
import tempfile
import textwrap
import unittest
# internal imports
from google.protobuf import text_format
from skydoc import build_pb2
from skydoc import load_extractor
from skydoc import rule_extractor
class RuleExtractorTest(unittest.TestCase):
def test_create_stubs(self):
stubs = {
'foo': 'bar',
'bar': 'baz',
}
load_symbols = [
load_extractor.LoadSymbol('//foo:bar.bzl', 'bar_library', None),
load_extractor.LoadSymbol('//foo:bar.bzl', 'foo_bar_binary',
'bar_binary'),
]
expected = {
'foo': 'bar',
'bar': 'baz',
'bar_library': '',
'bar_binary': '',
}
self.assertEqual(expected, rule_extractor.create_stubs(stubs, load_symbols))
def check_protos(self, src, expected, load_symbols=[]):
tf = tempfile.NamedTemporaryFile(mode='w+', delete=False)
tf.write(src)
tf.flush()
tf.close()
expected_proto = build_pb2.BuildLanguage()
text_format.Merge(expected, expected_proto)
extractor = rule_extractor.RuleDocExtractor()
extractor.parse_bzl(tf.name, load_symbols)
os.remove(tf.name)
proto = extractor.proto()
self.assertEqual(expected_proto, proto)
def test_all_types(self):
src = textwrap.dedent("""\
def impl(ctx):
return struct()
all_types = rule(
implementation = impl,
attrs = {
"arg_bool": attr.bool(default=True),
"arg_int": attr.int(default=10),
"arg_int_list": attr.int_list(default=[1, 2]),
"arg_label": attr.label(
default=Label("//foo:bar",
relative_to_caller_repository=True)),
"arg_label_list": attr.label_list(
default=[Label("//foo:bar"), Label("//bar:baz")]),
"arg_license": attr.license(),
"arg_output": attr.output(default=Label("//foo:bar")),
"arg_output_list": attr.output_list(
default=[Label("//foo:bar"), Label("//bar:baz")]),
"arg_string": attr.string(default="foo"),
"arg_string_dict": attr.string_dict(default={"foo": "bar"}),
"arg_string_list": attr.string_list(default=["foo", "bar"]),
"arg_string_list_dict": attr.string_list_dict(
default={"foo": ["bar", "baz"]}),
},
)
\"\"\"Test rule with all types.
Args:
name: A unique name for this rule.
arg_bool: A boolean argument.
arg_int: An integer argument.
arg_int_list: A list of integers argument.
arg_label: A label argument.
arg_label_list: A list of labels argument.
arg_license: A license argument.
arg_output: An output argument.
arg_output_list: A list of outputs argument.
arg_string: A string argument.
arg_string_dict: A dictionary mapping string to string argument.
arg_string_list: A list of strings argument.
arg_string_list_dict: A dictionary mapping string to list of string argument.
\"\"\"
""")
expected = textwrap.dedent("""
rule {
name: "all_types"
documentation: "Test rule with all types."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_bool"
type: BOOLEAN
mandatory: false
documentation: "A boolean argument."
default: "True"
}
attribute {
name: "arg_int"
type: INTEGER
mandatory: false
documentation: "An integer argument."
default: "10"
}
attribute {
name: "arg_int_list"
type: INTEGER_LIST
mandatory: false
documentation: "A list of integers argument."
default: "[1, 2]"
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument."
default: "//foo:bar"
}
attribute {
name: "arg_label_list"
type: LABEL_LIST
mandatory: false
documentation: "A list of labels argument."
default: "[\'//foo:bar\', \'//bar:baz\']"
}
attribute {
name: "arg_license"
type: LICENSE
mandatory: false
documentation: "A license argument."
}
attribute {
name: "arg_output"
type: OUTPUT
mandatory: false
documentation: "An output argument."
default: "//foo:bar"
}
attribute {
name: "arg_output_list"
type: OUTPUT_LIST
mandatory: false
documentation: "A list of outputs argument."
default: "[\'//foo:bar\', \'//bar:baz\']"
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
documentation: "A string argument."
default: "\'foo\'"
}
attribute {
name: "arg_string_dict"
type: STRING_DICT
mandatory: false
documentation: "A dictionary mapping string to string argument."
default: "{\'foo\': \'bar\'}"
}
attribute {
name: "arg_string_list"
type: STRING_LIST
mandatory: false
documentation: "A list of strings argument."
default: "[\'foo\', \'bar\']"
}
attribute {
name: "arg_string_list_dict"
type: STRING_LIST_DICT
mandatory: false
documentation: "A dictionary mapping string to list of string argument."
default: "{\'foo\': [\'bar\', \'baz\']}"
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_undocumented(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
undocumented = rule(
implementation = _impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
)
""")
expected = textwrap.dedent("""\
rule {
name: "undocumented"
attribute {
name: "name"
type: UNKNOWN
mandatory: true
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
default: "''"
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_private_rules_skipped(self):
src = textwrap.dedent("""\
def _private_impl(ctx):
return struct()
def _public_impl(ctx):
return struct()
_private = rule(
implementation = _private_impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
)
\"\"\"A private rule that should not appear in documentation.
Args:
name: A unique name for this rule.
arg_label: A label argument.
arg_string: A string argument.
\"\"\"
public = rule(
implementation = _public_impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
)
\"\"\"A public rule that should appear in documentation.
Args:
name: A unique name for this rule.
arg_label: A label argument.
arg_string: A string argument.
\"\"\"
""")
expected = textwrap.dedent("""
rule {
name: "public"
documentation: "A public rule that should appear in documentation."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument."
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
documentation: "A string argument."
default: "''"
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_rule_with_generated_impl(self):
src = textwrap.dedent("""\
def real_impl(ctx):
return struct()
def macro():
return real_impl
impl = macro()
thisrule = rule(
implementation = impl,
)
""")
expected = textwrap.dedent("""\
rule {
name: "thisrule"
attribute {
name: "name"
type: UNKNOWN
mandatory: true
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_multi_line_description(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
multiline = rule(
implementation = _impl,
attrs = {
"arg_bool": attr.bool(),
"arg_label": attr.label(),
},
)
\"\"\"A rule with multiline documentation.
Some more documentation about this rule here.
Args:
name: A unique name for this rule.
arg_bool: A boolean argument.
Documentation for arg_bool continued here.
arg_label: A label argument.
Documentation for arg_label continued here.
\"\"\"
""")
expected = textwrap.dedent("""\
rule {
name: "multiline"
documentation: "A rule with multiline documentation.\\n\\nSome more documentation about this rule here."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_bool"
type: BOOLEAN
mandatory: false
documentation: "A boolean argument.\\n\\nDocumentation for arg_bool continued here."
default: "False"
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument.\\n\\nDocumentation for arg_label continued here."
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_rule_macro_mix(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
example_rule = rule(
implementation = _impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
)
\"\"\"An example rule.
Args:
name: A unique name for this rule.
arg_label: A label argument.
arg_string: A string argument.
\"\"\"
def example_macro(name, foo, visibility=None):
\"\"\"An example macro.
Args:
name: A unique name for this rule.
foo: A test argument.
visibility: The visibility of this rule.
\"\"\"
native.genrule(
name = name,
out = ["foo"],
cmd = "touch $@",
visibility = visibility,
)
""")
expected = textwrap.dedent("""\
rule {
name: "example_rule"
documentation: "An example rule."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument."
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
documentation: "A string argument."
default: "''"
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_rule_with_example_doc(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
rule_with_example = rule(
implementation = _impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
)
\"\"\"Rule with example.
Example:
Here is an example of how to use this rule.
Args:
name: A unique name for this rule.
arg_label: A label argument.
arg_string: A string argument.
\"\"\"
""")
expected = textwrap.dedent("""\
rule {
name: "rule_with_example"
documentation: "Rule with example."
example_documentation: "Here is an example of how to use this rule."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument."
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
documentation: "A string argument."
default: "''"
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_rule_with_output_doc(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
rule_with_outputs = rule(
implementation = _impl,
attrs = {
"arg_label": attr.label(),
"arg_string": attr.string(),
},
outputs = {
"jar": "%{name}.jar",
"deploy_jar": "%{name}_deploy.jar",
},
)
\"\"\"Rule with output documentation.
Outputs:
jar: A Java archive.
deploy_jar: A Java archive suitable for deployment.
Only built if explicitly requested.
Args:
name: A unique name for this rule.
arg_label: A label argument.
arg_string: A string argument.
\"\"\"
""")
expected = textwrap.dedent("""\
rule {
name: "rule_with_outputs"
documentation: "Rule with output documentation."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "arg_label"
type: LABEL
mandatory: false
documentation: "A label argument."
}
attribute {
name: "arg_string"
type: STRING
mandatory: false
documentation: "A string argument."
default: "''"
}
output {
template: "%{name}.jar"
documentation: "A Java archive."
}
output {
template: "%{name}_deploy.jar"
documentation: "A Java archive suitable for deployment.\\n\\nOnly built if explicitly requested."
}
type: RULE
}
""")
self.check_protos(src, expected)
def test_loads(self):
src = textwrap.dedent("""\
load("//foo/bar:bar.bzl", "foo_library")
load("//foo/bar:baz.bzl", "foo_test", orig_foo_binary = "foo_binary")
_references_foo_library = foo_library
_references_orig_foo_binary = orig_foo_binary
""")
load_symbols = [
load_extractor.LoadSymbol('//foo/bar:bar.bzl', 'foo_library', None),
load_extractor.LoadSymbol('//foo/bar:baz.bzl', 'foo_test', None),
load_extractor.LoadSymbol('//foo/bar:baz.bzl', 'foo_binary',
'orig_foo_binary'),
]
expected = ""
self.check_protos(src, expected, load_symbols)
def test_repository_rule(self):
src = textwrap.dedent("""\
def _impl(repository_ctx):
return struct()
repo_rule = repository_rule(
implementation = _impl,
local = True,
attrs = {
"path": attr.string(mandatory=True)
},
)
\"\"\"A repository rule.
Args:
name: A unique name for this rule.
path: The path of the external dependency.
\"\"\"
""")
expected = textwrap.dedent("""\
rule {
name: "repo_rule"
documentation: "A repository rule."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
documentation: "A unique name for this rule."
}
attribute {
name: "path"
type: STRING
mandatory: true
documentation: "The path of the external dependency."
default: "\'\'"
}
type: REPOSITORY_RULE
}
""")
self.check_protos(src, expected)
def test_doc_arg(self):
src = textwrap.dedent("""\
def _impl(ctx):
return struct()
rule_with_doc = rule(
implementation = _impl,
attrs = {
"foo": attr.string(doc = "Attribute documentation.")
},
)
\"\"\"A rule.
\"\"\"
""")
expected = textwrap.dedent("""\
rule {
name: "rule_with_doc"
documentation: "A rule."
attribute {
name: "name"
type: UNKNOWN
mandatory: true
}
attribute {
name: "foo"
type: STRING
mandatory: false
documentation: "Attribute documentation."
default: "\'\'"
}
type: RULE
}
""")
self.check_protos(src, expected)
if __name__ == '__main__':
unittest.main()