mozunit.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from unittest import TextTestRunner as _TestRunner, TestResult as _TestResult
  5. import unittest
  6. import inspect
  7. from StringIO import StringIO
  8. import os
  9. import sys
  10. '''Helper to make python unit tests report the way that the Mozilla
  11. unit test infrastructure expects tests to report.
  12. Usage:
  13. import unittest
  14. import mozunit
  15. if __name__ == '__main__':
  16. mozunit.main()
  17. '''
  18. class _MozTestResult(_TestResult):
  19. def __init__(self, stream, descriptions):
  20. _TestResult.__init__(self)
  21. self.stream = stream
  22. self.descriptions = descriptions
  23. def getDescription(self, test):
  24. if self.descriptions:
  25. return test.shortDescription() or str(test)
  26. else:
  27. return str(test)
  28. def printStatus(self, status, test, message=''):
  29. line = "{status} | {file} | {klass}.{test}{sep}{message}".format(
  30. status=status,
  31. file=inspect.getfile(test.__class__),
  32. klass=test.__class__.__name__,
  33. test=test._testMethodName,
  34. sep=', ' if message else '',
  35. message=message,
  36. )
  37. self.stream.writeln(line)
  38. def addSuccess(self, test):
  39. _TestResult.addSuccess(self, test)
  40. self.printStatus('TEST-PASS', test)
  41. def addSkip(self, test, reason):
  42. _TestResult.addSkip(self, test, reason)
  43. self.printStatus('TEST-SKIP', test)
  44. def addExpectedFailure(self, test, err):
  45. _TestResult.addExpectedFailure(self, test, err)
  46. self.printStatus('TEST-KNOWN-FAIL', test)
  47. def addUnexpectedSuccess(self, test):
  48. _TestResult.addUnexpectedSuccess(self, test)
  49. self.printStatus('TEST-UNEXPECTED-PASS', test)
  50. def addError(self, test, err):
  51. _TestResult.addError(self, test, err)
  52. self.printFail(test, err)
  53. self.stream.writeln("ERROR: {0}".format(self.getDescription(test)))
  54. self.stream.writeln(self.errors[-1][1])
  55. def addFailure(self, test, err):
  56. _TestResult.addFailure(self, test, err)
  57. self.printFail(test,err)
  58. self.stream.writeln("FAIL: {0}".format(self.getDescription(test)))
  59. self.stream.writeln(self.failures[-1][1])
  60. def printFail(self, test, err):
  61. exctype, value, tb = err
  62. message = value.message.splitlines()[0] if value.message else 'NO MESSAGE'
  63. # Skip test runner traceback levels
  64. while tb and self._is_relevant_tb_level(tb):
  65. tb = tb.tb_next
  66. if tb:
  67. _, ln, _ = inspect.getframeinfo(tb)[:3]
  68. message = 'line {0}: {1}'.format(ln, message)
  69. self.printStatus("TEST-UNEXPECTED-FAIL", test, message)
  70. class MozTestRunner(_TestRunner):
  71. def _makeResult(self):
  72. return _MozTestResult(self.stream, self.descriptions)
  73. def run(self, test):
  74. result = self._makeResult()
  75. test(result)
  76. return result
  77. class MockedFile(StringIO):
  78. def __init__(self, context, filename, content = ''):
  79. self.context = context
  80. self.name = filename
  81. StringIO.__init__(self, content)
  82. def close(self):
  83. self.context.files[self.name] = self.getvalue()
  84. StringIO.close(self)
  85. def __enter__(self):
  86. return self
  87. def __exit__(self, type, value, traceback):
  88. self.close()
  89. def normcase(path):
  90. '''
  91. Normalize the case of `path`.
  92. Don't use `os.path.normcase` because that also normalizes forward slashes
  93. to backslashes on Windows.
  94. '''
  95. if sys.platform.startswith('win'):
  96. return path.lower()
  97. return path
  98. class MockedOpen(object):
  99. '''
  100. Context manager diverting the open builtin such that opening files
  101. can open "virtual" file instances given when creating a MockedOpen.
  102. with MockedOpen({'foo': 'foo', 'bar': 'bar'}):
  103. f = open('foo', 'r')
  104. will thus open the virtual file instance for the file 'foo' to f.
  105. MockedOpen also masks writes, so that creating or replacing files
  106. doesn't touch the file system, while subsequently opening the file
  107. will return the recorded content.
  108. with MockedOpen():
  109. f = open('foo', 'w')
  110. f.write('foo')
  111. self.assertRaises(Exception,f.open('foo', 'r'))
  112. '''
  113. def __init__(self, files = {}):
  114. self.files = {}
  115. for name, content in files.iteritems():
  116. self.files[normcase(os.path.abspath(name))] = content
  117. def __call__(self, name, mode = 'r'):
  118. absname = normcase(os.path.abspath(name))
  119. if 'w' in mode:
  120. file = MockedFile(self, absname)
  121. elif absname in self.files:
  122. file = MockedFile(self, absname, self.files[absname])
  123. elif 'a' in mode:
  124. file = MockedFile(self, absname, self.open(name, 'r').read())
  125. else:
  126. file = self.open(name, mode)
  127. if 'a' in mode:
  128. file.seek(0, os.SEEK_END)
  129. return file
  130. def __enter__(self):
  131. import __builtin__
  132. self.open = __builtin__.open
  133. self._orig_path_exists = os.path.exists
  134. self._orig_path_isdir = os.path.isdir
  135. self._orig_path_isfile = os.path.isfile
  136. __builtin__.open = self
  137. os.path.exists = self._wrapped_exists
  138. os.path.isdir = self._wrapped_isdir
  139. os.path.isfile = self._wrapped_isfile
  140. def __exit__(self, type, value, traceback):
  141. import __builtin__
  142. __builtin__.open = self.open
  143. os.path.exists = self._orig_path_exists
  144. os.path.isdir = self._orig_path_isdir
  145. os.path.isfile = self._orig_path_isfile
  146. def _wrapped_exists(self, p):
  147. return (self._wrapped_isfile(p) or
  148. self._wrapped_isdir(p) or
  149. self._orig_path_exists(p))
  150. def _wrapped_isfile(self, p):
  151. p = normcase(p)
  152. if p in self.files:
  153. return True
  154. abspath = normcase(os.path.abspath(p))
  155. if abspath in self.files:
  156. return True
  157. return self._orig_path_isfile(p)
  158. def _wrapped_isdir(self, p):
  159. p = normcase(p)
  160. p = p if p.endswith(('/', '\\')) else p + os.sep
  161. if any(f.startswith(p) for f in self.files):
  162. return True
  163. abspath = normcase(os.path.abspath(p) + os.sep)
  164. if any(f.startswith(abspath) for f in self.files):
  165. return True
  166. return self._orig_path_exists(p)
  167. def main(*args, **kwargs):
  168. unittest.main(testRunner=MozTestRunner(), *args, **kwargs)