blob: 362fcf67d38b4ac42328bc68ce6b0f5a0939a482 [file] [log] [blame]
# Copyright 2023 The Pigweed Authors
#
# 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
#
# https://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.
"""Tests for the pw_build.bazel_query module."""
import json
import unittest
from unittest import mock
from tempfile import TemporaryDirectory
from pw_build.bazel_query import ParseError, BazelRule, BazelWorkspace
class TestBazelRule(unittest.TestCase):
"""Tests for bazel_query.Rule."""
def test_rule_top_level(self):
"""Tests a top-level rule with no package name."""
rule = BazelRule('//:no-package', 'custom-type')
self.assertEqual(rule.package(), '')
def test_rule_with_label(self):
"""Tests a rule with a package and target name."""
rule = BazelRule('//foo:target', 'custom-type')
self.assertEqual(rule.package(), 'foo')
self.assertEqual(rule.label(), '//foo:target')
def test_rule_in_subdirectory(self):
"""Tests a rule in a subdirectory."""
rule = BazelRule('//foo:bar/target', 'custom-type')
self.assertEqual(rule.package(), 'foo')
self.assertEqual(rule.label(), '//foo:bar/target')
def test_rule_in_subpackage(self):
"""Tests a rule in a subpackage."""
rule = BazelRule('//foo/bar:target', 'custom-type')
self.assertEqual(rule.package(), 'foo/bar')
self.assertEqual(rule.label(), '//foo/bar:target')
def test_rule_no_target(self):
"""Tests a rule with only a package name."""
rule = BazelRule('//foo/bar', 'custom-type')
self.assertEqual(rule.package(), 'foo/bar')
self.assertEqual(rule.label(), '//foo/bar:bar')
def test_rule_invalid_relative(self):
"""Tests a rule with an invalid (non-absolute) package name."""
with self.assertRaises(ParseError):
BazelRule('../foo/bar:target', 'custom-type')
def test_rule_invalid_double_colon(self):
"""Tests a rule with an invalid (non-absolute) package name."""
with self.assertRaises(ParseError):
BazelRule('//foo:bar:target', 'custom-type')
def test_rule_parse_invalid(self):
"""Test for parsing invalid rule attributes."""
rule = BazelRule('//package:target', 'kind')
with self.assertRaises(ParseError):
rule.parse(
json.loads(
'''[{
"name": "invalid_attr",
"type": "ESOTERIC",
"intValue": 0,
"stringValue": "false",
"explicitlySpecified": true,
"booleanValue": false
}]'''
)
)
def test_rule_parse_boolean_unspecified(self):
"""Test parsing an unset boolean rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "bool_attr",
"type": "BOOLEAN",
"intValue": 0,
"stringValue": "false",
"explicitlySpecified": false,
"booleanValue": false
}]'''
)
)
self.assertFalse(rule.has_attr('bool_attr'))
def test_rule_parse_boolean_false(self):
"""Tests parsing boolean rule attribute set to false."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "bool_attr",
"type": "BOOLEAN",
"intValue": 0,
"stringValue": "false",
"explicitlySpecified": true,
"booleanValue": false
}]'''
)
)
self.assertTrue(rule.has_attr('bool_attr'))
self.assertFalse(rule.get_bool('bool_attr'))
def test_rule_parse_boolean_true(self):
"""Tests parsing a boolean rule attribute set to true."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "bool_attr",
"type": "BOOLEAN",
"intValue": 1,
"stringValue": "true",
"explicitlySpecified": true,
"booleanValue": true
}]'''
)
)
self.assertTrue(rule.has_attr('bool_attr'))
self.assertTrue(rule.get_bool('bool_attr'))
def test_rule_parse_integer_unspecified(self):
"""Tests parsing an unset integer rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "int_attr",
"type": "INTEGER",
"intValue": 0,
"explicitlySpecified": false
}]'''
)
)
self.assertFalse(rule.has_attr('int_attr'))
def test_rule_parse_integer(self):
"""Tests parsing an integer rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "int_attr",
"type": "INTEGER",
"intValue": 100,
"explicitlySpecified": true
}]'''
)
)
self.assertTrue(rule.has_attr('int_attr'))
self.assertEqual(rule.get_int('int_attr'), 100)
def test_rule_parse_string_unspecified(self):
"""Tests parsing an unset string rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_attr",
"type": "STRING",
"stringValue": "",
"explicitlySpecified": false
}]'''
)
)
self.assertFalse(rule.has_attr('string_attr'))
def test_rule_parse_string(self):
"""Tests parsing a string rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_attr",
"type": "STRING",
"stringValue": "hello, world!",
"explicitlySpecified": true
}]'''
)
)
self.assertTrue(rule.has_attr('string_attr'))
self.assertEqual(rule.get_str('string_attr'), 'hello, world!')
def test_rule_parse_string_list_unspecified(self):
"""Tests parsing an unset string list rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_list_attr",
"type": "STRING_LIST",
"stringListValue": [],
"explicitlySpecified": false
}]'''
)
)
self.assertFalse(rule.has_attr('string_list_attr'))
def test_rule_parse_string_list(self):
"""Tests parsing a string list rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_list_attr",
"type": "STRING_LIST",
"stringListValue": [ "hello", "world!" ],
"explicitlySpecified": true
}]'''
)
)
self.assertTrue(rule.has_attr('string_list_attr'))
self.assertEqual(rule.get_list('string_list_attr'), ['hello', 'world!'])
def test_rule_parse_label_list_unspecified(self):
"""Tests parsing an unset label list rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "label_list_attr",
"type": "LABEL_LIST",
"stringListValue": [],
"explicitlySpecified": false
}]'''
)
)
self.assertFalse(rule.has_attr('label_list_attr'))
def test_rule_parse_label_list(self):
"""Tests parsing a label list rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "label_list_attr",
"type": "LABEL_LIST",
"stringListValue": [ "hello", "world!" ],
"explicitlySpecified": true
}]'''
)
)
self.assertTrue(rule.has_attr('label_list_attr'))
self.assertEqual(rule.get_list('label_list_attr'), ['hello', 'world!'])
def test_rule_parse_string_dict_unspecified(self):
"""Tests parsing an unset string dict rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_dict_attr",
"type": "LABEL_LIST",
"stringDictValue": [],
"explicitlySpecified": false
}]'''
)
)
self.assertFalse(rule.has_attr('string_dict_attr'))
def test_rule_parse_string_dict(self):
"""Tests parsing a string dict rule attribute."""
rule = BazelRule('//package:target', 'kind')
rule.parse(
json.loads(
'''[{
"name": "string_dict_attr",
"type": "STRING_DICT",
"stringDictValue": [
{
"key": "foo",
"value": "hello"
},
{
"key": "bar",
"value": "world"
}
],
"explicitlySpecified": true
}]'''
)
)
string_dict_attr = rule.get_dict('string_dict_attr')
self.assertTrue(rule.has_attr('string_dict_attr'))
self.assertEqual(string_dict_attr['foo'], 'hello')
self.assertEqual(string_dict_attr['bar'], 'world')
class TestWorkspace(unittest.TestCase):
"""Test for bazel_query.Workspace."""
@mock.patch('subprocess.run')
def test_workspace_get_rules(self, mock_run):
"""Tests querying a workspace for Bazel rules."""
attrs = []
# `bazel query //... --output=package
attrs.append(
{
'stdout.decode.return_value': '''
foo/pkg1
bar/pkg2'''
}
)
# bazel query buildfiles(//foo:*) --output=xml
attrs.append(
{
'stdout.decode.return_value': '''
<query version="2">
<source-file name="//foo/pkg1:BUILD.bazel">
<visibility-label name="//visibility:public"/>
</source-file>
</query>'''
}
)
# bazel query buildfiles(//bar:*) --output=xml
attrs.append(
{
'stdout.decode.return_value': '''
<query version="2">
<source-file name="//bar/pkg2:BUILD.bazel">
<visibility-label name="//visibility:private"/>
</source-file>
</query>'''
}
)
# bazel cquery kind(some_kind, //...) --output=jsonproto
attrs.append(
{
'stdout.decode.return_value': '''
{
"results": [
{
"target": {
"type": "RULE",
"rule": {
"name": "//foo/pkg1:rule1",
"ruleClass": "some_kind",
"attribute": []
}
}
},
{
"target": {
"type": "RULE",
"rule": {
"name": "//bar/pkg2:rule2",
"ruleClass": "some_kind",
"attribute": []
}
}
}
]
}'''
}
)
mock_run.side_effect = [mock.MagicMock(**attr) for attr in attrs]
with TemporaryDirectory() as tmp:
workspace = BazelWorkspace(tmp)
rules = list(workspace.get_rules('some_kind'))
actual = [r.label() for r in rules]
self.assertEqual(actual, ['//foo/pkg1:rule1', '//bar/pkg2:rule2'])
@mock.patch('subprocess.run')
def test_revision(self, mock_run):
"""Tests writing an OWNERS file."""
attrs = {'stdout.decode.return_value': 'fake-hash'}
mock_run.return_value = mock.MagicMock(**attrs)
with TemporaryDirectory() as tmp:
workspace = BazelWorkspace(tmp)
self.assertEqual(workspace.revision(), 'fake-hash')
args, kwargs = mock_run.call_args
self.assertEqual(*args, ['git', 'rev-parse', 'HEAD'])
self.assertEqual(kwargs['cwd'], tmp)
@mock.patch('subprocess.run')
def test_url(self, mock_run):
"""Tests writing an OWNERS file."""
attrs = {'stdout.decode.return_value': 'https://repohub.com/repo.git'}
mock_run.return_value = mock.MagicMock(**attrs)
with TemporaryDirectory() as tmp:
workspace = BazelWorkspace(tmp)
self.assertEqual(workspace.url(), 'https://repohub.com/repo.git')
args, kwargs = mock_run.call_args
self.assertEqual(*args, ['git', 'remote', 'get-url', 'origin'])
self.assertEqual(kwargs['cwd'], tmp)
if __name__ == '__main__':
unittest.main()