Index: testing_support/git_test_utils.py |
diff --git a/testing_support/git_test_utils.py b/testing_support/git_test_utils.py |
index 5becad94ba58e0ece7bbcbdaa812fee034d1d0d9..d4c0d8035fbcd41a1b3f7696800c3984688417db 100644 |
--- a/testing_support/git_test_utils.py |
+++ b/testing_support/git_test_utils.py |
@@ -10,9 +10,12 @@ import hashlib |
import os |
import shutil |
import subprocess |
+import sys |
import tempfile |
import unittest |
+from cStringIO import StringIO |
+ |
def git_hash_data(data, typ='blob'): |
"""Calculate the git-style SHA1 for some data. |
@@ -159,6 +162,12 @@ class GitRepoSchema(object): |
v.difference_update(empty_keys) |
is_root = False |
+ def add_partial(self, commit, parent=None): |
+ if commit not in self.par_map: |
+ self.par_map[commit] = OrderedSet() |
+ if parent is not None: |
+ self.par_map[commit].add(parent) |
+ |
def add_commits(self, schema): |
"""Adds more commits from a schema into the existing Schema. |
@@ -170,10 +179,7 @@ class GitRepoSchema(object): |
for commits in (l.split() for l in schema.splitlines() if l.strip()): |
parent = None |
for commit in commits: |
- if commit not in self.par_map: |
- self.par_map[commit] = OrderedSet() |
- if parent is not None: |
- self.par_map[commit].add(parent) |
+ self.add_partial(commit, parent) |
parent = commit |
if parent and not self.master: |
self.master = parent |
@@ -195,6 +201,18 @@ class GitRepoSchema(object): |
self.data_cache[commit] = self.content_fn(commit) |
return self.data_cache[commit] |
+ def simple_graph(self): |
+ """Returns a dictionary of {commit_subject: {parent commit_subjects}} |
+ |
+ This allows you to get a very simple connection graph over the whole repo |
+ for comparison purposes. Only commit subjects (not ids, not content/data) |
+ are considered |
+ """ |
+ ret = {} |
+ for commit in self.walk(): |
+ ret.setdefault(commit.name, set()).update(commit.parents) |
+ return ret |
+ |
class GitRepo(object): |
"""Creates a real git repo for a GitRepoSchema. |
@@ -253,12 +271,16 @@ class GitRepo(object): |
self.commit_map = {} |
self._date = datetime.datetime(1970, 1, 1) |
+ self.to_schema_refs = ['--branches'] |
+ |
self.git('init') |
+ self.git('config', 'user.name', 'testcase') |
+ self.git('config', 'user.email', 'testcase@example.com') |
for commit in schema.walk(): |
self._add_schema_commit(commit, schema.data_for(commit.name)) |
self.last_commit = self[commit.name] |
if schema.master: |
- self.git('update-ref', 'master', self[schema.master]) |
+ self.git('update-ref', 'refs/heads/master', self[schema.master]) |
def __getitem__(self, commit_name): |
"""Gets the hash of a commit by its schema name. |
@@ -269,8 +291,8 @@ class GitRepo(object): |
""" |
return self.commit_map[commit_name] |
- def _add_schema_commit(self, commit, data): |
- data = data or {} |
+ def _add_schema_commit(self, commit, commit_data): |
+ commit_data = commit_data or {} |
if commit.parents: |
parents = list(commit.parents) |
@@ -281,22 +303,9 @@ class GitRepo(object): |
self.git('checkout', '--orphan', 'root_%s' % commit.name) |
self.git('rm', '-rf', '.') |
- env = {} |
- for prefix in ('AUTHOR', 'COMMITTER'): |
- for suffix in ('NAME', 'EMAIL', 'DATE'): |
- singleton = '%s_%s' % (prefix, suffix) |
- key = getattr(self, singleton) |
- if key in data: |
- val = data[key] |
- else: |
- if suffix == 'DATE': |
- val = self._date |
- self._date += datetime.timedelta(days=1) |
- else: |
- val = getattr(self, 'DEFAULT_%s' % singleton) |
- env['GIT_%s' % singleton] = str(val) |
+ env = self.get_git_commit_env(commit_data) |
- for fname, file_data in data.iteritems(): |
+ for fname, file_data in commit_data.iteritems(): |
deleted = False |
if 'data' in file_data: |
data = file_data.get('data') |
@@ -324,6 +333,25 @@ class GitRepo(object): |
if commit.is_branch: |
self.git('branch', '-f', 'branch_%s' % commit.name, self[commit.name]) |
+ def get_git_commit_env(self, commit_data=None): |
+ commit_data = commit_data or {} |
+ env = {} |
+ for prefix in ('AUTHOR', 'COMMITTER'): |
+ for suffix in ('NAME', 'EMAIL', 'DATE'): |
+ singleton = '%s_%s' % (prefix, suffix) |
+ key = getattr(self, singleton) |
+ if key in commit_data: |
+ val = commit_data[key] |
+ else: |
+ if suffix == 'DATE': |
+ val = self._date |
+ self._date += datetime.timedelta(days=1) |
+ else: |
+ val = getattr(self, 'DEFAULT_%s' % singleton) |
+ env['GIT_%s' % singleton] = str(val) |
+ return env |
+ |
+ |
def git(self, *args, **kwargs): |
"""Runs a git command specified by |args| in this repo.""" |
assert self.repo_path is not None |
@@ -335,6 +363,9 @@ class GitRepo(object): |
except subprocess.CalledProcessError as e: |
return self.COMMAND_OUTPUT(e.returncode, e.output) |
+ def git_commit(self, message): |
+ return self.git('commit', '-am', message, env=self.get_git_commit_env()) |
+ |
def nuke(self): |
"""Obliterates the git repo on disk. |
@@ -354,11 +385,59 @@ class GitRepo(object): |
finally: |
os.chdir(curdir) |
+ def capture_stdio(self, fn, *args, **kwargs): |
+ """Run a python function with the given args and kwargs with the cwd set to |
+ the git repo. |
+ |
+ Returns the (stdout, stderr) of whatever ran, instead of the what |fn| |
+ returned. |
+ """ |
+ stdout = sys.stdout |
+ stderr = sys.stderr |
+ try: |
+ sys.stdout = StringIO() |
+ sys.stderr = StringIO() |
+ try: |
+ self.run(fn, *args, **kwargs) |
+ except SystemExit: |
+ pass |
+ return sys.stdout.getvalue(), sys.stderr.getvalue() |
+ finally: |
+ sys.stdout = stdout |
+ sys.stderr = stderr |
+ |
+ def open(self, path, mode='rb'): |
+ return open(os.path.join(self.repo_path, path), mode) |
+ |
+ def to_schema(self): |
+ lines = self.git('rev-list', '--parents', '--reverse', '--topo-order', |
+ '--format=%s', *self.to_schema_refs).stdout.splitlines() |
+ hash_to_msg = {} |
+ ret = GitRepoSchema() |
+ current = None |
+ parents = [] |
+ for line in lines: |
+ if line.startswith('commit'): |
+ assert current is None |
+ tokens = line.split() |
+ current, parents = tokens[1], tokens[2:] |
+ assert all(p in hash_to_msg for p in parents) |
+ else: |
+ assert current is not None |
+ hash_to_msg[current] = line |
+ ret.add_partial(line) |
+ for parent in parents: |
+ ret.add_partial(line, hash_to_msg[parent]) |
+ current = None |
+ parents = [] |
+ assert current is None |
+ return ret |
+ |
class GitRepoSchemaTestBase(unittest.TestCase): |
"""A TestCase with a built-in GitRepoSchema. |
- Expects a class variable REPO to be a GitRepoSchema string in the form |
+ Expects a class variable REPO_SCHEMA to be a GitRepoSchema string in the form |
described by that class. |
You may also set class variables in the form COMMIT_%(commit_name)s, which |
@@ -367,7 +446,7 @@ class GitRepoSchemaTestBase(unittest.TestCase): |
You probably will end up using either GitRepoReadOnlyTestBase or |
GitRepoReadWriteTestBase for real tests. |
""" |
- REPO = None |
+ REPO_SCHEMA = None |
@classmethod |
def getRepoContent(cls, commit): |
@@ -376,8 +455,8 @@ class GitRepoSchemaTestBase(unittest.TestCase): |
@classmethod |
def setUpClass(cls): |
super(GitRepoSchemaTestBase, cls).setUpClass() |
- assert cls.REPO is not None |
- cls.r_schema = GitRepoSchema(cls.REPO, cls.getRepoContent) |
+ assert cls.REPO_SCHEMA is not None |
+ cls.r_schema = GitRepoSchema(cls.REPO_SCHEMA, cls.getRepoContent) |
class GitRepoReadOnlyTestBase(GitRepoSchemaTestBase): |
@@ -387,12 +466,12 @@ class GitRepoReadOnlyTestBase(GitRepoSchemaTestBase): |
This GitRepo will appear as self.repo, and will be deleted and recreated once |
for the duration of all the tests in the subclass. |
""" |
- REPO = None |
+ REPO_SCHEMA = None |
@classmethod |
def setUpClass(cls): |
super(GitRepoReadOnlyTestBase, cls).setUpClass() |
- assert cls.REPO is not None |
+ assert cls.REPO_SCHEMA is not None |
cls.repo = cls.r_schema.reify() |
def setUp(self): |
@@ -411,7 +490,7 @@ class GitRepoReadWriteTestBase(GitRepoSchemaTestBase): |
This GitRepo will appear as self.repo, and will be deleted and recreated for |
each test function in the subclass. |
""" |
- REPO = None |
+ REPO_SCHEMA = None |
def setUp(self): |
super(GitRepoReadWriteTestBase, self).setUp() |
@@ -420,3 +499,7 @@ class GitRepoReadWriteTestBase(GitRepoSchemaTestBase): |
def tearDown(self): |
self.repo.nuke() |
super(GitRepoReadWriteTestBase, self).tearDown() |
+ |
+ def assertSchema(self, schema_string): |
+ self.assertEqual(GitRepoSchema(schema_string).simple_graph(), |
+ self.repo.to_schema().simple_graph()) |