lib.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors
  2. #
  3. # This module is part of GitDB and is released under
  4. # the New BSD License: http://www.opensource.org/licenses/bsd-license.php
  5. """Utilities used in ODB testing"""
  6. from gitdb import OStream
  7. import sys
  8. import random
  9. from array import array
  10. from io import BytesIO
  11. import glob
  12. import unittest
  13. import tempfile
  14. import shutil
  15. import os
  16. import gc
  17. import logging
  18. from functools import wraps
  19. #{ Bases
  20. class TestBase(unittest.TestCase):
  21. """Base class for all tests
  22. TestCase providing access to readonly repositories using the following member variables.
  23. * gitrepopath
  24. * read-only base path of the git source repository, i.e. .../git/.git
  25. """
  26. #{ Invvariants
  27. k_env_git_repo = "GITDB_TEST_GIT_REPO_BASE"
  28. #} END invariants
  29. @classmethod
  30. def setUpClass(cls):
  31. try:
  32. super().setUpClass()
  33. except AttributeError:
  34. pass
  35. cls.gitrepopath = os.environ.get(cls.k_env_git_repo)
  36. if not cls.gitrepopath:
  37. logging.info(
  38. "You can set the %s environment variable to a .git repository of your choice - defaulting to the gitdb repository", cls.k_env_git_repo)
  39. ospd = os.path.dirname
  40. cls.gitrepopath = os.path.join(ospd(ospd(ospd(__file__))), '.git')
  41. # end assure gitrepo is set
  42. assert cls.gitrepopath.endswith('.git')
  43. #} END bases
  44. #{ Decorators
  45. def with_rw_directory(func):
  46. """Create a temporary directory which can be written to, remove it if the
  47. test succeeds, but leave it otherwise to aid additional debugging"""
  48. def wrapper(self):
  49. path = tempfile.mktemp(prefix=func.__name__)
  50. os.mkdir(path)
  51. keep = False
  52. try:
  53. try:
  54. return func(self, path)
  55. except Exception:
  56. sys.stderr.write(f"Test {type(self).__name__}.{func.__name__} failed, output is at {path!r}\n")
  57. keep = True
  58. raise
  59. finally:
  60. # Need to collect here to be sure all handles have been closed. It appears
  61. # a windows-only issue. In fact things should be deleted, as well as
  62. # memory maps closed, once objects go out of scope. For some reason
  63. # though this is not the case here unless we collect explicitly.
  64. if not keep:
  65. gc.collect()
  66. shutil.rmtree(path)
  67. # END handle exception
  68. # END wrapper
  69. wrapper.__name__ = func.__name__
  70. return wrapper
  71. def with_packs_rw(func):
  72. """Function that provides a path into which the packs for testing should be
  73. copied. Will pass on the path to the actual function afterwards"""
  74. def wrapper(self, path):
  75. src_pack_glob = fixture_path('packs/*')
  76. copy_files_globbed(src_pack_glob, path, hard_link_ok=True)
  77. return func(self, path)
  78. # END wrapper
  79. wrapper.__name__ = func.__name__
  80. return wrapper
  81. #} END decorators
  82. #{ Routines
  83. def fixture_path(relapath=''):
  84. """:return: absolute path into the fixture directory
  85. :param relapath: relative path into the fixtures directory, or ''
  86. to obtain the fixture directory itself"""
  87. return os.path.join(os.path.dirname(__file__), 'fixtures', relapath)
  88. def copy_files_globbed(source_glob, target_dir, hard_link_ok=False):
  89. """Copy all files found according to the given source glob into the target directory
  90. :param hard_link_ok: if True, hard links will be created if possible. Otherwise
  91. the files will be copied"""
  92. for src_file in glob.glob(source_glob):
  93. if hard_link_ok and hasattr(os, 'link'):
  94. target = os.path.join(target_dir, os.path.basename(src_file))
  95. try:
  96. os.link(src_file, target)
  97. except OSError:
  98. shutil.copy(src_file, target_dir)
  99. # END handle cross device links ( and resulting failure )
  100. else:
  101. shutil.copy(src_file, target_dir)
  102. # END try hard link
  103. # END for each file to copy
  104. def make_bytes(size_in_bytes, randomize=False):
  105. """:return: string with given size in bytes
  106. :param randomize: try to produce a very random stream"""
  107. actual_size = size_in_bytes // 4
  108. producer = range(actual_size)
  109. if randomize:
  110. producer = list(producer)
  111. random.shuffle(producer)
  112. # END randomize
  113. a = array('i', producer)
  114. return a.tobytes()
  115. def make_object(type, data):
  116. """:return: bytes resembling an uncompressed object"""
  117. odata = "blob %i\0" % len(data)
  118. return odata.encode("ascii") + data
  119. def make_memory_file(size_in_bytes, randomize=False):
  120. """:return: tuple(size_of_stream, stream)
  121. :param randomize: try to produce a very random stream"""
  122. d = make_bytes(size_in_bytes, randomize)
  123. return len(d), BytesIO(d)
  124. #} END routines
  125. #{ Stream Utilities
  126. class DummyStream:
  127. def __init__(self):
  128. self.was_read = False
  129. self.bytes = 0
  130. self.closed = False
  131. def read(self, size):
  132. self.was_read = True
  133. self.bytes = size
  134. def close(self):
  135. self.closed = True
  136. def _assert(self):
  137. assert self.was_read
  138. class DeriveTest(OStream):
  139. def __init__(self, sha, type, size, stream, *args, **kwargs):
  140. self.myarg = kwargs.pop('myarg')
  141. self.args = args
  142. def _assert(self):
  143. assert self.args
  144. assert self.myarg
  145. #} END stream utilitiess