Source code for code_aster.Utilities.Tester

# coding=utf-8
# --------------------------------------------------------------------
# Copyright (C) 1991 - 2026 - EDF - www.code-aster.org
# This file is part of code_aster.
#
# code_aster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# code_aster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with code_aster.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

""":py:mod:`Tester` --- Checking code_aster execution of testcases
***************************************************************

"""

import inspect
import re
import unittest
import unittest.case as case
from functools import partial, wraps

import numpy as np

# TODO use the logger object
# TODO tell the Helpers to increase the exit status in case of failure
#      (through the logger) ?

_print = partial(print, flush=True)


[docs]def addSuccess(method): """Decorator to wrap TestCase methods by calling writeResult""" @wraps(method) def wrapper(inst, *args, **kwds): """Wrapper""" # move 'msg' arguments from args to kwds if it exists sig = inspect.signature(method) args = list(args) for i, para in enumerate(sig.parameters): if i >= len(args): break if para == "msg": kwds["msg"] = args.pop(i) try: ret = method(inst, *args, **kwds) except AssertionError as exc: ret = None inst.writeResult(False, method.__name__, kwds.get("msg"), str(exc)) else: inst.writeResult(True, method.__name__, kwds.get("msg")) return ret return wrapper
[docs]def where(level=3): """Return the filename/line number where the test is called. Arguments: level (Optional[int]): Number of frames to rewind to find the caller. Defaults to 3 (1: *here*, 2: *write_xxx*, 3: *assertXxx*). Returns: (str, int): Filename and line number. """ filename, line_no = "not_found", 0 caller = inspect.currentframe() try: for _ in range(level): if not caller.f_back: break caller = caller.f_back filename = caller.f_code.co_filename line_no = caller.f_lineno finally: pass return f"{filename}#{line_no}"
[docs]class AssertRaisesContext(case._AssertRaisesContext): """Wrap Context of TestCase object"""
[docs] def __init__(self, expected, test_case, expected_regexp=None): self.writeResult = test_case.writeResult # these two lines already exist in __exit__ in python >= 2.7.9 if isinstance(expected_regexp, str): expected_regexp = re.compile(expected_regexp) super().__init__(expected, test_case, expected_regexp)
def __exit__(self, exc_type, exc_value, tb): comment = "" try: ret = super().__exit__(exc_type, exc_value, tb) if not ret: try: exc_name = exc_type.__name__ except AttributeError: exc_name = str(exc_type) raise AssertionError("unexpected exception raised: {0}".format(exc_name)) except AssertionError as exc: ret = False comment = str(exc) try: exc_name = self.expected.__name__ except AttributeError: exc_name = str(self.expected) self.writeResult(ret, exc_name, comment) # never fail return True
[docs]class TestCase(unittest.TestCase): """Similar to a unittest.TestCase Does not fail but print result OK/NOOK in the .resu file """
[docs] def __init__(self, methodName="runTest", silent=False): """Initialization""" self._silent = silent self._passed = 0 self._failure = 0 self._last_ok = True super().__init__("runTest")
@property def last_failed(self): """bool: Tell if the last test failed.""" return not self._last_ok
[docs] def runTest(self): """Does nothing""" pass
[docs] def printSummary(self): """Print a summary of the tests""" _print(("-" * 70)) count = self._passed + self._failure _print( ("Ran {0} tests, {1} passed, {2} in failure".format(count, self._passed, self._failure)) ) if self._failure: _print("\nNOOK\n") else: _print("\n OK \n")
[docs] def writeResult(self, ok, funcTest, msg, exc=None): """Write a message in the result file""" if self._silent: return exc = exc or "" msg = msg or "" s1 = " : " if exc else "" s2 = " : " if msg else "" here = where() self._last_ok = ok if ok: self._passed += 1 fmt = " OK {func:>16} passed{s2}{msg}" else: self._failure += 1 fmt = "NOOK {func:>16} failed{s1}{exc} - {here}" _print(fmt.format(func=funcTest, msg=msg, exc=exc, s1=s1, s2=s2, here=here))
# just use a derivated context class
[docs] def assertRaises(self, excClass, callableObj=None, *args, **kwargs): """Fail unless an exception of class excClass is raised""" context = AssertRaisesContext(excClass, self) if callableObj is None: return context with context: callableObj(*args, **kwargs)
[docs] def assertRaisesRegex( self, expected_exception, expected_regexp, callable_obj=None, *args, **kwargs ): """Asserts that the message in a raised exception matches a regexp.""" context = AssertRaisesContext(expected_exception, self, expected_regexp) if callable_obj is None: return context with context: callable_obj(*args, **kwargs)
[docs] def assertArrayEqual(self, actual, reference, rtol=1.0e-07, atol=1.0e-6): """Compare numpy arrays using isclose tolerance. If assertion fails a message will be printed detailling failing rows. Arguments: actual (numpy.ndarray): 1D or 2D reference (numpy.ndarray): 1D or 2D rtol (float): relative tolerance atol (float): absolute tolerance """ if not actual.dtype.fields: is_close_results = np.isclose(actual, reference, rtol=rtol, atol=atol) else: to_stack = [] for field in actual.dtype.fields: to_stack.append(np.isclose(actual[field], reference[field], rtol=rtol, atol=atol)) is_close_results = np.stack(to_stack, axis=-1) arrays_are_equal = np.all(is_close_results) if not arrays_are_equal: differences = ~is_close_results not_equal_rows = np.any(differences, axis=-1) not_equal_rows_actual = actual[not_equal_rows] not_equal_rows_reference = reference[not_equal_rows] msg = ( "Arrays are not equal, differing lines are printed bellow\n" f"actual: {not_equal_rows_actual}\n" f"reference: {not_equal_rows_reference}\n" ) else: msg = "Arrays are equal" self.assertTrue(arrays_are_equal, msg=msg)
def _add_assert_methods(cls): for meth in [ "assertAlmostEqual", "assertCountEqual", "assertDictContainsSubset", "assertDictEqual", "assertEqual", "assertFalse", "assertGreater", "assertGreaterEqual", "assertIn", "assertIs", "assertIsInstance", "assertIsNone", "assertIsNot", "assertIsNotNone", "assertLess", "assertLessEqual", "assertMultiLineEqual", "assertNotAlmostEqual", "assertNotEqual", "assertNotIn", "assertNotIsInstance", "assertNotRegex", "assertRegex", "assertSequenceEqual", "assertSetEqual", "assertTrue", "assertTupleEqual", ]: if not hasattr(unittest.TestCase, meth): continue setattr(cls, meth, addSuccess(getattr(unittest.TestCase, meth))) _add_assert_methods(TestCase)