checking.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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 sys
  7. import traceback
  8. UNKNOWN = 0
  9. SUCCESS = 1
  10. FAILURE = 2
  11. ERROR = 3
  12. _result_to_str = {UNKNOWN: 'Inconnu',
  13. SUCCESS: 'Succès',
  14. FAILURE: 'Echec',
  15. ERROR: 'Erreur'}
  16. def _linenumber(m):
  17. try:
  18. _, line_no = inspect.findsource(m)
  19. return line_no
  20. except AttributeError:
  21. return -1
  22. class TestError():
  23. def __init__(self, message, info = {}, critical=False):
  24. self.message = message
  25. self.info = info
  26. self.critical = critical
  27. def __repr__(self):
  28. return f"TestError[message='{self.message}'; info={self.info}; critical={self.critical}]"
  29. class TestResult():
  30. def __init__(self, test):
  31. self._test = test
  32. self._name = ""
  33. self._status = SUCCESS
  34. self.errors = []
  35. self._exc_info = None
  36. @property
  37. def name(self):
  38. return self._name or self._test.__name__[5:]
  39. @property
  40. def title(self):
  41. return self._test.__doc__.split("\n")[0].strip()
  42. @property
  43. def description(self):
  44. return self._test.__doc__.strip()
  45. @property
  46. def status(self):
  47. return self._status
  48. @property
  49. def status_str(self):
  50. return _result_to_str[self._status]
  51. def __repr__(self):
  52. return f"TestResult[title='{self.title}'; status={self.status}; name={self.name}; method={self._test.__name__}; errors_count={len(self.errors)}]"
  53. def log_error(self, message, info={}, critical=False):
  54. self._status = FAILURE
  55. error = TestError(message, info, critical)
  56. self.errors.append(error)
  57. def handle_exception(self, exc_info):
  58. self._status = ERROR
  59. typ, value, trace = exc_info
  60. error = TestError("Une erreur inconnue s'est produite, veuillez consulter les fichiers de journalisation.",
  61. {"exc_info": "{}\n{}\n{}".format(typ.__name__, value, ''.join(traceback.format_tb(trace)))})
  62. self.errors.append(error)
  63. def grouped_errors(self):
  64. res = {}
  65. for err in self.errors:
  66. if not err.message in res:
  67. res[err.message] = []
  68. res[err.message].append(err)
  69. return res
  70. class BaseChecker():
  71. def __init__(self):
  72. self._test_running = None
  73. def setUp(self):
  74. pass
  75. def tearDown(self):
  76. pass
  77. def log_error(self, message, **info):
  78. self._test_running.log_error(message, info)
  79. def log_critical(self, message, **info):
  80. self._test_running.log_error(f"[CRITIQUE] {message}", info, critical=True)
  81. def run(self):
  82. tests_results = []
  83. tests = sorted([m for _, m in inspect.getmembers(self, predicate=inspect.ismethod) if m.__name__[:5] == 'test_'], key=_linenumber)
  84. for test in tests:
  85. result = TestResult(test)
  86. self._test_running = result
  87. self.setUp()
  88. try:
  89. test()
  90. except:
  91. result.handle_exception(sys.exc_info())
  92. self.tearDown()
  93. tests_results.append(result)
  94. if any(err.critical for err in result.errors):
  95. break
  96. return tests_results
  97. if __name__ == '__main__':
  98. class ExampleChecker(BaseChecker):
  99. def test_c(self):
  100. """ Test 1 """
  101. for i in range(10):
  102. self.log_error(f"error-{i}", i=i)
  103. def test_b(self):
  104. """ Test 2
  105. some longer description """
  106. raise Exception("bla bla")
  107. ch = ExampleChecker()
  108. results = ch.run()
  109. for r in results:
  110. print(r)
  111. for e in r.errors:
  112. print(e)