Support project configs in multiple locations

If a project definition appears in more than one file (which will
be necessary if a project wants to add a job to its pipeline config
and even more so if that appears in multiple branches) it should be
merged with the existing project config.

To accomplish that, when building the layout, pass all of the
project definitions to the project parser so that we can re-use
the existing template/project-pipeline-config inheritance mechanism
to merge all of the project-pipeline definitions across all of these
configurations.  Rely on the job implicit branch matching to
automatically resolve a given job appearing in the project-pipeline
on multiple branches.

Also, change the in-repo-config zuulv3 test to not use Ansible since
we now have sufficient ansible test coverage elsewhere and this does
not exercise any ansible features.

Change-Id: I5e4cddbc5d29215e2d9da4749c5ec06738e3b305
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index 9dac383..9bd405e 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -197,7 +197,7 @@
         })
         layout.addJob(python27essex)
 
-        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -205,7 +205,7 @@
                     'python27'
                 ]
             }
-        })
+        }])
         layout.addProjectConfig(project_config, update_pipeline=False)
 
         change = model.Change(project)
@@ -406,7 +406,7 @@
         })
         layout.addJob(python27diablo)
 
-        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -414,7 +414,7 @@
                     {'python27': {'timeout': 70}}
                 ]
             }
-        })
+        }])
         layout.addProjectConfig(project_config, update_pipeline=False)
 
         change = model.Change(project)
@@ -471,7 +471,7 @@
         })
         layout.addJob(python27)
 
-        project_config = configloader.ProjectParser.fromYaml(tenant, layout, {
+        project_config = configloader.ProjectParser.fromYaml(tenant, layout, [{
             '_source_context': context,
             'name': 'project',
             'gate': {
@@ -479,7 +479,7 @@
                     'python27',
                 ]
             }
-        })
+        }])
         layout.addProjectConfig(project_config, update_pipeline=False)
 
         change = model.Change(project)
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index f9eaece..97002b2 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -17,7 +17,7 @@
 import os
 import textwrap
 
-from tests.base import AnsibleZuulTestCase
+from tests.base import AnsibleZuulTestCase, ZuulTestCase
 
 
 class TestMultipleTenants(AnsibleZuulTestCase):
@@ -63,7 +63,7 @@
                          "not affect tenant one")
 
 
-class TestInRepoConfig(AnsibleZuulTestCase):
+class TestInRepoConfig(ZuulTestCase):
     # A temporary class to hold new tests while others are disabled
 
     tenant_config_file = 'config/in-repo/main.yaml'
@@ -129,6 +129,62 @@
             dict(name='project-test2', result='SUCCESS', changes='1,1'),
             dict(name='project-test2', result='SUCCESS', changes='2,1')])
 
+    def test_in_repo_branch(self):
+        in_repo_conf = textwrap.dedent(
+            """
+            - job:
+                name: project-test2
+
+            - project:
+                name: org/project
+                tenant-one-gate:
+                  jobs:
+                    - project-test2
+            """)
+
+        in_repo_playbook = textwrap.dedent(
+            """
+            - hosts: all
+              tasks: []
+            """)
+
+        file_dict = {'.zuul.yaml': in_repo_conf,
+                     'playbooks/project-test2.yaml': in_repo_playbook}
+        self.create_branch('org/project', 'stable')
+        A = self.fake_gerrit.addFakeChange('org/project', 'stable', 'A',
+                                           files=file_dict)
+        A.addApproval('code-review', 2)
+        self.fake_gerrit.addEvent(A.addApproval('approved', 1))
+        self.waitUntilSettled()
+        self.assertEqual(A.data['status'], 'MERGED')
+        self.assertEqual(A.reported, 2,
+                         "A should report start and success")
+        self.assertIn('tenant-one-gate', A.messages[1],
+                      "A should transit tenant-one gate")
+        self.assertHistory([
+            dict(name='project-test2', result='SUCCESS', changes='1,1')])
+        self.fake_gerrit.addEvent(A.getChangeMergedEvent())
+
+        # The config change should not affect master.
+        B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
+        B.addApproval('code-review', 2)
+        self.fake_gerrit.addEvent(B.addApproval('approved', 1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='project-test2', result='SUCCESS', changes='1,1'),
+            dict(name='project-test1', result='SUCCESS', changes='2,1')])
+
+        # The config change should be live for further changes on
+        # stable.
+        C = self.fake_gerrit.addFakeChange('org/project', 'stable', 'C')
+        C.addApproval('code-review', 2)
+        self.fake_gerrit.addEvent(C.addApproval('approved', 1))
+        self.waitUntilSettled()
+        self.assertHistory([
+            dict(name='project-test2', result='SUCCESS', changes='1,1'),
+            dict(name='project-test1', result='SUCCESS', changes='2,1'),
+            dict(name='project-test2', result='SUCCESS', changes='3,1')])
+
 
 class TestAnsible(AnsibleZuulTestCase):
     # A temporary class to hold new tests while others are disabled