| OLD | NEW |
| (Empty) |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import collections | |
| 6 import json | |
| 7 import logging | |
| 8 import os | |
| 9 import sys | |
| 10 import tempfile | |
| 11 | |
| 12 from cStringIO import StringIO | |
| 13 | |
| 14 from infra.services.gnumbd import inner_loop as gnumbd | |
| 15 | |
| 16 from infra.services.gnumbd.support import config_ref, data, git | |
| 17 | |
| 18 from infra.services.gnumbd.test import gnumbd_smoketests | |
| 19 | |
| 20 # should already be on path | |
| 21 import expect_tests # pylint: disable=F0401 | |
| 22 | |
| 23 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) | |
| 24 | |
| 25 | |
| 26 # TODO(iannucci): Make these first class data library objects | |
| 27 class GitEntry(object): | |
| 28 typ = None | |
| 29 mode = None | |
| 30 | |
| 31 def intern(self, repo): | |
| 32 raise NotImplementedError() # pragma: no cover | |
| 33 | |
| 34 | |
| 35 class GitFile(GitEntry): | |
| 36 typ = 'blob' | |
| 37 | |
| 38 def __init__(self, content, mode=0644): | |
| 39 super(GitFile, self).__init__() | |
| 40 self.content = content | |
| 41 assert mode in (0644, 0664, 0755) | |
| 42 self.mode = '0100%o' % mode | |
| 43 | |
| 44 def intern(self, repo): | |
| 45 return repo.intern(self.content) | |
| 46 | |
| 47 | |
| 48 class GitTree(GitEntry): | |
| 49 typ = 'tree' | |
| 50 mode = '0040000' | |
| 51 | |
| 52 def __init__(self, entries): | |
| 53 super(GitTree, self).__init__() | |
| 54 assert all( | |
| 55 isinstance(k, str) and isinstance(v, GitEntry) | |
| 56 for k, v in entries.iteritems() | |
| 57 ) | |
| 58 self.entries = entries | |
| 59 | |
| 60 def intern(self, repo): | |
| 61 with tempfile.TemporaryFile() as tf: | |
| 62 for path, entry in self.entries.iteritems(): | |
| 63 tf.write('%s %s %s\t%s' % | |
| 64 (entry.mode, entry.typ, entry.intern(repo), path)) | |
| 65 tf.seek(0) | |
| 66 return repo.run('mktree', '-z', stdin=tf).strip() | |
| 67 | |
| 68 class TestRef(git.Ref): | |
| 69 def synthesize_commit(self, message, number=None, tree=None, svn=False, | |
| 70 footers=None): | |
| 71 footers = footers or collections.OrderedDict() | |
| 72 if number is not None: | |
| 73 if svn: | |
| 74 footers[gnumbd.GIT_SVN_ID] = [ | |
| 75 'svn://repo/path@%s 0039d316-1c4b-4281-b951-d872f2087c98' % number] | |
| 76 else: | |
| 77 footers[gnumbd.COMMIT_POSITION] = [ | |
| 78 gnumbd.FMT_COMMIT_POSITION(self, number)] | |
| 79 | |
| 80 commit = self.repo.synthesize_commit(self.commit, message, footers=footers, | |
| 81 tree=tree) | |
| 82 self.update_to(commit) | |
| 83 return commit | |
| 84 | |
| 85 | |
| 86 class TestClock(object): | |
| 87 def __init__(self): | |
| 88 self._time = 1402589336 | |
| 89 | |
| 90 def time(self): | |
| 91 self._time += 10 | |
| 92 return self._time | |
| 93 | |
| 94 | |
| 95 class TestConfigRef(config_ref.ConfigRef): | |
| 96 def update(self, **values): | |
| 97 new_config = self.current | |
| 98 new_config.update(values) | |
| 99 self.ref.synthesize_commit( | |
| 100 'update(%r)' % values.keys(), | |
| 101 tree=GitTree({'config.json': GitFile(json.dumps(new_config))})) | |
| 102 | |
| 103 | |
| 104 class TestRepo(git.Repo): | |
| 105 def __init__(self, short_name, tmpdir, clock, mirror_of=None): | |
| 106 super(TestRepo, self).__init__(mirror_of or 'local test repo') | |
| 107 self._short_name = short_name | |
| 108 self.repos_dir = tmpdir | |
| 109 | |
| 110 if mirror_of is None: | |
| 111 self._repo_path = tempfile.mkdtemp(dir=self.repos_dir, suffix='.git') | |
| 112 self.run('init', '--bare') | |
| 113 | |
| 114 self._clock = clock | |
| 115 | |
| 116 # pylint: disable=W0212 | |
| 117 repo_path = property(lambda self: self._repo_path) | |
| 118 | |
| 119 def __getitem__(self, refstr): | |
| 120 return TestRef(self, refstr) | |
| 121 | |
| 122 def synthesize_commit(self, parent, message, tree=None, footers=None): | |
| 123 tree = tree or GitTree({'file': GitFile('contents')}) | |
| 124 tree = tree.intern(self) if isinstance(tree, GitTree) else tree | |
| 125 assert isinstance(tree, str) | |
| 126 | |
| 127 parents = [parent.hsh] if parent is not git.INVALID else [] | |
| 128 | |
| 129 timestamp = data.CommitTimestamp(self._clock.time(), '+', 8, 0) | |
| 130 user = data.CommitUser('Test User', 'test_user@example.com', timestamp) | |
| 131 | |
| 132 return self.get_commit(self.intern(data.CommitData( | |
| 133 tree, parents, user, user, (), message.splitlines(), | |
| 134 data.CommitData.merge_lines([], footers or {}) | |
| 135 ), 'commit')) | |
| 136 | |
| 137 def snap(self, include_committer=False, include_config=False): | |
| 138 ret = {} | |
| 139 if include_committer: | |
| 140 fmt = '%H%x00committer %cn <%ce> %ci%n%n%B%x00%x00' | |
| 141 else: | |
| 142 fmt = '%H%x00%B%x00%x00' | |
| 143 for ref in (r.ref for r in self.refglob('*')): | |
| 144 if ref == gnumbd.DEFAULT_CONFIG_REF and not include_config: | |
| 145 continue | |
| 146 log = self.run('log', ref, '--format=%s' % fmt) | |
| 147 ret[ref] = collections.OrderedDict( | |
| 148 (commit, message.splitlines()) | |
| 149 for commit, message in ( | |
| 150 r.split('\0') for r in log.split('\0\0\n') if r) | |
| 151 ) | |
| 152 return ret | |
| 153 | |
| 154 def __repr__(self): | |
| 155 return 'TestRepo(%r)' % self._short_name | |
| 156 | |
| 157 | |
| 158 def RunTest(tmpdir, test_name): | |
| 159 ret = [] | |
| 160 clock = TestClock() | |
| 161 origin = TestRepo('origin', tmpdir, clock) | |
| 162 local = TestRepo('local', tmpdir, clock, origin.repo_path) | |
| 163 | |
| 164 cref = TestConfigRef(origin[gnumbd.DEFAULT_CONFIG_REF]) | |
| 165 cref.update(enabled_refglobs=['refs/heads/*'], interval=0) | |
| 166 | |
| 167 def checkpoint(message, include_committer=False, include_config=False): | |
| 168 ret.append([message, {'origin': origin.snap(include_committer, | |
| 169 include_config)}]) | |
| 170 | |
| 171 def run(include_log=True): | |
| 172 stdout = sys.stdout | |
| 173 stderr = sys.stderr | |
| 174 | |
| 175 if include_log: | |
| 176 logout = StringIO() | |
| 177 root_logger = logging.getLogger() | |
| 178 log_level = root_logger.getEffectiveLevel() | |
| 179 shandler = logging.StreamHandler(logout) | |
| 180 shandler.setFormatter( | |
| 181 logging.Formatter('%(levelname)s: %(message)s')) | |
| 182 root_logger.addHandler(shandler) | |
| 183 root_logger.setLevel(logging.INFO) | |
| 184 | |
| 185 try: | |
| 186 sys.stderr = sys.stdout = open(os.devnull, 'w') | |
| 187 local.reify() | |
| 188 gnumbd.inner_loop(local, cref, clock) | |
| 189 except Exception: # pragma: no cover | |
| 190 import traceback | |
| 191 ret.append(traceback.format_exc().splitlines()) | |
| 192 finally: | |
| 193 sys.stdout = stdout | |
| 194 sys.stderr = stderr | |
| 195 | |
| 196 if include_log: | |
| 197 root_logger.removeHandler(shandler) | |
| 198 root_logger.setLevel(log_level) | |
| 199 ret.append({'log output': logout.getvalue().splitlines()}) | |
| 200 | |
| 201 gnumbd_smoketests.GNUMBD_TESTS[test_name]( | |
| 202 origin, local, cref, run, checkpoint) | |
| 203 | |
| 204 return expect_tests.Result(ret) | |
| 205 | |
| 206 | |
| 207 def GenTests(tmpdir): | |
| 208 for test_name, test in gnumbd_smoketests.GNUMBD_TESTS.iteritems(): | |
| 209 yield expect_tests.Test( | |
| 210 __package__ + '.' + test_name, | |
| 211 expect_tests.FuncCall(RunTest, tmpdir, test_name), | |
| 212 os.path.join(BASE_PATH, 'gnumbd_smoketests.expected'), | |
| 213 test_name, 'yaml', break_funcs=[test]) | |
| OLD | NEW |