@ -17,108 +17,107 @@ import unittest
import gclient_utils
DEFAULT_BRANCH = ' main '
def git_hash_data ( data , typ = ' blob ' ) :
""" Calculate the git-style SHA1 for some data.
""" Calculate the git-style SHA1 for some data.
Only supports ' blob ' type data at the moment .
"""
assert typ == ' blob ' , ' Only support blobs for now '
return hashlib . sha1 ( b ' blob %d \0 %s ' % ( len ( data ) , data ) ) . hexdigest ( )
assert typ == ' blob ' , ' Only support blobs for now '
return hashlib . sha1 ( b ' blob %d \0 %s ' % ( len ( data ) , data ) ) . hexdigest ( )
class OrderedSet ( collections . MutableSet ) :
# from http://code.activestate.com/recipes/576694/
def __init__ ( self , iterable = None ) :
self . end = end = [ ]
end + = [ None , end , end ] # sentinel node for doubly linked list
self . data = { } # key --> [key, prev, next]
if iterable is not None :
self | = iterable
def __contains__ ( self , key ) :
return key in self . data
def __eq__ ( self , other ) :
if isinstance ( other , OrderedSet ) :
return len ( self ) == len ( other ) and list ( self ) == list ( other )
return set ( self ) == set ( other )
def __ne__ ( self , other ) :
if isinstance ( other , OrderedSet ) :
return len ( self ) != len ( other ) or list ( self ) != list ( other )
return set ( self ) != set ( other )
def __len__ ( self ) :
return len ( self . data )
def __iter__ ( self ) :
end = self . end
curr = end [ 2 ]
while curr is not end :
yield curr [ 0 ]
curr = curr [ 2 ]
def __repr__ ( self ) :
if not self :
return ' %s () ' % ( self . __class__ . __name__ , )
return ' %s ( %r ) ' % ( self . __class__ . __name__ , list ( self ) )
def __reversed__ ( self ) :
end = self . end
curr = end [ 1 ]
while curr is not end :
yield curr [ 0 ]
curr = curr [ 1 ]
def add ( self , key ) :
if key not in self . data :
end = self . end
curr = end [ 1 ]
curr [ 2 ] = end [ 1 ] = self . data [ key ] = [ key , curr , end ]
def difference_update ( self , * others ) :
for other in others :
for i in other :
self . discard ( i )
def discard ( self , key ) :
if key in self . data :
key , prev , nxt = self . data . pop ( key )
prev [ 2 ] = nxt
nxt [ 1 ] = prev
def pop ( self , last = True ) : # pylint: disable=arguments-differ
if not self :
raise KeyError ( ' set is empty ' )
key = self . end [ 1 ] [ 0 ] if last else self . end [ 2 ] [ 0 ]
self . discard ( key )
return key
# from http://code.activestate.com/recipes/576694/
def __init__ ( self , iterable = None ) :
self . end = end = [ ]
end + = [ None , end , end ] # sentinel node for doubly linked list
self . data = { } # key --> [key, prev, next]
if iterable is not None :
self | = iterable
def __contains__ ( self , key ) :
return key in self . data
def __eq__ ( self , other ) :
if isinstance ( other , OrderedSet ) :
return len ( self ) == len ( other ) and list ( self ) == list ( other )
return set ( self ) == set ( other )
def __ne__ ( self , other ) :
if isinstance ( other , OrderedSet ) :
return len ( self ) != len ( other ) or list ( self ) != list ( other )
return set ( self ) != set ( other )
def __len__ ( self ) :
return len ( self . data )
def __iter__ ( self ) :
end = self . end
curr = end [ 2 ]
while curr is not end :
yield curr [ 0 ]
curr = curr [ 2 ]
def __repr__ ( self ) :
if not self :
return ' %s () ' % ( self . __class__ . __name__ , )
return ' %s ( %r ) ' % ( self . __class__ . __name__ , list ( self ) )
def __reversed__ ( self ) :
end = self . end
curr = end [ 1 ]
while curr is not end :
yield curr [ 0 ]
curr = curr [ 1 ]
def add ( self , key ) :
if key not in self . data :
end = self . end
curr = end [ 1 ]
curr [ 2 ] = end [ 1 ] = self . data [ key ] = [ key , curr , end ]
def difference_update ( self , * others ) :
for other in others :
for i in other :
self . discard ( i )
def discard ( self , key ) :
if key in self . data :
key , prev , nxt = self . data . pop ( key )
prev [ 2 ] = nxt
nxt [ 1 ] = prev
def pop ( self , last = True ) : # pylint: disable=arguments-differ
if not self :
raise KeyError ( ' set is empty ' )
key = self . end [ 1 ] [ 0 ] if last else self . end [ 2 ] [ 0 ]
self . discard ( key )
return key
class UTC ( datetime . tzinfo ) :
""" UTC time zone.
""" UTC time zone.
from https : / / docs . python . org / 2 / library / datetime . html #tzinfo-objects
"""
def utcoffset ( self , dt ) :
return datetime . timedelta ( 0 )
def utcoffset ( self , dt ) :
return datetime . timedelta ( 0 )
def tzname ( self , dt ) :
return " UTC "
def tzname ( self , dt ) :
return " UTC "
def dst ( self , dt ) :
return datetime . timedelta ( 0 )
def dst ( self , dt ) :
return datetime . timedelta ( 0 )
UTC = UTC ( )
class GitRepoSchema ( object ) :
""" A declarative git testing repo.
""" A declarative git testing repo.
Pass a schema to __init__ in the form of :
A B C D
@ -141,11 +140,10 @@ class GitRepoSchema(object):
in the schema ) get earlier timestamps . Stamps start at the Unix Epoch , and
increment by 1 day each .
"""
COMMIT = collections . namedtuple ( ' COMMIT ' , ' name parents is_branch is_root ' )
COMMIT = collections . namedtuple ( ' COMMIT ' , ' name parents is_branch is_root ' )
def __init__ ( self , repo_schema = ' ' ,
content_fn = lambda v : { v : { ' data ' : v } } ) :
""" Builds a new GitRepoSchema.
def __init__ ( self , repo_schema = ' ' , content_fn = lambda v : { v : { ' data ' : v } } ) :
""" Builds a new GitRepoSchema.
Args :
repo_schema ( str ) - Initial schema for this repo . See class docstring for
@ -156,88 +154,88 @@ class GitRepoSchema(object):
commit_name ) . See the docstring on the GitRepo class for the format of
the data returned by this function .
"""
self . main = None
self . par_map = { }
self . data_cache = { }
self . content_fn = content_fn
self . add_commits ( repo_schema )
self . main = None
self . par_map = { }
self . data_cache = { }
self . content_fn = content_fn
self . add_commits ( repo_schema )
def walk ( self ) :
""" (Generator) Walks the repo schema from roots to tips.
def walk ( self ) :
""" (Generator) Walks the repo schema from roots to tips.
Generates GitRepoSchema . COMMIT objects for each commit .
Throws an AssertionError if it detects a cycle .
"""
is_root = True
par_map = copy . deepcopy ( self . par_map )
while par_map :
empty_keys = set ( k for k , v in par_map . items ( ) if not v )
assert empty_keys , ' Cycle detected! %s ' % par_map
for k in sorted ( empty_keys ) :
yield self . COMMIT ( k , self . par_map [ k ] ,
not any ( k in v for v in self . par_map . values ( ) ) ,
is_root )
del par_map [ k ]
for v in par_map . values ( ) :
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.
is_root = True
par_map = copy . deepcopy ( self . par_map )
while par_map :
empty_keys = set ( k for k , v in par_map . items ( ) if not v )
assert empty_keys , ' Cycle detected! %s ' % par_map
for k in sorted ( empty_keys ) :
yield self . COMMIT (
k , self . par_map [ k ] ,
not any ( k in v for v in self . par_map . values ( ) ) , is_root )
del par_map [ k ]
for v in par_map . values ( ) :
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.
Args :
schema ( str ) - See class docstring for info on schema format .
Throws an AssertionError if it detects a cycle .
"""
for commits in ( l . split ( ) for l in schema . splitlines ( ) if l . strip ( ) ) :
parent = None
for commit in commits :
self . add_partial ( commit , parent )
parent = commit
if parent and not self . main :
self . main = parent
for _ in self . walk ( ) : # This will throw if there are any cycles.
pass
def reify ( self ) :
""" Returns a real GitRepo for this GitRepoSchema """
return GitRepo ( self )
def data_for ( self , commit ) :
""" Obtains the data for |commit|.
for commits in ( l . split ( ) for l in schema . splitlines ( ) if l . strip ( ) ) :
parent = None
for commit in commits :
self . add_partial ( commit , parent )
parent = commit
if parent and not self . main :
self . main = parent
for _ in self . walk ( ) : # This will throw if there are any cycles.
pass
def reify ( self ) :
""" Returns a real GitRepo for this GitRepoSchema """
return GitRepo ( self )
def data_for ( self , commit ) :
""" Obtains the data for |commit|.
See the docstring on the GitRepo class for the format of the returned data .
Caches the result on this GitRepoSchema instance .
"""
if commit not in self . data_cache :
self . data_cache [ commit ] = self . content_fn ( commit )
return self . data_cache [ commit ]
if commit not in self . data_cache :
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}}
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
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.
""" Creates a real git repo for a GitRepoSchema.
Obtains schema and content information from the GitRepoSchema .
@ -260,26 +258,26 @@ class GitRepo(object):
For file content , if ' data ' is None , then this commit will ` git rm ` that file .
"""
BASE_TEMP_DIR = tempfile . mkdtemp ( suffix = ' base ' , prefix = ' git_repo ' )
atexit . register ( gclient_utils . rmtree , BASE_TEMP_DIR )
BASE_TEMP_DIR = tempfile . mkdtemp ( suffix = ' base ' , prefix = ' git_repo ' )
atexit . register ( gclient_utils . rmtree , BASE_TEMP_DIR )
# Singleton objects to specify specific data in a commit dictionary.
AUTHOR_NAME = object ( )
AUTHOR_EMAIL = object ( )
AUTHOR_DATE = object ( )
COMMITTER_NAME = object ( )
COMMITTER_EMAIL = object ( )
COMMITTER_DATE = object ( )
# Singleton objects to specify specific data in a commit dictionary.
AUTHOR_NAME = object ( )
AUTHOR_EMAIL = object ( )
AUTHOR_DATE = object ( )
COMMITTER_NAME = object ( )
COMMITTER_EMAIL = object ( )
COMMITTER_DATE = object ( )
DEFAULT_AUTHOR_NAME = ' Author McAuthorly '
DEFAULT_AUTHOR_EMAIL = ' author@example.com '
DEFAULT_COMMITTER_NAME = ' Charles Committish '
DEFAULT_COMMITTER_EMAIL = ' commitish@example.com '
DEFAULT_AUTHOR_NAME = ' Author McAuthorly '
DEFAULT_AUTHOR_EMAIL = ' author@example.com '
DEFAULT_COMMITTER_NAME = ' Charles Committish '
DEFAULT_COMMITTER_EMAIL = ' commitish@example.com '
COMMAND_OUTPUT = collections . namedtuple ( ' COMMAND_OUTPUT ' , ' retcode stdout ' )
COMMAND_OUTPUT = collections . namedtuple ( ' COMMAND_OUTPUT ' , ' retcode stdout ' )
def __init__ ( self , schema ) :
""" Makes new GitRepo.
def __init__ ( self , schema ) :
""" Makes new GitRepo.
Automatically creates a temp folder under GitRepo . BASE_TEMP_DIR . It ' s
recommended that you clean this repo up by calling nuke ( ) on it , but if not ,
@ -289,194 +287,198 @@ class GitRepo(object):
Args :
schema - An instance of GitRepoSchema
"""
self . repo_path = os . path . realpath ( tempfile . mkdtemp ( dir = self . BASE_TEMP_DIR ) )
self . commit_map = { }
self . _date = datetime . datetime ( 1970 , 1 , 1 , tzinfo = UTC )
self . repo_path = os . path . realpath (
tempfile . mkdtemp ( dir = self . BASE_TEMP_DIR ) )
self . commit_map = { }
self . _date = datetime . datetime ( 1970 , 1 , 1 , tzinfo = UTC )
self . to_schema_refs = [ ' --branches ' ]
self . to_schema_refs = [ ' --branches ' ]
self . git ( ' init ' , ' -b ' , DEFAULT_BRANCH )
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 . main :
self . git ( ' update-ref ' , ' refs/heads/main ' , self [ schema . main ] )
self . git ( ' init ' , ' -b ' , DEFAULT_BRANCH )
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 . main :
self . git ( ' update-ref ' , ' refs/heads/main ' , self [ schema . main ] )
def __getitem__ ( self , commit_name ) :
""" Gets the hash of a commit by its schema name.
def __getitem__ ( self , commit_name ) :
""" Gets the hash of a commit by its schema name.
>> > r = GitRepo ( GitRepoSchema ( ' A B C ' ) )
>> > r [ ' B ' ]
' 7381febe1da03b09da47f009963ab7998a974935 '
"""
return self . commit_map [ commit_name ]
def _add_schema_commit ( self , commit , commit_data ) :
commit_data = commit_data or { }
if commit . parents :
parents = list ( commit . parents )
self . git ( ' checkout ' , ' --detach ' , ' -q ' , self [ parents [ 0 ] ] )
if len ( parents ) > 1 :
self . git ( ' merge ' , ' --no-commit ' , ' -q ' , * [ self [ x ] for x in parents [ 1 : ] ] )
else :
self . git ( ' checkout ' , ' --orphan ' , ' root_ %s ' % commit . name )
self . git ( ' rm ' , ' -rf ' , ' . ' )
env = self . get_git_commit_env ( commit_data )
for fname , file_data in commit_data . items ( ) :
# If it isn't a string, it's one of the special keys.
if not isinstance ( fname , str ) :
continue
deleted = False
if ' data ' in file_data :
data = file_data . get ( ' data ' )
if data is None :
deleted = True
self . git ( ' rm ' , fname )
else :
path = os . path . join ( self . repo_path , fname )
pardir = os . path . dirname ( path )
if not os . path . exists ( pardir ) :
os . makedirs ( pardir )
with open ( path , ' wb ' ) as f :
f . write ( data )
mode = file_data . get ( ' mode ' )
if mode and not deleted :
os . chmod ( path , mode )
self . git ( ' add ' , fname )
rslt = self . git ( ' commit ' , ' --allow-empty ' , ' -m ' , commit . name , env = env )
assert rslt . retcode == 0 , ' Failed to commit %s ' % str ( commit )
self . commit_map [ commit . name ] = self . git ( ' rev-parse ' , ' HEAD ' ) . stdout . strip ( )
self . git ( ' tag ' , ' tag_ %s ' % commit . name , self [ commit . name ] )
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 = os . environ . copy ( )
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 ]
elif suffix == ' DATE ' :
val = self . _date
self . _date + = datetime . timedelta ( days = 1 )
return self . commit_map [ commit_name ]
def _add_schema_commit ( self , commit , commit_data ) :
commit_data = commit_data or { }
if commit . parents :
parents = list ( commit . parents )
self . git ( ' checkout ' , ' --detach ' , ' -q ' , self [ parents [ 0 ] ] )
if len ( parents ) > 1 :
self . git ( ' merge ' , ' --no-commit ' , ' -q ' ,
* [ self [ x ] for x in parents [ 1 : ] ] )
else :
val = getattr ( self , ' DEFAULT_ %s ' % singleton )
if not isinstance ( val , str ) and not isinstance ( val , bytes ) :
val = str ( val )
env [ ' GIT_ %s ' % singleton ] = 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
try :
with open ( os . devnull , ' wb ' ) as devnull :
shell = sys . platform == ' win32 '
output = subprocess . check_output (
( ' git ' , ) + args ,
shell = shell ,
cwd = self . repo_path ,
stderr = devnull ,
* * kwargs )
output = output . decode ( ' utf-8 ' )
return self . COMMAND_OUTPUT ( 0 , output )
except subprocess . CalledProcessError as e :
return self . COMMAND_OUTPUT ( e . returncode , e . output )
def show_commit ( self , commit_name , format_string ) :
""" Shows a commit (by its schema name) with a given format string. """
return self . git ( ' show ' , ' -q ' , ' --pretty=format: %s ' % format_string ,
self [ commit_name ] ) . stdout
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.
self . git ( ' checkout ' , ' --orphan ' , ' root_ %s ' % commit . name )
self . git ( ' rm ' , ' -rf ' , ' . ' )
env = self . get_git_commit_env ( commit_data )
for fname , file_data in commit_data . items ( ) :
# If it isn't a string, it's one of the special keys.
if not isinstance ( fname , str ) :
continue
deleted = False
if ' data ' in file_data :
data = file_data . get ( ' data ' )
if data is None :
deleted = True
self . git ( ' rm ' , fname )
else :
path = os . path . join ( self . repo_path , fname )
pardir = os . path . dirname ( path )
if not os . path . exists ( pardir ) :
os . makedirs ( pardir )
with open ( path , ' wb ' ) as f :
f . write ( data )
mode = file_data . get ( ' mode ' )
if mode and not deleted :
os . chmod ( path , mode )
self . git ( ' add ' , fname )
rslt = self . git ( ' commit ' , ' --allow-empty ' , ' -m ' , commit . name , env = env )
assert rslt . retcode == 0 , ' Failed to commit %s ' % str ( commit )
self . commit_map [ commit . name ] = self . git ( ' rev-parse ' ,
' HEAD ' ) . stdout . strip ( )
self . git ( ' tag ' , ' tag_ %s ' % commit . name , self [ commit . name ] )
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 = os . environ . copy ( )
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 ]
elif suffix == ' DATE ' :
val = self . _date
self . _date + = datetime . timedelta ( days = 1 )
else :
val = getattr ( self , ' DEFAULT_ %s ' % singleton )
if not isinstance ( val , str ) and not isinstance ( val , bytes ) :
val = str ( val )
env [ ' GIT_ %s ' % singleton ] = 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
try :
with open ( os . devnull , ' wb ' ) as devnull :
shell = sys . platform == ' win32 '
output = subprocess . check_output ( ( ' git ' , ) + args ,
shell = shell ,
cwd = self . repo_path ,
stderr = devnull ,
* * kwargs )
output = output . decode ( ' utf-8 ' )
return self . COMMAND_OUTPUT ( 0 , output )
except subprocess . CalledProcessError as e :
return self . COMMAND_OUTPUT ( e . returncode , e . output )
def show_commit ( self , commit_name , format_string ) :
""" Shows a commit (by its schema name) with a given format string. """
return self . git ( ' show ' , ' -q ' , ' --pretty=format: %s ' % format_string ,
self [ commit_name ] ) . stdout
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.
Causes this GitRepo to be unusable .
"""
gclient_utils . rmtree ( self . repo_path )
self . repo_path = None
def run ( self , fn , * args , * * kwargs ) :
""" Run a python function with the given args and kwargs with the cwd set to
the git repo . """
assert self . repo_path is not None
curdir = os . getcwd ( )
try :
os . chdir ( self . repo_path )
return fn ( * args , * * kwargs )
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 .
gclient_utils . rmtree ( self . repo_path )
self . repo_path = None
def run ( self , fn , * args , * * kwargs ) :
""" Run a python function with the given args and kwargs with the cwd
set to the git repo . """
assert self . repo_path is not None
curdir = os . getcwd ( )
try :
os . chdir ( self . repo_path )
return fn ( * args , * * kwargs )
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
t o t he git repo .
Returns the ( stdout , stderr ) of whatever ran , instead of the what | fn |
returned .
"""
stdout = sys . stdout
stderr = sys . stderr
try :
with tempfile . TemporaryFile ( ' w+ ' ) as out :
with tempfile . TemporaryFile ( ' w+ ' ) as err :
sys . stdout = out
sys . stderr = err
try :
self . run ( fn , * args , * * kwargs )
except SystemExit :
pass
out . seek ( 0 )
err . seek ( 0 )
return out . read ( ) , err . read ( )
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 ] )
stdout = sys . stdout
stderr = sys . stderr
try :
with tempfile . TemporaryFile ( ' w+ ' ) as out :
with tempfile . TemporaryFile ( ' w+ ' ) as err :
sys . stdout = out
sys . stderr = err
try :
self . run ( fn , * args , * * kwargs )
except SystemExit :
pass
out . seek ( 0 )
err . seek ( 0 )
return out . read ( ) , err . read ( )
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 = [ ]
assert current is None
return ret
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.
""" A TestCase with a built-in GitRepoSchema.
Expects a class variable REPO_SCHEMA to be a GitRepoSchema string in the form
described by that class .
@ -487,61 +489,62 @@ class GitRepoSchemaTestBase(unittest.TestCase):
You probably will end up using either GitRepoReadOnlyTestBase or
GitRepoReadWriteTestBase for real tests .
"""
REPO_SCHEMA = None
REPO_SCHEMA = None
@classmethod
def getRepoContent ( cls , commit ) :
commit = ' COMMIT_ %s ' % commit
return getattr ( cls , commit , None )
@classmethod
def getRepoContent ( cls , commit ) :
commit = ' COMMIT_ %s ' % commit
return getattr ( cls , commit , None )
@classmethod
def setUpClass ( cls ) :
super ( GitRepoSchemaTestBase , cls ) . setUpClass ( )
assert cls . REPO_SCHEMA is not None
cls . r_schema = GitRepoSchema ( cls . REPO_SCHEMA , cls . getRepoContent )
@classmethod
def setUpClass ( cls ) :
super ( GitRepoSchemaTestBase , cls ) . setUpClass ( )
assert cls . REPO_SCHEMA is not None
cls . r_schema = GitRepoSchema ( cls . REPO_SCHEMA , cls . getRepoContent )
class GitRepoReadOnlyTestBase ( GitRepoSchemaTestBase ) :
""" Injects a GitRepo object given the schema and content from
""" Injects a GitRepo object given the schema and content from
GitRepoSchemaTestBase into TestCase classes which subclass this .
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_SCHEMA = None
REPO_SCHEMA = None
@classmethod
def setUpClass ( cls ) :
super ( GitRepoReadOnlyTestBase , cls ) . setUpClass ( )
assert cls . REPO_SCHEMA is not None
cls . repo = cls . r_schema . reify ( )
@classmethod
def setUpClass ( cls ) :
super ( GitRepoReadOnlyTestBase , cls ) . setUpClass ( )
assert cls . REPO_SCHEMA is not None
cls . repo = cls . r_schema . reify ( )
def setUp ( self ) :
self . repo . git ( ' checkout ' , ' -f ' , self . repo . last_commit )
def setUp ( self ) :
self . repo . git ( ' checkout ' , ' -f ' , self . repo . last_commit )
@classmethod
def tearDownClass ( cls ) :
cls . repo . nuke ( )
super ( GitRepoReadOnlyTestBase , cls ) . tearDownClass ( )
@classmethod
def tearDownClass ( cls ) :
cls . repo . nuke ( )
super ( GitRepoReadOnlyTestBase , cls ) . tearDownClass ( )
class GitRepoReadWriteTestBase ( GitRepoSchemaTestBase ) :
""" Injects a GitRepo object given the schema and content from
""" Injects a GitRepo object given the schema and content from
GitRepoSchemaTestBase into TestCase classes which subclass this .
This GitRepo will appear as self . repo , and will be deleted and recreated for
each test function in the subclass .
"""
REPO_SCHEMA = None
REPO_SCHEMA = None
def setUp ( self ) :
super ( GitRepoReadWriteTestBase , self ) . setUp ( )
self . repo = self . r_schema . reify ( )
def setUp ( self ) :
super ( GitRepoReadWriteTestBase , self ) . setUp ( )
self . repo = self . r_schema . reify ( )
def tearDown ( self ) :
self . repo . nuke ( )
super ( GitRepoReadWriteTestBase , self ) . tearDown ( )
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 ( ) )
def assertSchema ( self , schema_string ) :
self . assertEqual (
GitRepoSchema ( schema_string ) . simple_graph ( ) ,
self . repo . to_schema ( ) . simple_graph ( ) )