checking.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. '''
  2. A simplified version of the unittest module, adapted to run unit tests on data sets instead of code.
  3. @author: olivier.massot, 2018
  4. '''
  5. import inspect
  6. import re
  7. import sys
  8. import traceback
  9. UNKNOWN = 0
  10. SUCCESS = 1
  11. FAILURE = 2
  12. ERROR = 3
  13. _result_to_str = {UNKNOWN: 'Inconnu',
  14. SUCCESS: 'Succès',
  15. FAILURE: 'Echec',
  16. ERROR: 'Erreur'}
  17. def _linenumber(m):
  18. try:
  19. _, line_no = inspect.findsource(m)
  20. return line_no
  21. except AttributeError:
  22. return -1
  23. class CheckingException(Exception):
  24. pass
  25. class TestError():
  26. def __init__(self, message, info = {}, critical=False):
  27. self.message = message
  28. self.info = info
  29. self.critical = critical
  30. def __repr__(self):
  31. return f"TestError[message='{self.message}'; info={self.info}; critical={self.critical}]"
  32. class TestResult():
  33. def __init__(self, test):
  34. self._test = test
  35. self._name = ""
  36. self._status = SUCCESS
  37. self.errors = []
  38. self._exc_info = None
  39. @property
  40. def name(self):
  41. return self._name or self._test.__name__[5:]
  42. @property
  43. def title(self):
  44. try:
  45. return self._test.__doc__.split("\n")[0].strip()
  46. except AttributeError:
  47. return self.name
  48. @property
  49. def description(self):
  50. try:
  51. return re.sub(" +", " ", self._test.__doc__.strip(), flags=re.MULTILINE) #@UndefinedVariable
  52. except AttributeError:
  53. return ""
  54. @property
  55. def status(self):
  56. return self._status
  57. @property
  58. def status_str(self):
  59. return _result_to_str[self._status]
  60. def __repr__(self):
  61. return f"TestResult[title='{self.title}'; status={self.status}; name={self.name}; method={self._test.__name__}; errors_count={len(self.errors)}]"
  62. def log_error(self, message, info={}, critical=False):
  63. self._status = FAILURE
  64. error = TestError(message, info, critical)
  65. self.errors.append(error)
  66. def log_exception(self, message, info={}):
  67. self._status = ERROR
  68. error = TestError(message, info)
  69. self.errors.append(error)
  70. def handle_exception(self, exc_info):
  71. typ, value, trace = exc_info
  72. self.log_exception("Une erreur s'est produite: {}".format(typ.__name__),
  73. {"exc_info": "{}\n{}\n{}".format(typ.__name__, value, ''.join(traceback.format_tb(trace)))})
  74. class Comlink():
  75. def _started_test(self, test):
  76. pass
  77. def _ended_test(self, test):
  78. pass
  79. class BaseChecker():
  80. def __init__(self):
  81. self._test_running = None
  82. self.comlink = Comlink()
  83. self.tests = sorted([m for _, m in inspect.getmembers(self, predicate=inspect.ismethod) if m.__name__[:5] == 'test_'], key=_linenumber)
  84. def setUp(self):
  85. pass
  86. def tearDown(self):
  87. pass
  88. def log_error(self, message, **info):
  89. self._test_running.log_error(message, info)
  90. def log_critical(self, message, **info):
  91. self._test_running.log_error(f"[CRITIQUE] {message}", info, critical=True)
  92. def run(self, rxfilter="", dry=False):
  93. # 'rxfilter' allow to filter the tests to run with a regex
  94. # if 'dry' is set to True, setUp and tearDown are skipped
  95. # both are used for testing purpose
  96. tests_results = []
  97. for test in self.tests:
  98. if rxfilter and not re.fullmatch(rxfilter, test.__name__, re.IGNORECASE): #@UndefinedVariable
  99. continue
  100. result = TestResult(test)
  101. self._test_running = result
  102. self.comlink._started_test(result)
  103. try:
  104. if not dry:
  105. self.setUp()
  106. test()
  107. if not dry:
  108. self.tearDown()
  109. except CheckingException as e:
  110. result.log_exception(str(e))
  111. except:
  112. result.handle_exception(sys.exc_info())
  113. tests_results.append(result)
  114. self.comlink._ended_test(result)
  115. if any(err.critical for err in result.errors):
  116. break
  117. return tests_results
  118. if __name__ == '__main__':
  119. class ExampleChecker(BaseChecker):
  120. def test_c(self):
  121. """ Test 1 """
  122. for i in range(10):
  123. self.log_error(f"error-{i}", i=i)
  124. def test_b(self):
  125. """ Test 2
  126. some longer description """
  127. raise Exception("bla bla")
  128. ch = ExampleChecker()
  129. results = ch.run()
  130. for r in results:
  131. print(r)
  132. for e in r.errors:
  133. print(e)