23 """Support code for running unit tests""" 24 from __future__
import print_function
25 from __future__
import division
26 from builtins
import zip
27 from builtins
import range
41 __all__ = [
"init",
"run",
"MemoryTestCase",
"ExecutablesTestCase",
"getTempFilePath",
42 "TestCase",
"assertFloatsAlmostEqual",
"assertFloatsNotEqual",
"assertFloatsEqual"]
65 def _get_open_files():
66 """Return a set containing the list of files currently open in this 72 Set containing the list of open files. 80 """Initialize the memory tester and file descriptor leak tester.""" 84 memId0 = dafBase.Citizen.getNextMemId()
86 open_files = _get_open_files()
89 def run(suite, exit=True):
90 """Run a test suite and report the test return status to caller or shell. 92 .. note:: Deprecated in 13_0 93 Use `unittest.main()` instead, which automatically detects 94 all tests in a test case and does not require a test suite. 98 suite : `unittest.TestSuite` 100 exit : `bool`, optional 101 If `True`, Python process will exit with the test exit status. 106 If ``exit`` is `False`, will return 0 if the tests passed, or 1 if 110 warnings.warn(
"lsst.utils.tests.run() is deprecated; please use unittest.main() instead",
111 DeprecationWarning, stacklevel=2)
113 if unittest.TextTestRunner().
run(suite).wasSuccessful():
125 """Sort supplied test suites such that MemoryTestCases are at the end. 127 `lsst.utils.tests.MemoryTestCase` tests should always run after any other 133 Sequence of test suites. 137 suite : `unittest.TestSuite` 138 A combined `~unittest.TestSuite` with 139 `~lsst.utils.tests.MemoryTestCase` at the end. 142 suite = unittest.TestSuite()
144 for test_suite
in tests:
151 for method
in test_suite:
152 bases = inspect.getmro(method.__class__)
154 if bases
is not None and MemoryTestCase
in bases:
155 memtests.append(test_suite)
157 suite.addTests(test_suite)
159 if isinstance(test_suite, MemoryTestCase):
160 memtests.append(test_suite)
162 suite.addTest(test_suite)
163 suite.addTests(memtests)
174 unittest.defaultTestLoader.suiteClass = suiteClassWrapper
178 """Check for memory leaks since memId0 was allocated""" 185 """Reset the leak counter when the tests have been completed""" 189 """Check for memory leaks in the preceding tests""" 192 global memId0, nleakPrintMax
193 nleak = dafBase.Citizen.census(0, memId0)
195 plural =
"s" if nleak != 1
else "" 196 print(
"\n%d Object%s leaked:" % (nleak, plural))
198 if nleak <= nleakPrintMax:
199 print(dafBase.Citizen.census(memId0))
201 census = dafBase.Citizen.census()
203 for i
in range(nleakPrintMax - 1, -1, -1):
204 print(census[i].repr())
206 self.fail(
"Leaked %d block%s" % (nleak, plural))
209 """Check if any file descriptors are open since init() called.""" 211 self.skipTest(
"Unable to test file descriptor leaks. psutil unavailable.")
214 now_open = _get_open_files()
217 now_open =
set(f
for f
in now_open
if not f.endswith(
".car")
and 218 not f.startswith(
"/proc/")
and 219 not f.endswith(
".ttf")
and 220 not (f.startswith(
"/var/lib/")
and f.endswith(
"/passwd"))
and 221 not f.endswith(
"astropy.log"))
223 diff = now_open.difference(open_files)
226 print(
"File open: %s" % f)
227 self.fail(
"Failed to close %d file%s" % (len(diff),
"s" if len(diff) != 1
else ""))
231 """Test that executables can be run and return good status. 233 The test methods are dynamically created. Callers 234 must subclass this class in their own test file and invoke 235 the create_executable_tests() class method to register the tests. 237 TESTS_DISCOVERED = -1
241 """Abort testing if automated test creation was enabled and 242 no tests were found.""" 245 raise Exception(
"No executables discovered.")
248 """This test exists to ensure that there is at least one test to be 249 executed. This allows the test runner to trigger the class set up 250 machinery to test whether there are some executables to test.""" 254 """Check an executable runs and returns good status. 256 Prints output to standard out. On bad exit status the test 257 fails. If the executable can not be located the test is skipped. 262 Path to an executable. ``root_dir`` is not used if this is an 264 root_dir : `str`, optional 265 Directory containing executable. Ignored if `None`. 266 args : `list` or `tuple`, optional 267 Arguments to be provided to the executable. 268 msg : `str`, optional 269 Message to use when the test fails. Can be `None` for default 275 The executable did not return 0 exit status. 278 if root_dir
is not None and not os.path.isabs(executable):
279 executable = os.path.join(root_dir, executable)
282 sp_args = [executable]
283 argstr =
"no arguments" 286 argstr =
'arguments "' +
" ".join(args) +
'"' 288 print(
"Running executable '{}' with {}...".
format(executable, argstr))
289 if not os.path.exists(executable):
290 self.skipTest(
"Executable {} is unexpectedly missing".
format(executable))
293 output = subprocess.check_output(sp_args)
294 except subprocess.CalledProcessError
as e:
296 failmsg =
"Bad exit status from '{}': {}".
format(executable, e.returncode)
297 print(output.decode(
'utf-8'))
304 def _build_test_method(cls, executable, root_dir):
305 """Build a test method and attach to class. 307 A test method is created for the supplied excutable located 308 in the supplied root directory. This method is attached to the class 309 so that the test runner will discover the test and run it. 314 The class in which to create the tests. 316 Name of executable. Can be absolute path. 318 Path to executable. Not used if executable path is absolute. 320 if not os.path.isabs(executable):
321 executable = os.path.abspath(os.path.join(root_dir, executable))
324 test_name =
"test_exe_" + executable.replace(
"/",
"_")
327 def test_executable_runs(*args):
329 self.assertExecutable(executable)
332 test_executable_runs.__name__ = test_name
333 setattr(cls, test_name, test_executable_runs)
337 """Discover executables to test and create corresponding test methods. 339 Scans the directory containing the supplied reference file 340 (usually ``__file__`` supplied from the test class) to look for 341 executables. If executables are found a test method is created 342 for each one. That test method will run the executable and 343 check the returned value. 345 Executable scripts with a ``.py`` extension and shared libraries 346 are ignored by the scanner. 348 This class method must be called before test discovery. 353 Path to a file within the directory to be searched. 354 If the files are in the same location as the test file, then 355 ``__file__`` can be used. 356 executables : `list` or `tuple`, optional 357 Sequence of executables that can override the automated 358 detection. If an executable mentioned here is not found, a 359 skipped test will be created for it, rather than a failed 364 >>> cls.create_executable_tests(__file__) 368 ref_dir = os.path.abspath(os.path.dirname(ref_file))
370 if executables
is None:
373 for root, dirs, files
in os.walk(ref_dir):
376 if not f.endswith(
".py")
and not f.endswith(
".so"):
377 full_path = os.path.join(root, f)
378 if os.access(full_path, os.X_OK):
379 executables.append(full_path)
388 for e
in executables:
392 @contextlib.contextmanager
394 """Return a path suitable for a temporary file and try to delete the 397 If the with block completes successfully then the file is deleted, 398 if possible; failure results in a printed warning. 399 If a file is remains when it should not, a RuntimeError exception is 400 raised. This exception is also raised if a file is not present on context 401 manager exit when one is expected to exist. 402 If the block exits with an exception the file if left on disk so it can be 403 examined. The file name has a random component such that nested context 404 managers can be used with the same file suffix. 410 File name extension, e.g. ``.fits``. 411 expectOutput : `bool`, optional 412 If `True`, a file should be created within the context manager. 413 If `False`, a file should not be present when the context manager 419 Path for a temporary file. The path is a combination of the caller's 420 file path and the name of the top-level function 426 # file tests/testFoo.py 428 import lsst.utils.tests 429 class FooTestCase(unittest.TestCase): 430 def testBasics(self): 434 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 435 # if tests/.tests exists then 436 # tmpFile = "tests/.tests/testFoo_testBasics.fits" 437 # otherwise tmpFile = "testFoo_testBasics.fits" 439 # at the end of this "with" block the path tmpFile will be 440 # deleted, but only if the file exists and the "with" 441 # block terminated normally (rather than with an exception) 444 stack = inspect.stack()
446 for i
in range(2, len(stack)):
447 frameInfo = inspect.getframeinfo(stack[i][0])
449 callerFilePath = frameInfo.filename
450 callerFuncName = frameInfo.function
451 elif callerFilePath == frameInfo.filename:
453 callerFuncName = frameInfo.function
457 callerDir, callerFileNameWithExt = os.path.split(callerFilePath)
458 callerFileName = os.path.splitext(callerFileNameWithExt)[0]
459 outDir = os.path.join(callerDir,
".tests")
460 if not os.path.isdir(outDir):
462 prefix =
"%s_%s-" % (callerFileName, callerFuncName)
463 outPath = tempfile.mktemp(dir=outDir, suffix=ext, prefix=prefix)
464 if os.path.exists(outPath):
467 warnings.warn(
"Unexpectedly found pre-existing tempfile named %r" % (outPath,),
476 fileExists = os.path.exists(outPath)
479 raise RuntimeError(
"Temp file expected named {} but none found".
format(outPath))
482 raise RuntimeError(
"Unexpectedly discovered temp file named {}".
format(outPath))
489 warnings.warn(
"Warning: could not remove file %r: %s" % (outPath, e), stacklevel=3)
493 """Subclass of unittest.TestCase that adds some custom assertions for 499 """A decorator to add a free function to our custom TestCase class, while also 500 making it available as a free function. 502 setattr(TestCase, func.__name__, func)
508 """.. note:: Deprecated in 12_0""" 509 warnings.warn(
"assertRaisesLsstCpp is deprecated; please just use TestCase.assertRaises",
510 DeprecationWarning, stacklevel=2)
511 return testcase.assertRaises(excClass, callableObj, *args, **kwargs)
515 """Decorator to enter the debugger when there's an uncaught exception 517 To use, just slap a ``@debugger()`` on your function. 519 You may provide specific exception classes to catch as arguments to 520 the decorator function, e.g., 521 ``@debugger(RuntimeError, NotImplementedError)``. 522 This defaults to just `AssertionError`, for use on `unittest.TestCase` 525 Code provided by "Rosh Oxymoron" on StackOverflow: 526 http://stackoverflow.com/questions/4398967/python-unit-testing-automatically-running-the-debugger-when-a-test-fails 530 Consider using ``pytest --pdb`` instead of this decorator. 533 exceptions = (AssertionError, )
537 def wrapper(*args, **kwargs):
539 return f(*args, **kwargs)
543 pdb.post_mortem(sys.exc_info()[2])
549 """Plot the comparison of two 2-d NumPy arrays. 553 lhs : `numpy.ndarray` 554 LHS values to compare; a 2-d NumPy array 555 rhs : `numpy.ndarray` 556 RHS values to compare; a 2-d NumPy array 557 bad : `numpy.ndarray` 558 A 2-d boolean NumPy array of values to emphasize in the plots 559 diff : `numpy.ndarray` 560 difference array; a 2-d NumPy array, or None to show lhs-rhs 562 Filename to save the plot to. If None, the plot will be displayed in 567 This method uses `matplotlib` and imports it internally; it should be 568 wrapped in a try/except block within packages that do not depend on 569 `matplotlib` (including `~lsst.utils`). 571 from matplotlib
import pyplot
577 badImage = numpy.zeros(bad.shape + (4,), dtype=numpy.uint8)
578 badImage[:, :, 0] = 255
579 badImage[:, :, 1] = 0
580 badImage[:, :, 2] = 0
581 badImage[:, :, 3] = 255*bad
582 vmin1 = numpy.minimum(numpy.min(lhs), numpy.min(rhs))
583 vmax1 = numpy.maximum(numpy.max(lhs), numpy.max(rhs))
584 vmin2 = numpy.min(diff)
585 vmax2 = numpy.max(diff)
586 for n, (image, title)
in enumerate([(lhs,
"lhs"), (rhs,
"rhs"), (diff,
"diff")]):
587 pyplot.subplot(2, 3, n + 1)
588 im1 = pyplot.imshow(image, cmap=pyplot.cm.gray, interpolation=
'nearest', origin=
'lower',
589 vmin=vmin1, vmax=vmax1)
591 pyplot.imshow(badImage, alpha=0.2, interpolation=
'nearest', origin=
'lower')
594 pyplot.subplot(2, 3, n + 4)
595 im2 = pyplot.imshow(image, cmap=pyplot.cm.gray, interpolation=
'nearest', origin=
'lower',
596 vmin=vmin2, vmax=vmax2)
598 pyplot.imshow(badImage, alpha=0.2, interpolation=
'nearest', origin=
'lower')
601 pyplot.subplots_adjust(left=0.05, bottom=0.05, top=0.92, right=0.75, wspace=0.05, hspace=0.05)
602 cax1 = pyplot.axes([0.8, 0.55, 0.05, 0.4])
603 pyplot.colorbar(im1, cax=cax1)
604 cax2 = pyplot.axes([0.8, 0.05, 0.05, 0.4])
605 pyplot.colorbar(im2, cax=cax2)
607 pyplot.savefig(plotFileName)
614 atol=sys.float_info.epsilon, relTo=None,
615 printFailures=True, plotOnFailure=False,
616 plotFileName=None, invert=False, msg=None):
617 """Highly-configurable floating point comparisons for scalars and arrays. 619 The test assertion will fail if all elements ``lhs`` and ``rhs`` are not 620 equal to within the tolerances specified by ``rtol`` and ``atol``. 621 More precisely, the comparison is: 623 ``abs(lhs - rhs) <= relTo*rtol OR abs(lhs - rhs) <= atol`` 625 If ``rtol`` or ``atol`` is `None`, that term in the comparison is not 628 When not specified, ``relTo`` is the elementwise maximum of the absolute 629 values of ``lhs`` and ``rhs``. If set manually, it should usually be set 630 to either ``lhs`` or ``rhs``, or a scalar value typical of what is 635 testCase : `unittest.TestCase` 636 Instance the test is part of. 637 lhs : scalar or array-like 638 LHS value(s) to compare; may be a scalar or array-like of any 640 rhs : scalar or array-like 641 RHS value(s) to compare; may be a scalar or array-like of any 643 rtol : `float`, optional 644 Relative tolerance for comparison; defaults to double-precision 646 atol : `float`, optional 647 Absolute tolerance for comparison; defaults to double-precision 649 relTo : `float`, optional 650 Value to which comparison with rtol is relative. 651 printFailures : `bool`, optional 652 Upon failure, print all inequal elements as part of the message. 653 plotOnFailure : `bool`, optional 654 Upon failure, plot the originals and their residual with matplotlib. 655 Only 2-d arrays are supported. 656 plotFileName : `str`, optional 657 Filename to save the plot to. If `None`, the plot will be displayed in 659 invert : `bool`, optional 660 If `True`, invert the comparison and fail only if any elements *are* 661 equal. Used to implement `~lsst.utils.tests.assertFloatsNotEqual`, 662 which should generally be used instead for clarity. 663 msg : `str`, optional 664 String to append to the error message when assert fails. 669 The values are not almost equal. 671 if not numpy.isfinite(lhs).
all():
672 testCase.fail(
"Non-finite values in lhs")
673 if not numpy.isfinite(rhs).
all():
674 testCase.fail(
"Non-finite values in rhs")
676 absDiff = numpy.abs(lhs - rhs)
679 relTo = numpy.maximum(numpy.abs(lhs), numpy.abs(rhs))
681 relTo = numpy.abs(relTo)
682 bad = absDiff > rtol*relTo
684 bad = numpy.logical_and(bad, absDiff > atol)
687 raise ValueError(
"rtol and atol cannot both be None")
689 failed = numpy.any(bad)
692 bad = numpy.logical_not(bad)
694 failStr =
"are the same" 700 if numpy.isscalar(bad):
702 errMsg = [
"%s %s %s; diff=%s with atol=%s" 703 % (lhs, cmpStr, rhs, absDiff, atol)]
705 errMsg = [
"%s %s %s; diff=%s/%s=%s with rtol=%s" 706 % (lhs, cmpStr, rhs, absDiff, relTo, absDiff/relTo, rtol)]
708 errMsg = [
"%s %s %s; diff=%s/%s=%s with rtol=%s, atol=%s" 709 % (lhs, cmpStr, rhs, absDiff, relTo, absDiff/relTo, rtol, atol)]
711 errMsg = [
"%d/%d elements %s with rtol=%s, atol=%s" 712 % (bad.sum(), bad.size, failStr, rtol, atol)]
714 if len(lhs.shape) != 2
or len(rhs.shape) != 2:
715 raise ValueError(
"plotOnFailure is only valid for 2-d arrays")
717 plotImageDiff(lhs, rhs, bad, diff=diff, plotFileName=plotFileName)
719 errMsg.append(
"Failure plot requested but matplotlib could not be imported.")
724 if numpy.isscalar(relTo):
725 relTo = numpy.ones(bad.shape, dtype=float) * relTo
726 if numpy.isscalar(lhs):
727 lhs = numpy.ones(bad.shape, dtype=float) * lhs
728 if numpy.isscalar(rhs):
729 rhs = numpy.ones(bad.shape, dtype=float) * rhs
731 for a, b, diff
in zip(lhs[bad], rhs[bad], absDiff[bad]):
732 errMsg.append(
"%s %s %s (diff=%s)" % (a, cmpStr, b, diff))
734 for a, b, diff, rel
in zip(lhs[bad], rhs[bad], absDiff[bad], relTo[bad]):
735 errMsg.append(
"%s %s %s (diff=%s/%s=%s)" % (a, cmpStr, b, diff, rel, diff/rel))
739 testCase.assertFalse(failed, msg=
"\n".join(errMsg))
744 """Fail a test if the given floating point values are equal to within the 747 See `~lsst.utils.tests.assertFloatsAlmostEqual` (called with 748 ``rtol=atol=0``) for more information. 752 testCase : `unittest.TestCase` 753 Instance the test is part of. 754 lhs : scalar or array-like 755 LHS value(s) to compare; may be a scalar or array-like of any 757 rhs : scalar or array-like 758 RHS value(s) to compare; may be a scalar or array-like of any 764 The values are almost equal. 772 Assert that lhs == rhs (both numeric types, whether scalar or array). 774 See `~lsst.utils.tests.assertFloatsAlmostEqual` (called with 775 ``rtol=atol=0``) for more information. 779 testCase : `unittest.TestCase` 780 Instance the test is part of. 781 lhs : scalar or array-like 782 LHS value(s) to compare; may be a scalar or array-like of any 784 rhs : scalar or array-like 785 RHS value(s) to compare; may be a scalar or array-like of any 791 The values are not equal. 798 """.. note:: Deprecated in 12_0""" 799 warnings.warn(
"assertClose is deprecated; please use TestCase.assertFloatsAlmostEqual",
800 DeprecationWarning, stacklevel=2)
806 """.. note:: Deprecated in 12_0""" 807 warnings.warn(
"assertNotClose is deprecated; please use TestCase.assertFloatsNotEqual",
808 DeprecationWarning, stacklevel=2)
def suiteClassWrapper(tests)
def assertExecutable(self, executable, root_dir=None, args=None, msg=None)
def assertFloatsEqual(testCase, lhs, rhs, kwargs)
def plotImageDiff(lhs, rhs, bad=None, diff=None, plotFileName=None)
def assertClose(args, kwargs)
daf::base::PropertySet * set
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def _build_test_method(cls, executable, root_dir)
def assertFloatsAlmostEqual(testCase, lhs, rhs, rtol=sys.float_info.epsilon, atol=sys.float_info.epsilon, relTo=None, printFailures=True, plotOnFailure=False, plotFileName=None, invert=False, msg=None)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def assertFloatsNotEqual(testCase, lhs, rhs, kwds)
def run(suite, exit=True)
def getTempFilePath(ext, expectOutput=True)
def assertNotClose(args, kwargs)
def create_executable_tests(cls, ref_file, executables=None)
def testFileDescriptorLeaks(self)
def assertRaisesLsstCpp(testcase, excClass, callableObj, args, kwargs)