blob: da290e6e86f45804d97fc971b9c421b9076f4968 [file]
# Copyright 2025 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 workflow validator."""
import unittest
from pw_build.workflows.build_driver import BuildDriver
from pw_build.workflows.private import validator
from pw_build.proto import workflows_pb2, build_driver_pb2
class NopBuildDriver(BuildDriver):
def generate_action_sequence(
self, job: build_driver_pb2.JobRequest
) -> build_driver_pb2.JobResponse:
return build_driver_pb2.JobResponse()
class ValidatorTest(unittest.TestCase):
"""Tests for the Validator class."""
def test_check_fragment_has_unique_name(self):
"""Test that duplicate names are detected."""
workflow_suite = workflows_pb2.WorkflowSuite()
workflow_suite.builds.add(name='same-name')
workflow_suite.tools.add(name='same-name')
with self.assertRaises(validator.ValidationError):
val = validator.Validator(workflow_suite, {})
val.check_fragment_has_unique_name(workflow_suite.builds[0])
def test_check_fragment_has_name(self):
"""Test that all fragments are named."""
workflow_suite = workflows_pb2.WorkflowSuite()
workflow_suite.tools.add()
with self.assertRaises(validator.ValidationError):
val = validator.Validator(workflow_suite, {})
val.check_fragment_has_name(workflow_suite.tools[0])
def test_check_config_references_real_build_type(self):
"""Test that configs reference real build types."""
workflow_suite = workflows_pb2.WorkflowSuite()
config = workflow_suite.configs.add(
name='test-config', build_type='bazel'
)
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_config_references_real_build_type(config)
def test_check_build_references_real_config(self):
"""Test that builds reference real configs."""
workflow_suite = workflows_pb2.WorkflowSuite()
build = workflow_suite.builds.add(
name='test-build', use_config='missing-config'
)
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_build_references_real_config(build)
def test_check_build_references_real_output(self):
"""Test that builds reference real outputs."""
workflow_suite = workflows_pb2.WorkflowSuite()
build = workflow_suite.builds.add(name='test-build')
build.use_output.append('missing-output')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_build_references_real_output(build)
def test_check_build_has_config(self):
"""Test that builds have a config."""
workflow_suite = workflows_pb2.WorkflowSuite()
build = workflow_suite.builds.add(name='test-build')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_build_has_config(build)
def test_check_build_has_targets(self):
"""Test that builds have targets."""
workflow_suite = workflows_pb2.WorkflowSuite()
build = workflow_suite.builds.add(name='test-build')
build.build_config.name = 'test-config'
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_build_has_targets(build)
def test_check_tool_references_real_config(self):
"""Test that tools reference real configs."""
workflow_suite = workflows_pb2.WorkflowSuite()
tool = workflow_suite.tools.add(
name='test-tool', use_config='missing-config'
)
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_references_real_config(tool)
def test_check_tool_references_real_output(self):
"""Test that tools reference real configs."""
workflow_suite = workflows_pb2.WorkflowSuite()
tool = workflow_suite.tools.add(name='test-tool')
tool.use_output.append('missing-output')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_references_real_output(tool)
def test_check_tool_has_config(self):
"""Test that tools have a config."""
workflow_suite = workflows_pb2.WorkflowSuite()
tool = workflow_suite.tools.add(name='test-tool')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_has_config(tool)
def test_check_tool_has_target(self):
"""Test that tools have a target."""
workflow_suite = workflows_pb2.WorkflowSuite()
tool = workflow_suite.tools.add(name='test-tool')
tool.build_config.name = 'test-config'
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_has_target(tool)
def test_check_group_builds_exist(self):
"""Test that group builds exist."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.builds.append('missing-build')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_builds_exist(group)
def test_check_group_analyzers_exist(self):
"""Test that group analyzers exist."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.analyzers.append('missing-analyzer')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_analyzers_exist(group)
def test_check_group_groups_exist(self):
"""Test that group groups exist."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.groups.append('missing-group')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_groups_exist(group)
def test_check_group_groups_are_groups(self):
"""Test that group groups are TaskGroups."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.groups.append('not-a-group')
workflow_suite.builds.add(name='not-a-group')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_groups_exist(group)
def test_check_group_has_no_cycles(self):
"""Test that group cycles are detected."""
workflow_suite = workflows_pb2.WorkflowSuite()
group1 = workflow_suite.groups.add(name='group1')
group2 = workflow_suite.groups.add(name='group2')
group1.groups.append('group2')
group2.groups.append('group1')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_has_no_cycles(group1)
def test_check_general_tools_do_not_set_rerun_shortcut(self):
"""Test that tools missing analyzer_safe_args do not set shortcut."""
workflow_suite = workflows_pb2.WorkflowSuite()
general_tool = workflow_suite.tools.add(
name='test-tool', rerun_shortcut='test-tool-alt'
)
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_rerun_shortcut_if_hybrid(general_tool)
def test_check_analyzers_do_not_set_rerun_shortcut(self):
"""Test analyzers do not use rerun_shortcut."""
workflow_suite = workflows_pb2.WorkflowSuite()
analyzer = workflow_suite.tools.add(
name='test-tool',
type=workflows_pb2.Tool.Type.ANALYZER,
rerun_shortcut='test-tool-alt',
)
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_tool_rerun_shortcut_if_hybrid(analyzer)
def test_check_group_analyzers_are_tools(self):
"""Test that group analyzers are tools."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.analyzers.append('not-a-tool')
workflow_suite.builds.add(name='not-a-tool')
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_analyzers_exist(group)
def test_check_group_analyzers_are_analyzers(self):
"""Test that group analyzers are of type ANALYZER."""
workflow_suite = workflows_pb2.WorkflowSuite()
group = workflow_suite.groups.add(name='test-group')
group.analyzers.append('not-an-analyzer')
tool = workflow_suite.tools.add(name='not-an-analyzer')
tool.type = workflows_pb2.Tool.Type.GENERAL
val = validator.Validator(workflow_suite, {})
with self.assertRaises(validator.ValidationError):
val.check_group_analyzers_exist(group)
@staticmethod
def test_validate_valid_config():
"""Test that a valid config passes validation."""
workflow_suite = workflows_pb2.WorkflowSuite()
workflow_suite.configs.add(name='test-config', build_type='bazel')
build = workflow_suite.builds.add(
name='test-build', use_config='test-config'
)
build.targets.append('//:test')
tool = workflow_suite.tools.add(
name='test-tool', use_config='test-config'
)
tool.target = '//:test_tool'
group = workflow_suite.groups.add(name='test-group')
group.builds.append('test-build')
group.analyzers.append('test-tool')
tool.type = workflows_pb2.Tool.Type.ANALYZER
val = validator.Validator(workflow_suite, {'bazel': NopBuildDriver()})
val.validate()
def test_validate_invalid_config(self):
"""Test that an invalid config fails validation."""
workflow_suite = workflows_pb2.WorkflowSuite()
workflow_suite.builds.add(name='same-name')
workflow_suite.tools.add(name='same-name')
with self.assertRaises(validator.ValidationError):
val = validator.Validator(workflow_suite, {})
val.validate()
if __name__ == '__main__':
unittest.main()