diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 2fc7090b79..1ab698b5f3 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -31,6 +31,7 @@ import zuul.lib.connections from tests.base import BaseTestCase, FIXTURE_DIR from zuul.lib.ansible import AnsibleManager from zuul.zk.zkobject import LocalZKContext +from zuul import change_matcher class Dummy(object): @@ -746,3 +747,22 @@ class TestRef(BaseTestCase): self.assertFalse(branch1.equals(change1)) self.assertFalse(branch1.equals(tag1)) + + +class TestSourceContext(BaseTestCase): + def setUp(self): + super().setUp() + self.connection = Dummy(connection_name='dummy_connection') + self.source = Dummy(canonical_hostname='git.example.com', + connection=self.connection) + self.project = model.Project('project', self.source) + self.context = model.SourceContext( + self.project.canonical_name, self.project.name, + self.project.connection_name, 'master', 'test', True) + self.context.implied_branches = [ + change_matcher.BranchMatcher('foo'), + change_matcher.ImpliedBranchMatcher('foo'), + ] + + def test_serialize(self): + self.context.deserialize(self.context.serialize()) diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py index 5e21cd57f4..b8db861887 100644 --- a/tests/unit/test_v3.py +++ b/tests/unit/test_v3.py @@ -890,6 +890,39 @@ class TestBranchMismatch(ZuulTestCase): dict(name='project-test1', result='SUCCESS', changes='1,1'), ], ordered=False) + def test_implied_branch_matcher_pragma_syntax_error(self): + # Test that syntax errors are reported if the implied branch + # matcher pragma is set. This catches potential errors when + # serializing configuration errors since the pragma causes + # extra information to be added to the error source context. + self.create_branch('org/project1', 'feature/test') + self.fake_gerrit.addEvent( + self.fake_gerrit.getFakeBranchCreatedEvent( + 'org/project1', 'feature/test')) + + in_repo_conf = textwrap.dedent( + """ + - job: + name: project-test1 + nodeset: bar + - pragma: + implied-branches: + - master + - feature/r1 + """) + file_dict = {'zuul.yaml': in_repo_conf} + A = self.fake_gerrit.addFakeChange('org/project1', 'master', 'A', + files=file_dict) + self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1)) + self.waitUntilSettled() + + self.assertHistory([]) + self.assertEqual(A.data['status'], 'NEW') + self.assertEqual(A.reported, 1, + "A should report failure") + self.assertIn('nodeset "bar" was not found', A.messages[0], + "A should have a syntax error reported") + class TestBranchRef(ZuulTestCase): tenant_config_file = 'config/branch-ref/main.yaml' @@ -1717,6 +1750,25 @@ class TestInRepoConfig(ZuulTestCase): self.assertIn('while constructing a mapping', A.messages[0], "A should have a syntax error reported") + def test_yaml_dict_error3(self): + in_repo_conf = textwrap.dedent( + """ + - job: + """) + + file_dict = {'.zuul.yaml': in_repo_conf} + A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A', + files=file_dict) + A.addApproval('Code-Review', 2) + self.fake_gerrit.addEvent(A.addApproval('Approved', 1)) + self.waitUntilSettled() + + self.assertEqual(A.data['status'], 'NEW') + self.assertEqual(A.reported, 1, + "A should report failure") + self.assertIn('is not a dictionary', A.messages[0], + "A should have a syntax error reported") + def test_yaml_duplicate_key_error(self): in_repo_conf = textwrap.dedent( """ diff --git a/zuul/change_matcher.py b/zuul/change_matcher.py index 139c7b6a9c..0a347bc72b 100644 --- a/zuul/change_matcher.py +++ b/zuul/change_matcher.py @@ -82,6 +82,17 @@ class BranchMatcher(AbstractChangeMatcher): return True return False + def serialize(self): + return { + "implied": self.exactmatch, + "regex": self._regex, + } + + @classmethod + def deserialize(cls, data): + o = cls.__new__(cls, data['regex']) + return o + class ImpliedBranchMatcher(BranchMatcher): exactmatch = True diff --git a/zuul/model.py b/zuul/model.py index 9500647076..cb75f58706 100644 --- a/zuul/model.py +++ b/zuul/model.py @@ -1695,6 +1695,9 @@ class SourceContext(ConfigObject): self.trusted == other.trusted) def serialize(self): + ibs = None + if self.implied_branches: + ibs = [ibm.serialize() for ibm in self.implied_branches] return { "project_canonical_name": self.project_canonical_name, "project_name": self.project_name, @@ -1703,12 +1706,22 @@ class SourceContext(ConfigObject): "path": self.path, "trusted": self.trusted, "implied_branch_matchers": self.implied_branch_matchers, - "implied_branches": self.implied_branches, + "implied_branches": ibs, } @classmethod def deserialize(cls, data): o = cls.__new__(cls) + ibs = data.get('implied_branches') + if ibs: + data['implied_branches'] = [] + for matcher_data in ibs: + if matcher_data['implied']: + cls = change_matcher.ImpliedBranchMatcher + else: + cls = change_matcher.BranchMatcher + data['implied_branches'].append( + cls.deserialize(matcher_data)) o.__dict__.update(data) return o @@ -6738,6 +6751,8 @@ class UnparsedConfig(object): if len(item.keys()) > 1: raise ConfigItemMultipleKeysError(item) key, value = list(item.items())[0] + if not isinstance(value, dict): + raise ConfigItemNotDictError(item) if key == 'project': self.projects.append(value) elif key == 'job':