Check out implicit branch in timer jobs

So that we may re-use the same jobs for pre and post merge tests,
enqueue an item for every branch of every timer-triggered project
and checkout that branch before running the job.  This means that
rather than having a job for gate plus a job for each stable branch,
we hav just have a single job which runs with different content.

The old method is still supported using override branches.

This updates the model to include Change, Branch, Tag, and Ref
objects which can be used as the value of Item.change.  Branch,
Tag, and Ref are all very similar, but the distinction may help
us ensure that we're encoding the right information about the items
we are enqueing.  This is important for branch matching in pipelines
and is also used to provide job variables.

Change-Id: I5c41d2dcbbbd1c17d68074cd7480e6ab83f884ea
diff --git a/tests/base.py b/tests/base.py
index fb94638..484b9e5 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -1068,8 +1068,10 @@
         self.__dict__.update(kw)
 
     def __repr__(self):
-        return ("<Completed build, result: %s name: %s uuid: %s changes: %s>" %
-                (self.result, self.name, self.uuid, self.changes))
+        return ("<Completed build, result: %s name: %s uuid: %s "
+                "changes: %s ref: %s>" %
+                (self.result, self.name, self.uuid,
+                 self.changes, self.ref))
 
 
 class FakeStatsd(threading.Thread):
@@ -1344,6 +1346,7 @@
         self.executor_server.build_history.append(
             BuildHistory(name=build.name, result=result, changes=build.changes,
                          node=build.node, uuid=build.unique,
+                         ref=build.parameters['zuul']['ref'],
                          parameters=build.parameters, jobdir=build.jobdir,
                          pipeline=build.parameters['ZUUL_PIPELINE'])
         )
diff --git a/tests/fixtures/layouts/idle.yaml b/tests/fixtures/layouts/idle.yaml
index 60f8ed1..49c45ac 100644
--- a/tests/fixtures/layouts/idle.yaml
+++ b/tests/fixtures/layouts/idle.yaml
@@ -6,20 +6,14 @@
         - time: '* * * * * */1'
 
 - job:
-    name: project-bitrot-stable-old
+    name: project-bitrot
     nodes:
       - name: static
         label: ubuntu-xenial
 
-- job:
-    name: project-bitrot-stable-older
-    nodes:
-      - name: static
-        label: ubuntu-trusty
-
 - project:
     name: org/project
     periodic:
       jobs:
-        - project-bitrot-stable-old
-        - project-bitrot-stable-older
+        - project-bitrot
+
diff --git a/tests/fixtures/layouts/no-timer.yaml b/tests/fixtures/layouts/no-timer.yaml
index 12eaa35..05f17d2 100644
--- a/tests/fixtures/layouts/no-timer.yaml
+++ b/tests/fixtures/layouts/no-timer.yaml
@@ -24,17 +24,11 @@
     name: project-test1
 
 - job:
-    name: project-bitrot-stable-old
+    name: project-bitrot
     nodes:
       - name: static
         label: ubuntu-xenial
 
-- job:
-    name: project-bitrot-stable-older
-    nodes:
-      - name: static
-        label: ubuntu-trusty
-
 - project:
     name: org/project
     check:
@@ -42,5 +36,4 @@
         - project-test1
     periodic:
       jobs:
-        - project-bitrot-stable-old
-        - project-bitrot-stable-older
+        - project-bitrot
diff --git a/tests/fixtures/layouts/repo-checkout-timer-override.yaml b/tests/fixtures/layouts/repo-checkout-timer-override.yaml
new file mode 100644
index 0000000..594d74c
--- /dev/null
+++ b/tests/fixtures/layouts/repo-checkout-timer-override.yaml
@@ -0,0 +1,19 @@
+- pipeline:
+    name: periodic
+    manager: independent
+    trigger:
+      timer:
+        - time: '* * * * * */1'
+
+- job:
+    name: integration
+    branches: master
+    override-branch: stable/havana
+    required-projects:
+      - org/project1
+
+- project:
+    name: org/project1
+    periodic:
+      jobs:
+        - integration
diff --git a/tests/fixtures/layouts/repo-checkout-timer.yaml b/tests/fixtures/layouts/repo-checkout-timer.yaml
index d5917d1..3c4d030 100644
--- a/tests/fixtures/layouts/repo-checkout-timer.yaml
+++ b/tests/fixtures/layouts/repo-checkout-timer.yaml
@@ -7,7 +7,6 @@
 
 - job:
     name: integration
-    override-branch: stable/havana
     required-projects:
       - org/project1
 
diff --git a/tests/fixtures/layouts/timer.yaml b/tests/fixtures/layouts/timer.yaml
index 883c32e..dbce516 100644
--- a/tests/fixtures/layouts/timer.yaml
+++ b/tests/fixtures/layouts/timer.yaml
@@ -25,17 +25,11 @@
     name: project-test2
 
 - job:
-    name: project-bitrot-stable-old
+    name: project-bitrot
     nodes:
       - name: static
         label: ubuntu-xenial
 
-- job:
-    name: project-bitrot-stable-older
-    nodes:
-      - name: static
-        label: ubuntu-trusty
-
 - project:
     name: org/project
     check:
@@ -44,5 +38,4 @@
         - project-test2
     periodic:
       jobs:
-        - project-bitrot-stable-old
-        - project-bitrot-stable-older
+        - project-bitrot
diff --git a/tests/unit/test_executor.py b/tests/unit/test_executor.py
index 7b76802..4700bd1 100755
--- a/tests/unit/test_executor.py
+++ b/tests/unit/test_executor.py
@@ -248,6 +248,46 @@
 
         self.assertBuildStates(states, projects)
 
+    def test_periodic_override(self):
+        # This test can not use simple_layout because it must start
+        # with a configuration which does not include a
+        # timer-triggered job so that we have an opportunity to set
+        # the hold flag before the first job.
+
+        # This tests that we can override the branch in a timer
+        # trigger (mostly to ensure backwards compatability for jobs).
+        self.executor_server.hold_jobs_in_build = True
+        # Start timer trigger - also org/project
+        self.commitConfigUpdate('common-config',
+                                'layouts/repo-checkout-timer-override.yaml')
+        self.sched.reconfigure(self.config)
+
+        p1 = 'review.example.com/org/project1'
+        projects = [p1]
+        self.create_branch('org/project1', 'stable/havana')
+
+        # The pipeline triggers every second, so we should have seen
+        # several by now.
+        time.sleep(5)
+        self.waitUntilSettled()
+
+        # Stop queuing timer triggered jobs so that the assertions
+        # below don't race against more jobs being queued.
+        self.commitConfigUpdate('common-config',
+                                'layouts/repo-checkout-no-timer.yaml')
+        self.sched.reconfigure(self.config)
+
+        self.assertEquals(1, len(self.builds), "One build is running")
+
+        upstream = self.getUpstreamRepos(projects)
+        states = [
+            {p1: dict(commit=str(upstream[p1].commit('stable/havana')),
+                      branch='stable/havana'),
+             },
+        ]
+
+        self.assertBuildStates(states, projects)
+
     def test_periodic(self):
         # This test can not use simple_layout because it must start
         # with a configuration which does not include a
@@ -274,14 +314,19 @@
                                 'layouts/repo-checkout-no-timer.yaml')
         self.sched.reconfigure(self.config)
 
-        self.assertEquals(1, len(self.builds), "One build is running")
+        self.assertEquals(2, len(self.builds), "Two builds are running")
 
         upstream = self.getUpstreamRepos(projects)
         states = [
             {p1: dict(commit=str(upstream[p1].commit('stable/havana')),
                       branch='stable/havana'),
              },
+            {p1: dict(commit=str(upstream[p1].commit('master')),
+                      branch='master'),
+             },
         ]
+        if self.builds[0].parameters['zuul']['ref'] == 'refs/heads/master':
+            states = list(reversed(states))
 
         self.assertBuildStates(states, projects)
 
diff --git a/tests/unit/test_github_driver.py b/tests/unit/test_github_driver.py
index 0cfe3da..8493570 100644
--- a/tests/unit/test_github_driver.py
+++ b/tests/unit/test_github_driver.py
@@ -12,11 +12,14 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import os
 import re
 from testtools.matchers import MatchesRegex, StartsWith
 import urllib
 import time
 
+import git
+
 from tests.base import ZuulTestCase, simple_layout, random_sha1
 
 
@@ -94,7 +97,16 @@
     def test_tag_event(self):
         self.executor_server.hold_jobs_in_build = True
 
-        sha = random_sha1()
+        self.create_branch('org/project', 'tagbranch')
+        files = {'README.txt': 'test'}
+        self.addCommitToRepo('org/project', 'test tag',
+                             files, branch='tagbranch', tag='newtag')
+        path = os.path.join(self.upstream_root, 'org/project')
+        repo = git.Repo(path)
+        tag = repo.tags['newtag']
+        sha = tag.commit.hexsha
+        del repo
+
         self.fake_github.emitEvent(
             self.fake_github.getPushEvent('org/project', 'refs/tags/newtag',
                                           new_rev=sha))
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index d9cf839..3e60ead 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -1806,17 +1806,17 @@
         self.commitConfigUpdate('common-config', 'layouts/no-timer.yaml')
         self.sched.reconfigure(self.config)
 
-        self.assertEqual(len(self.builds), 2, "Two timer jobs")
+        self.assertEqual(len(self.builds), 1, "One timer job")
 
         A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
         self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
-        self.assertEqual(len(self.builds), 3, "One change plus two timer jobs")
+        self.assertEqual(len(self.builds), 2, "One change plus one timer job")
 
         self.fake_gerrit.addEvent(A.getChangeAbandonedEvent())
         self.waitUntilSettled()
 
-        self.assertEqual(len(self.builds), 2, "Two timer jobs remain")
+        self.assertEqual(len(self.builds), 1, "One timer job remains")
 
         self.executor_server.release()
         self.waitUntilSettled()
@@ -2777,6 +2777,7 @@
         # with a configuration which does not include a
         # timer-triggered job so that we have an opportunity to set
         # the hold flag before the first job.
+        self.create_branch('org/project', 'stable')
         self.executor_server.hold_jobs_in_build = True
         self.commitConfigUpdate('common-config', 'layouts/timer.yaml')
         self.sched.reconfigure(self.config)
@@ -2803,10 +2804,12 @@
         self.executor_server.release()
         self.waitUntilSettled()
 
-        self.assertEqual(self.getJobFromHistory(
-            'project-bitrot-stable-old').result, 'SUCCESS')
-        self.assertEqual(self.getJobFromHistory(
-            'project-bitrot-stable-older').result, 'SUCCESS')
+        self.assertHistory([
+            dict(name='project-bitrot', result='SUCCESS',
+                 ref='refs/heads/master'),
+            dict(name='project-bitrot', result='SUCCESS',
+                 ref='refs/heads/stable'),
+        ], ordered=False)
 
         data = json.loads(data)
         status_jobs = set()
@@ -2816,8 +2819,7 @@
                     for change in head:
                         for job in change['jobs']:
                             status_jobs.add(job['name'])
-        self.assertIn('project-bitrot-stable-old', status_jobs)
-        self.assertIn('project-bitrot-stable-older', status_jobs)
+        self.assertIn('project-bitrot', status_jobs)
 
     def test_idle(self):
         "Test that frequent periodic jobs work"
@@ -2846,12 +2848,12 @@
                                     'layouts/no-timer.yaml')
             self.sched.reconfigure(self.config)
             self.waitUntilSettled()
-            self.assertEqual(len(self.builds), 2,
+            self.assertEqual(len(self.builds), 1,
                              'Timer builds iteration #%d' % x)
             self.executor_server.release('.*')
             self.waitUntilSettled()
             self.assertEqual(len(self.builds), 0)
-            self.assertEqual(len(self.history), x * 2)
+            self.assertEqual(len(self.history), x)
 
     @simple_layout('layouts/smtp.yaml')
     def test_check_smtp_pool(self):
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 734c45c..9c7ffea 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -667,7 +667,8 @@
         self.assertFalse(os.path.exists(pre_flag_path))
         post_flag_path = os.path.join(self.test_root, build.uuid +
                                       '.post.flag')
-        self.assertTrue(os.path.exists(post_flag_path))
+        self.assertTrue(os.path.exists(post_flag_path),
+                        "The file %s should exist" % post_flag_path)
 
 
 class TestBrokenConfig(ZuulTestCase):