Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(123)

Side by Side Diff: testing_support/git_test_utils.py

Issue 184253003: Add git-reup and friends (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@freeze_thaw
Patch Set: minor fixes Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import atexit 5 import atexit
6 import collections 6 import collections
7 import copy 7 import copy
8 import datetime 8 import datetime
9 import hashlib 9 import hashlib
10 import os 10 import os
11 import shutil 11 import shutil
12 import subprocess 12 import subprocess
13 import sys
13 import tempfile 14 import tempfile
14 import unittest 15 import unittest
15 16
17 from cStringIO import StringIO
18
16 19
17 def git_hash_data(data, typ='blob'): 20 def git_hash_data(data, typ='blob'):
18 """Calculate the git-style SHA1 for some data. 21 """Calculate the git-style SHA1 for some data.
19 22
20 Only supports 'blob' type data at the moment. 23 Only supports 'blob' type data at the moment.
21 """ 24 """
22 assert typ == 'blob', 'Only support blobs for now' 25 assert typ == 'blob', 'Only support blobs for now'
23 return hashlib.sha1('blob %s\0%s' % (len(data), data)).hexdigest() 26 return hashlib.sha1('blob %s\0%s' % (len(data), data)).hexdigest()
24 27
25 28
(...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after
247 program (assuming a normal exit like with sys.exit) 250 program (assuming a normal exit like with sys.exit)
248 251
249 Args: 252 Args:
250 schema - An instance of GitRepoSchema 253 schema - An instance of GitRepoSchema
251 """ 254 """
252 self.repo_path = tempfile.mkdtemp(dir=self.BASE_TEMP_DIR) 255 self.repo_path = tempfile.mkdtemp(dir=self.BASE_TEMP_DIR)
253 self.commit_map = {} 256 self.commit_map = {}
254 self._date = datetime.datetime(1970, 1, 1) 257 self._date = datetime.datetime(1970, 1, 1)
255 258
256 self.git('init') 259 self.git('init')
260 self.git('config', 'user.name', 'testcase')
261 self.git('config', 'user.email', 'testcase@example.com')
257 for commit in schema.walk(): 262 for commit in schema.walk():
258 self._add_schema_commit(commit, schema.data_for(commit.name)) 263 self._add_schema_commit(commit, schema.data_for(commit.name))
259 self.last_commit = self[commit.name] 264 self.last_commit = self[commit.name]
260 if schema.master: 265 if schema.master:
261 self.git('update-ref', 'master', self[schema.master]) 266 self.git('update-ref', 'refs/heads/master', self[schema.master])
262 267
263 def __getitem__(self, commit_name): 268 def __getitem__(self, commit_name):
264 """Gets the hash of a commit by its schema name. 269 """Gets the hash of a commit by its schema name.
265 270
266 >>> r = GitRepo(GitRepoSchema('A B C')) 271 >>> r = GitRepo(GitRepoSchema('A B C'))
267 >>> r['B'] 272 >>> r['B']
268 '7381febe1da03b09da47f009963ab7998a974935' 273 '7381febe1da03b09da47f009963ab7998a974935'
269 """ 274 """
270 return self.commit_map[commit_name] 275 return self.commit_map[commit_name]
271 276
272 def _add_schema_commit(self, commit, data): 277 def _add_schema_commit(self, commit, commit_data):
273 data = data or {} 278 commit_data = commit_data or {}
274 279
275 if commit.parents: 280 if commit.parents:
276 parents = list(commit.parents) 281 parents = list(commit.parents)
277 self.git('checkout', '--detach', '-q', self[parents[0]]) 282 self.git('checkout', '--detach', '-q', self[parents[0]])
278 if len(parents) > 1: 283 if len(parents) > 1:
279 self.git('merge', '--no-commit', '-q', *[self[x] for x in parents[1:]]) 284 self.git('merge', '--no-commit', '-q', *[self[x] for x in parents[1:]])
280 else: 285 else:
281 self.git('checkout', '--orphan', 'root_%s' % commit.name) 286 self.git('checkout', '--orphan', 'root_%s' % commit.name)
282 self.git('rm', '-rf', '.') 287 self.git('rm', '-rf', '.')
283 288
284 env = {} 289 env = self.get_git_commit_env(commit_data)
285 for prefix in ('AUTHOR', 'COMMITTER'):
286 for suffix in ('NAME', 'EMAIL', 'DATE'):
287 singleton = '%s_%s' % (prefix, suffix)
288 key = getattr(self, singleton)
289 if key in data:
290 val = data[key]
291 else:
292 if suffix == 'DATE':
293 val = self._date
294 self._date += datetime.timedelta(days=1)
295 else:
296 val = getattr(self, 'DEFAULT_%s' % singleton)
297 env['GIT_%s' % singleton] = str(val)
298 290
299 for fname, file_data in data.iteritems(): 291 for fname, file_data in commit_data.iteritems():
300 deleted = False 292 deleted = False
301 if 'data' in file_data: 293 if 'data' in file_data:
302 data = file_data.get('data') 294 data = file_data.get('data')
303 if data is None: 295 if data is None:
304 deleted = True 296 deleted = True
305 self.git('rm', fname) 297 self.git('rm', fname)
306 else: 298 else:
307 path = os.path.join(self.repo_path, fname) 299 path = os.path.join(self.repo_path, fname)
308 pardir = os.path.dirname(path) 300 pardir = os.path.dirname(path)
309 if not os.path.exists(pardir): 301 if not os.path.exists(pardir):
310 os.makedirs(pardir) 302 os.makedirs(pardir)
311 with open(path, 'wb') as f: 303 with open(path, 'wb') as f:
312 f.write(data) 304 f.write(data)
313 305
314 mode = file_data.get('mode') 306 mode = file_data.get('mode')
315 if mode and not deleted: 307 if mode and not deleted:
316 os.chmod(path, mode) 308 os.chmod(path, mode)
317 309
318 self.git('add', fname) 310 self.git('add', fname)
319 311
320 rslt = self.git('commit', '--allow-empty', '-m', commit.name, env=env) 312 rslt = self.git('commit', '--allow-empty', '-m', commit.name, env=env)
321 assert rslt.retcode == 0, 'Failed to commit %s' % str(commit) 313 assert rslt.retcode == 0, 'Failed to commit %s' % str(commit)
322 self.commit_map[commit.name] = self.git('rev-parse', 'HEAD').stdout.strip() 314 self.commit_map[commit.name] = self.git('rev-parse', 'HEAD').stdout.strip()
323 self.git('tag', 'tag_%s' % commit.name, self[commit.name]) 315 self.git('tag', 'tag_%s' % commit.name, self[commit.name])
324 if commit.is_branch: 316 if commit.is_branch:
325 self.git('branch', '-f', 'branch_%s' % commit.name, self[commit.name]) 317 self.git('branch', '-f', 'branch_%s' % commit.name, self[commit.name])
326 318
319 def get_git_commit_env(self, commit_data=None):
320 commit_data = commit_data or {}
321 env = {}
322 for prefix in ('AUTHOR', 'COMMITTER'):
323 for suffix in ('NAME', 'EMAIL', 'DATE'):
324 singleton = '%s_%s' % (prefix, suffix)
325 key = getattr(self, singleton)
326 if key in commit_data:
327 val = commit_data[key]
328 else:
329 if suffix == 'DATE':
330 val = self._date
331 self._date += datetime.timedelta(days=1)
332 else:
333 val = getattr(self, 'DEFAULT_%s' % singleton)
334 env['GIT_%s' % singleton] = str(val)
335 return env
336
337
327 def git(self, *args, **kwargs): 338 def git(self, *args, **kwargs):
328 """Runs a git command specified by |args| in this repo.""" 339 """Runs a git command specified by |args| in this repo."""
329 assert self.repo_path is not None 340 assert self.repo_path is not None
330 try: 341 try:
331 with open(os.devnull, 'wb') as devnull: 342 with open(os.devnull, 'wb') as devnull:
332 output = subprocess.check_output( 343 output = subprocess.check_output(
333 ('git',) + args, cwd=self.repo_path, stderr=devnull, **kwargs) 344 ('git',) + args, cwd=self.repo_path, stderr=devnull, **kwargs)
334 return self.COMMAND_OUTPUT(0, output) 345 return self.COMMAND_OUTPUT(0, output)
335 except subprocess.CalledProcessError as e: 346 except subprocess.CalledProcessError as e:
336 return self.COMMAND_OUTPUT(e.returncode, e.output) 347 return self.COMMAND_OUTPUT(e.returncode, e.output)
337 348
349 def git_commit(self, message):
350 return self.git('commit', '-am', message, env=self.get_git_commit_env())
351
338 def nuke(self): 352 def nuke(self):
339 """Obliterates the git repo on disk. 353 """Obliterates the git repo on disk.
340 354
341 Causes this GitRepo to be unusable. 355 Causes this GitRepo to be unusable.
342 """ 356 """
343 shutil.rmtree(self.repo_path) 357 shutil.rmtree(self.repo_path)
344 self.repo_path = None 358 self.repo_path = None
345 359
346 def run(self, fn, *args, **kwargs): 360 def run(self, fn, *args, **kwargs):
347 """Run a python function with the given args and kwargs with the cwd set to 361 """Run a python function with the given args and kwargs with the cwd set to
348 the git repo.""" 362 the git repo."""
349 assert self.repo_path is not None 363 assert self.repo_path is not None
350 curdir = os.getcwd() 364 curdir = os.getcwd()
351 try: 365 try:
352 os.chdir(self.repo_path) 366 os.chdir(self.repo_path)
353 return fn(*args, **kwargs) 367 return fn(*args, **kwargs)
354 finally: 368 finally:
355 os.chdir(curdir) 369 os.chdir(curdir)
356 370
371 def capture_stdio(self, fn, *args, **kwargs):
372 """Run a python function with the given args and kwargs with the cwd set to
373 the git repo.
374
375 Returns the (stdout, stderr) of whatever ran, instead of the what |fn|
376 returned.
377 """
378 stdout = sys.stdout
379 stderr = sys.stderr
380 try:
381 sys.stdout = StringIO()
382 sys.stderr = StringIO()
383 try:
384 self.run(fn, *args, **kwargs)
385 except SystemExit:
386 pass
387 return sys.stdout.getvalue(), sys.stderr.getvalue()
388 finally:
389 sys.stdout = stdout
390 sys.stderr = stderr
391
392 def open(self, path, mode='rb'):
393 return open(os.path.join(self.repo_path, path), mode)
394
357 395
358 class GitRepoSchemaTestBase(unittest.TestCase): 396 class GitRepoSchemaTestBase(unittest.TestCase):
359 """A TestCase with a built-in GitRepoSchema. 397 """A TestCase with a built-in GitRepoSchema.
360 398
361 Expects a class variable REPO to be a GitRepoSchema string in the form 399 Expects a class variable REPO_SCHEMA to be a GitRepoSchema string in the form
362 described by that class. 400 described by that class.
363 401
364 You may also set class variables in the form COMMIT_%(commit_name)s, which 402 You may also set class variables in the form COMMIT_%(commit_name)s, which
365 provide the content for the given commit_name commits. 403 provide the content for the given commit_name commits.
366 404
367 You probably will end up using either GitRepoReadOnlyTestBase or 405 You probably will end up using either GitRepoReadOnlyTestBase or
368 GitRepoReadWriteTestBase for real tests. 406 GitRepoReadWriteTestBase for real tests.
369 """ 407 """
370 REPO = None 408 REPO_SCHEMA = None
371 409
372 @classmethod 410 @classmethod
373 def getRepoContent(cls, commit): 411 def getRepoContent(cls, commit):
374 return getattr(cls, 'COMMIT_%s' % commit, None) 412 return getattr(cls, 'COMMIT_%s' % commit, None)
375 413
376 @classmethod 414 @classmethod
377 def setUpClass(cls): 415 def setUpClass(cls):
378 super(GitRepoSchemaTestBase, cls).setUpClass() 416 super(GitRepoSchemaTestBase, cls).setUpClass()
379 assert cls.REPO is not None 417 assert cls.REPO_SCHEMA is not None
380 cls.r_schema = GitRepoSchema(cls.REPO, cls.getRepoContent) 418 cls.r_schema = GitRepoSchema(cls.REPO_SCHEMA, cls.getRepoContent)
381 419
382 420
383 class GitRepoReadOnlyTestBase(GitRepoSchemaTestBase): 421 class GitRepoReadOnlyTestBase(GitRepoSchemaTestBase):
384 """Injects a GitRepo object given the schema and content from 422 """Injects a GitRepo object given the schema and content from
385 GitRepoSchemaTestBase into TestCase classes which subclass this. 423 GitRepoSchemaTestBase into TestCase classes which subclass this.
386 424
387 This GitRepo will appear as self.repo, and will be deleted and recreated once 425 This GitRepo will appear as self.repo, and will be deleted and recreated once
388 for the duration of all the tests in the subclass. 426 for the duration of all the tests in the subclass.
389 """ 427 """
390 REPO = None 428 REPO_SCHEMA = None
391 429
392 @classmethod 430 @classmethod
393 def setUpClass(cls): 431 def setUpClass(cls):
394 super(GitRepoReadOnlyTestBase, cls).setUpClass() 432 super(GitRepoReadOnlyTestBase, cls).setUpClass()
395 assert cls.REPO is not None 433 assert cls.REPO_SCHEMA is not None
396 cls.repo = cls.r_schema.reify() 434 cls.repo = cls.r_schema.reify()
397 435
398 def setUp(self): 436 def setUp(self):
399 self.repo.git('checkout', '-f', self.repo.last_commit) 437 self.repo.git('checkout', '-f', self.repo.last_commit)
400 438
401 @classmethod 439 @classmethod
402 def tearDownClass(cls): 440 def tearDownClass(cls):
403 cls.repo.nuke() 441 cls.repo.nuke()
404 super(GitRepoReadOnlyTestBase, cls).tearDownClass() 442 super(GitRepoReadOnlyTestBase, cls).tearDownClass()
405 443
406 444
407 class GitRepoReadWriteTestBase(GitRepoSchemaTestBase): 445 class GitRepoReadWriteTestBase(GitRepoSchemaTestBase):
408 """Injects a GitRepo object given the schema and content from 446 """Injects a GitRepo object given the schema and content from
409 GitRepoSchemaTestBase into TestCase classes which subclass this. 447 GitRepoSchemaTestBase into TestCase classes which subclass this.
410 448
411 This GitRepo will appear as self.repo, and will be deleted and recreated for 449 This GitRepo will appear as self.repo, and will be deleted and recreated for
412 each test function in the subclass. 450 each test function in the subclass.
413 """ 451 """
414 REPO = None 452 REPO_SCHEMA = None
415 453
416 def setUp(self): 454 def setUp(self):
417 super(GitRepoReadWriteTestBase, self).setUp() 455 super(GitRepoReadWriteTestBase, self).setUp()
418 self.repo = self.r_schema.reify() 456 self.repo = self.r_schema.reify()
419 457
420 def tearDown(self): 458 def tearDown(self):
421 self.repo.nuke() 459 self.repo.nuke()
422 super(GitRepoReadWriteTestBase, self).tearDown() 460 super(GitRepoReadWriteTestBase, self).tearDown()
OLDNEW
« git_reparent_branch.py ('K') | « git_upstream_diff.py ('k') | tests/git_common_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698