LSSTApplications  18.1.0
LSSTDataManagementBasePackage
tests.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 """Support code for running unit tests"""
24 
25 import contextlib
26 import gc
27 import inspect
28 import os
29 import subprocess
30 import sys
31 import unittest
32 import warnings
33 import numpy
34 import functools
35 import tempfile
36 
37 __all__ = ["init", "run", "MemoryTestCase", "ExecutablesTestCase", "getTempFilePath",
38  "TestCase", "assertFloatsAlmostEqual", "assertFloatsNotEqual", "assertFloatsEqual"]
39 
40 # File descriptor leak test will be skipped if psutil can not be imported
41 try:
42  import psutil
43 except ImportError:
44  psutil = None
45 
46 try:
47  import lsst.daf.base as dafBase
48 except ImportError:
49  dafBase = None
50 
51 try:
52  type(memId0)
53 except NameError:
54  memId0 = 0 # ignore leaked blocks with IDs before memId0
55  nleakPrintMax = 20 # maximum number of leaked blocks to print
56 
57 # Initialize the list of open files to an empty set
58 open_files = set()
59 
60 
61 def _get_open_files():
62  """Return a set containing the list of files currently open in this
63  process.
64 
65  Returns
66  -------
67  open_files : `set`
68  Set containing the list of open files.
69  """
70  if psutil is None:
71  return set()
72  return set(p.path for p in psutil.Process().open_files())
73 
74 
75 def init():
76  """Initialize the memory tester and file descriptor leak tester."""
77  global memId0
78  global open_files
79  if dafBase:
80  memId0 = dafBase.Citizen.getNextMemId() # used by MemoryTestCase
81  # Reset the list of open files
82  open_files = _get_open_files()
83 
84 
85 def run(suite, exit=True):
86  """Run a test suite and report the test return status to caller or shell.
87 
88  .. note:: Deprecated in 13_0
89  Use `unittest.main()` instead, which automatically detects
90  all tests in a test case and does not require a test suite.
91 
92  Parameters
93  ----------
94  suite : `unittest.TestSuite`
95  Test suite to run.
96  exit : `bool`, optional
97  If `True`, Python process will exit with the test exit status.
98 
99  Returns
100  -------
101  status : `int`
102  If ``exit`` is `False`, will return 0 if the tests passed, or 1 if
103  the tests failed.
104  """
105 
106  warnings.warn("lsst.utils.tests.run() is deprecated; please use unittest.main() instead",
107  DeprecationWarning, stacklevel=2)
108 
109  if unittest.TextTestRunner().run(suite).wasSuccessful():
110  status = 0
111  else:
112  status = 1
113 
114  if exit:
115  sys.exit(status)
116  else:
117  return status
118 
119 
120 def sort_tests(tests):
121  """Sort supplied test suites such that MemoryTestCases are at the end.
122 
123  `lsst.utils.tests.MemoryTestCase` tests should always run after any other
124  tests in the module.
125 
126  Parameters
127  ----------
128  tests : sequence
129  Sequence of test suites.
130 
131  Returns
132  -------
133  suite : `unittest.TestSuite`
134  A combined `~unittest.TestSuite` with
135  `~lsst.utils.tests.MemoryTestCase` at the end.
136  """
137 
138  suite = unittest.TestSuite()
139  memtests = []
140  for test_suite in tests:
141  try:
142  # Just test the first test method in the suite for MemoryTestCase
143  # Use loop rather than next as it is possible for a test class
144  # to not have any test methods and the Python community prefers
145  # for loops over catching a StopIteration exception.
146  bases = None
147  for method in test_suite:
148  bases = inspect.getmro(method.__class__)
149  break
150  if bases is not None and MemoryTestCase in bases:
151  memtests.append(test_suite)
152  else:
153  suite.addTests(test_suite)
154  except TypeError:
155  if isinstance(test_suite, MemoryTestCase):
156  memtests.append(test_suite)
157  else:
158  suite.addTest(test_suite)
159  suite.addTests(memtests)
160  return suite
161 
162 
163 def suiteClassWrapper(tests):
164  return unittest.TestSuite(sort_tests(tests))
165 
166 
167 # Replace the suiteClass callable in the defaultTestLoader
168 # so that we can reorder the test ordering. This will have
169 # no effect if no memory test cases are found.
170 unittest.defaultTestLoader.suiteClass = suiteClassWrapper
171 
172 
173 class MemoryTestCase(unittest.TestCase):
174  """Check for memory leaks since memId0 was allocated"""
175 
176  def setUp(self):
177  pass
178 
179  @classmethod
180  def tearDownClass(cls):
181  """Reset the leak counter when the tests have been completed"""
182  init()
183 
184  def testLeaks(self):
185  """Check for memory leaks in the preceding tests"""
186  if dafBase:
187  gc.collect()
188  global memId0, nleakPrintMax
189  nleak = dafBase.Citizen.census(0, memId0)
190  if nleak != 0:
191  plural = "s" if nleak != 1 else ""
192  print("\n%d Object%s leaked:" % (nleak, plural))
193 
194  if nleak <= nleakPrintMax:
195  print(dafBase.Citizen.census(memId0))
196  else:
197  census = dafBase.Citizen.census()
198  print("...")
199  for i in range(nleakPrintMax - 1, -1, -1):
200  print(census[i].repr())
201 
202  self.fail("Leaked %d block%s" % (nleak, plural))
203 
205  """Check if any file descriptors are open since init() called."""
206  if psutil is None:
207  self.skipTest("Unable to test file descriptor leaks. psutil unavailable.")
208  gc.collect()
209  global open_files
210  now_open = _get_open_files()
211 
212  # Some files are opened out of the control of the stack.
213  now_open = set(f for f in now_open if not f.endswith(".car") and
214  not f.startswith("/proc/") and
215  not f.endswith(".ttf") and
216  not (f.startswith("/var/lib/") and f.endswith("/passwd")) and
217  not f.endswith("astropy.log"))
218 
219  diff = now_open.difference(open_files)
220  if diff:
221  for f in diff:
222  print("File open: %s" % f)
223  self.fail("Failed to close %d file%s" % (len(diff), "s" if len(diff) != 1 else ""))
224 
225 
226 class ExecutablesTestCase(unittest.TestCase):
227  """Test that executables can be run and return good status.
228 
229  The test methods are dynamically created. Callers
230  must subclass this class in their own test file and invoke
231  the create_executable_tests() class method to register the tests.
232  """
233  TESTS_DISCOVERED = -1
234 
235  @classmethod
236  def setUpClass(cls):
237  """Abort testing if automated test creation was enabled and
238  no tests were found."""
239 
240  if cls.TESTS_DISCOVERED == 0:
241  raise Exception("No executables discovered.")
242 
243  def testSanity(self):
244  """This test exists to ensure that there is at least one test to be
245  executed. This allows the test runner to trigger the class set up
246  machinery to test whether there are some executables to test."""
247  pass
248 
249  def assertExecutable(self, executable, root_dir=None, args=None, msg=None):
250  """Check an executable runs and returns good status.
251 
252  Prints output to standard out. On bad exit status the test
253  fails. If the executable can not be located the test is skipped.
254 
255  Parameters
256  ----------
257  executable : `str`
258  Path to an executable. ``root_dir`` is not used if this is an
259  absolute path.
260  root_dir : `str`, optional
261  Directory containing executable. Ignored if `None`.
262  args : `list` or `tuple`, optional
263  Arguments to be provided to the executable.
264  msg : `str`, optional
265  Message to use when the test fails. Can be `None` for default
266  message.
267 
268  Raises
269  ------
270  AssertionError
271  The executable did not return 0 exit status.
272  """
273 
274  if root_dir is not None and not os.path.isabs(executable):
275  executable = os.path.join(root_dir, executable)
276 
277  # Form the argument list for subprocess
278  sp_args = [executable]
279  argstr = "no arguments"
280  if args is not None:
281  sp_args.extend(args)
282  argstr = 'arguments "' + " ".join(args) + '"'
283 
284  print("Running executable '{}' with {}...".format(executable, argstr))
285  if not os.path.exists(executable):
286  self.skipTest("Executable {} is unexpectedly missing".format(executable))
287  failmsg = None
288  try:
289  output = subprocess.check_output(sp_args)
290  except subprocess.CalledProcessError as e:
291  output = e.output
292  failmsg = "Bad exit status from '{}': {}".format(executable, e.returncode)
293  print(output.decode('utf-8'))
294  if failmsg:
295  if msg is None:
296  msg = failmsg
297  self.fail(msg)
298 
299  @classmethod
300  def _build_test_method(cls, executable, root_dir):
301  """Build a test method and attach to class.
302 
303  A test method is created for the supplied excutable located
304  in the supplied root directory. This method is attached to the class
305  so that the test runner will discover the test and run it.
306 
307  Parameters
308  ----------
309  cls : `object`
310  The class in which to create the tests.
311  executable : `str`
312  Name of executable. Can be absolute path.
313  root_dir : `str`
314  Path to executable. Not used if executable path is absolute.
315  """
316  if not os.path.isabs(executable):
317  executable = os.path.abspath(os.path.join(root_dir, executable))
318 
319  # Create the test name from the executable path.
320  test_name = "test_exe_" + executable.replace("/", "_")
321 
322  # This is the function that will become the test method
323  def test_executable_runs(*args):
324  self = args[0]
325  self.assertExecutable(executable)
326 
327  # Give it a name and attach it to the class
328  test_executable_runs.__name__ = test_name
329  setattr(cls, test_name, test_executable_runs)
330 
331  @classmethod
332  def create_executable_tests(cls, ref_file, executables=None):
333  """Discover executables to test and create corresponding test methods.
334 
335  Scans the directory containing the supplied reference file
336  (usually ``__file__`` supplied from the test class) to look for
337  executables. If executables are found a test method is created
338  for each one. That test method will run the executable and
339  check the returned value.
340 
341  Executable scripts with a ``.py`` extension and shared libraries
342  are ignored by the scanner.
343 
344  This class method must be called before test discovery.
345 
346  Parameters
347  ----------
348  ref_file : `str`
349  Path to a file within the directory to be searched.
350  If the files are in the same location as the test file, then
351  ``__file__`` can be used.
352  executables : `list` or `tuple`, optional
353  Sequence of executables that can override the automated
354  detection. If an executable mentioned here is not found, a
355  skipped test will be created for it, rather than a failed
356  test.
357 
358  Examples
359  --------
360  >>> cls.create_executable_tests(__file__)
361  """
362 
363  # Get the search directory from the reference file
364  ref_dir = os.path.abspath(os.path.dirname(ref_file))
365 
366  if executables is None:
367  # Look for executables to test by walking the tree
368  executables = []
369  for root, dirs, files in os.walk(ref_dir):
370  for f in files:
371  # Skip Python files. Shared libraries are executable.
372  if not f.endswith(".py") and not f.endswith(".so"):
373  full_path = os.path.join(root, f)
374  if os.access(full_path, os.X_OK):
375  executables.append(full_path)
376 
377  # Store the number of tests found for later assessment.
378  # Do not raise an exception if we have no executables as this would
379  # cause the testing to abort before the test runner could properly
380  # integrate it into the failure report.
381  cls.TESTS_DISCOVERED = len(executables)
382 
383  # Create the test functions and attach them to the class
384  for e in executables:
385  cls._build_test_method(e, ref_dir)
386 
387 
388 @contextlib.contextmanager
389 def getTempFilePath(ext, expectOutput=True):
390  """Return a path suitable for a temporary file and try to delete the
391  file on success
392 
393  If the with block completes successfully then the file is deleted,
394  if possible; failure results in a printed warning.
395  If a file is remains when it should not, a RuntimeError exception is
396  raised. This exception is also raised if a file is not present on context
397  manager exit when one is expected to exist.
398  If the block exits with an exception the file if left on disk so it can be
399  examined. The file name has a random component such that nested context
400  managers can be used with the same file suffix.
401 
402  Parameters
403  ----------
404 
405  ext : `str`
406  File name extension, e.g. ``.fits``.
407  expectOutput : `bool`, optional
408  If `True`, a file should be created within the context manager.
409  If `False`, a file should not be present when the context manager
410  exits.
411 
412  Returns
413  -------
414  `str`
415  Path for a temporary file. The path is a combination of the caller's
416  file path and the name of the top-level function
417 
418  Notes
419  -----
420  ::
421 
422  # file tests/testFoo.py
423  import unittest
424  import lsst.utils.tests
425  class FooTestCase(unittest.TestCase):
426  def testBasics(self):
427  self.runTest()
428 
429  def runTest(self):
430  with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
431  # if tests/.tests exists then
432  # tmpFile = "tests/.tests/testFoo_testBasics.fits"
433  # otherwise tmpFile = "testFoo_testBasics.fits"
434  ...
435  # at the end of this "with" block the path tmpFile will be
436  # deleted, but only if the file exists and the "with"
437  # block terminated normally (rather than with an exception)
438  ...
439  """
440  stack = inspect.stack()
441  # get name of first function in the file
442  for i in range(2, len(stack)):
443  frameInfo = inspect.getframeinfo(stack[i][0])
444  if i == 2:
445  callerFilePath = frameInfo.filename
446  callerFuncName = frameInfo.function
447  elif callerFilePath == frameInfo.filename:
448  # this function called the previous function
449  callerFuncName = frameInfo.function
450  else:
451  break
452 
453  callerDir, callerFileNameWithExt = os.path.split(callerFilePath)
454  callerFileName = os.path.splitext(callerFileNameWithExt)[0]
455  outDir = os.path.join(callerDir, ".tests")
456  if not os.path.isdir(outDir):
457  outDir = ""
458  prefix = "%s_%s-" % (callerFileName, callerFuncName)
459  outPath = tempfile.mktemp(dir=outDir, suffix=ext, prefix=prefix)
460  if os.path.exists(outPath):
461  # There should not be a file there given the randomizer. Warn and remove.
462  # Use stacklevel 3 so that the warning is reported from the end of the with block
463  warnings.warn("Unexpectedly found pre-existing tempfile named %r" % (outPath,),
464  stacklevel=3)
465  try:
466  os.remove(outPath)
467  except OSError:
468  pass
469 
470  yield outPath
471 
472  fileExists = os.path.exists(outPath)
473  if expectOutput:
474  if not fileExists:
475  raise RuntimeError("Temp file expected named {} but none found".format(outPath))
476  else:
477  if fileExists:
478  raise RuntimeError("Unexpectedly discovered temp file named {}".format(outPath))
479  # Try to clean up the file regardless
480  if fileExists:
481  try:
482  os.remove(outPath)
483  except OSError as e:
484  # Use stacklevel 3 so that the warning is reported from the end of the with block
485  warnings.warn("Warning: could not remove file %r: %s" % (outPath, e), stacklevel=3)
486 
487 
488 class TestCase(unittest.TestCase):
489  """Subclass of unittest.TestCase that adds some custom assertions for
490  convenience.
491  """
492 
493 
494 def inTestCase(func):
495  """A decorator to add a free function to our custom TestCase class, while also
496  making it available as a free function.
497  """
498  setattr(TestCase, func.__name__, func)
499  return func
500 
501 
502 @inTestCase
503 def assertRaisesLsstCpp(testcase, excClass, callableObj, *args, **kwargs):
504  """.. note:: Deprecated in 12_0"""
505  warnings.warn("assertRaisesLsstCpp is deprecated; please just use TestCase.assertRaises",
506  DeprecationWarning, stacklevel=2)
507  return testcase.assertRaises(excClass, callableObj, *args, **kwargs)
508 
509 
510 def debugger(*exceptions):
511  """Decorator to enter the debugger when there's an uncaught exception
512 
513  To use, just slap a ``@debugger()`` on your function.
514 
515  You may provide specific exception classes to catch as arguments to
516  the decorator function, e.g.,
517  ``@debugger(RuntimeError, NotImplementedError)``.
518  This defaults to just `AssertionError`, for use on `unittest.TestCase`
519  methods.
520 
521  Code provided by "Rosh Oxymoron" on StackOverflow:
522  http://stackoverflow.com/questions/4398967/python-unit-testing-automatically-running-the-debugger-when-a-test-fails
523 
524  Notes
525  -----
526  Consider using ``pytest --pdb`` instead of this decorator.
527  """
528  if not exceptions:
529  exceptions = (AssertionError, )
530 
531  def decorator(f):
532  @functools.wraps(f)
533  def wrapper(*args, **kwargs):
534  try:
535  return f(*args, **kwargs)
536  except exceptions:
537  import sys
538  import pdb
539  pdb.post_mortem(sys.exc_info()[2])
540  return wrapper
541  return decorator
542 
543 
544 def plotImageDiff(lhs, rhs, bad=None, diff=None, plotFileName=None):
545  """Plot the comparison of two 2-d NumPy arrays.
546 
547  Parameters
548  ----------
549  lhs : `numpy.ndarray`
550  LHS values to compare; a 2-d NumPy array
551  rhs : `numpy.ndarray`
552  RHS values to compare; a 2-d NumPy array
553  bad : `numpy.ndarray`
554  A 2-d boolean NumPy array of values to emphasize in the plots
555  diff : `numpy.ndarray`
556  difference array; a 2-d NumPy array, or None to show lhs-rhs
557  plotFileName : `str`
558  Filename to save the plot to. If None, the plot will be displayed in
559  a window.
560 
561  Notes
562  -----
563  This method uses `matplotlib` and imports it internally; it should be
564  wrapped in a try/except block within packages that do not depend on
565  `matplotlib` (including `~lsst.utils`).
566  """
567  from matplotlib import pyplot
568  if diff is None:
569  diff = lhs - rhs
570  pyplot.figure()
571  if bad is not None:
572  # make an rgba image that's red and transparent where not bad
573  badImage = numpy.zeros(bad.shape + (4,), dtype=numpy.uint8)
574  badImage[:, :, 0] = 255
575  badImage[:, :, 1] = 0
576  badImage[:, :, 2] = 0
577  badImage[:, :, 3] = 255*bad
578  vmin1 = numpy.minimum(numpy.min(lhs), numpy.min(rhs))
579  vmax1 = numpy.maximum(numpy.max(lhs), numpy.max(rhs))
580  vmin2 = numpy.min(diff)
581  vmax2 = numpy.max(diff)
582  for n, (image, title) in enumerate([(lhs, "lhs"), (rhs, "rhs"), (diff, "diff")]):
583  pyplot.subplot(2, 3, n + 1)
584  im1 = pyplot.imshow(image, cmap=pyplot.cm.gray, interpolation='nearest', origin='lower',
585  vmin=vmin1, vmax=vmax1)
586  if bad is not None:
587  pyplot.imshow(badImage, alpha=0.2, interpolation='nearest', origin='lower')
588  pyplot.axis("off")
589  pyplot.title(title)
590  pyplot.subplot(2, 3, n + 4)
591  im2 = pyplot.imshow(image, cmap=pyplot.cm.gray, interpolation='nearest', origin='lower',
592  vmin=vmin2, vmax=vmax2)
593  if bad is not None:
594  pyplot.imshow(badImage, alpha=0.2, interpolation='nearest', origin='lower')
595  pyplot.axis("off")
596  pyplot.title(title)
597  pyplot.subplots_adjust(left=0.05, bottom=0.05, top=0.92, right=0.75, wspace=0.05, hspace=0.05)
598  cax1 = pyplot.axes([0.8, 0.55, 0.05, 0.4])
599  pyplot.colorbar(im1, cax=cax1)
600  cax2 = pyplot.axes([0.8, 0.05, 0.05, 0.4])
601  pyplot.colorbar(im2, cax=cax2)
602  if plotFileName:
603  pyplot.savefig(plotFileName)
604  else:
605  pyplot.show()
606 
607 
608 @inTestCase
609 def assertFloatsAlmostEqual(testCase, lhs, rhs, rtol=sys.float_info.epsilon,
610  atol=sys.float_info.epsilon, relTo=None,
611  printFailures=True, plotOnFailure=False,
612  plotFileName=None, invert=False, msg=None):
613  """Highly-configurable floating point comparisons for scalars and arrays.
614 
615  The test assertion will fail if all elements ``lhs`` and ``rhs`` are not
616  equal to within the tolerances specified by ``rtol`` and ``atol``.
617  More precisely, the comparison is:
618 
619  ``abs(lhs - rhs) <= relTo*rtol OR abs(lhs - rhs) <= atol``
620 
621  If ``rtol`` or ``atol`` is `None`, that term in the comparison is not
622  performed at all.
623 
624  When not specified, ``relTo`` is the elementwise maximum of the absolute
625  values of ``lhs`` and ``rhs``. If set manually, it should usually be set
626  to either ``lhs`` or ``rhs``, or a scalar value typical of what is
627  expected.
628 
629  Parameters
630  ----------
631  testCase : `unittest.TestCase`
632  Instance the test is part of.
633  lhs : scalar or array-like
634  LHS value(s) to compare; may be a scalar or array-like of any
635  dimension.
636  rhs : scalar or array-like
637  RHS value(s) to compare; may be a scalar or array-like of any
638  dimension.
639  rtol : `float`, optional
640  Relative tolerance for comparison; defaults to double-precision
641  epsilon.
642  atol : `float`, optional
643  Absolute tolerance for comparison; defaults to double-precision
644  epsilon.
645  relTo : `float`, optional
646  Value to which comparison with rtol is relative.
647  printFailures : `bool`, optional
648  Upon failure, print all inequal elements as part of the message.
649  plotOnFailure : `bool`, optional
650  Upon failure, plot the originals and their residual with matplotlib.
651  Only 2-d arrays are supported.
652  plotFileName : `str`, optional
653  Filename to save the plot to. If `None`, the plot will be displayed in
654  a window.
655  invert : `bool`, optional
656  If `True`, invert the comparison and fail only if any elements *are*
657  equal. Used to implement `~lsst.utils.tests.assertFloatsNotEqual`,
658  which should generally be used instead for clarity.
659  msg : `str`, optional
660  String to append to the error message when assert fails.
661 
662  Raises
663  ------
664  AssertionError
665  The values are not almost equal.
666  """
667  if not numpy.isfinite(lhs).all():
668  testCase.fail("Non-finite values in lhs")
669  if not numpy.isfinite(rhs).all():
670  testCase.fail("Non-finite values in rhs")
671  diff = lhs - rhs
672  absDiff = numpy.abs(lhs - rhs)
673  if rtol is not None:
674  if relTo is None:
675  relTo = numpy.maximum(numpy.abs(lhs), numpy.abs(rhs))
676  else:
677  relTo = numpy.abs(relTo)
678  bad = absDiff > rtol*relTo
679  if atol is not None:
680  bad = numpy.logical_and(bad, absDiff > atol)
681  else:
682  if atol is None:
683  raise ValueError("rtol and atol cannot both be None")
684  bad = absDiff > atol
685  failed = numpy.any(bad)
686  if invert:
687  failed = not failed
688  bad = numpy.logical_not(bad)
689  cmpStr = "=="
690  failStr = "are the same"
691  else:
692  cmpStr = "!="
693  failStr = "differ"
694  errMsg = []
695  if failed:
696  if numpy.isscalar(bad):
697  if rtol is None:
698  errMsg = ["%s %s %s; diff=%s with atol=%s"
699  % (lhs, cmpStr, rhs, absDiff, atol)]
700  elif atol is None:
701  errMsg = ["%s %s %s; diff=%s/%s=%s with rtol=%s"
702  % (lhs, cmpStr, rhs, absDiff, relTo, absDiff/relTo, rtol)]
703  else:
704  errMsg = ["%s %s %s; diff=%s/%s=%s with rtol=%s, atol=%s"
705  % (lhs, cmpStr, rhs, absDiff, relTo, absDiff/relTo, rtol, atol)]
706  else:
707  errMsg = ["%d/%d elements %s with rtol=%s, atol=%s"
708  % (bad.sum(), bad.size, failStr, rtol, atol)]
709  if plotOnFailure:
710  if len(lhs.shape) != 2 or len(rhs.shape) != 2:
711  raise ValueError("plotOnFailure is only valid for 2-d arrays")
712  try:
713  plotImageDiff(lhs, rhs, bad, diff=diff, plotFileName=plotFileName)
714  except ImportError:
715  errMsg.append("Failure plot requested but matplotlib could not be imported.")
716  if printFailures:
717  # Make sure everything is an array if any of them are, so we can treat
718  # them the same (diff and absDiff are arrays if either rhs or lhs is),
719  # and we don't get here if neither is.
720  if numpy.isscalar(relTo):
721  relTo = numpy.ones(bad.shape, dtype=float) * relTo
722  if numpy.isscalar(lhs):
723  lhs = numpy.ones(bad.shape, dtype=float) * lhs
724  if numpy.isscalar(rhs):
725  rhs = numpy.ones(bad.shape, dtype=float) * rhs
726  if rtol is None:
727  for a, b, diff in zip(lhs[bad], rhs[bad], absDiff[bad]):
728  errMsg.append("%s %s %s (diff=%s)" % (a, cmpStr, b, diff))
729  else:
730  for a, b, diff, rel in zip(lhs[bad], rhs[bad], absDiff[bad], relTo[bad]):
731  errMsg.append("%s %s %s (diff=%s/%s=%s)" % (a, cmpStr, b, diff, rel, diff/rel))
732 
733  if msg is not None:
734  errMsg.append(msg)
735  testCase.assertFalse(failed, msg="\n".join(errMsg))
736 
737 
738 @inTestCase
739 def assertFloatsNotEqual(testCase, lhs, rhs, **kwds):
740  """Fail a test if the given floating point values are equal to within the
741  given tolerances.
742 
743  See `~lsst.utils.tests.assertFloatsAlmostEqual` (called with
744  ``rtol=atol=0``) for more information.
745 
746  Parameters
747  ----------
748  testCase : `unittest.TestCase`
749  Instance the test is part of.
750  lhs : scalar or array-like
751  LHS value(s) to compare; may be a scalar or array-like of any
752  dimension.
753  rhs : scalar or array-like
754  RHS value(s) to compare; may be a scalar or array-like of any
755  dimension.
756 
757  Raises
758  ------
759  AssertionError
760  The values are almost equal.
761  """
762  return assertFloatsAlmostEqual(testCase, lhs, rhs, invert=True, **kwds)
763 
764 
765 @inTestCase
766 def assertFloatsEqual(testCase, lhs, rhs, **kwargs):
767  """
768  Assert that lhs == rhs (both numeric types, whether scalar or array).
769 
770  See `~lsst.utils.tests.assertFloatsAlmostEqual` (called with
771  ``rtol=atol=0``) for more information.
772 
773  Parameters
774  ----------
775  testCase : `unittest.TestCase`
776  Instance the test is part of.
777  lhs : scalar or array-like
778  LHS value(s) to compare; may be a scalar or array-like of any
779  dimension.
780  rhs : scalar or array-like
781  RHS value(s) to compare; may be a scalar or array-like of any
782  dimension.
783 
784  Raises
785  ------
786  AssertionError
787  The values are not equal.
788  """
789  return assertFloatsAlmostEqual(testCase, lhs, rhs, rtol=0, atol=0, **kwargs)
790 
791 
792 @inTestCase
793 def assertClose(*args, **kwargs):
794  """.. note:: Deprecated in 12_0"""
795  warnings.warn("assertClose is deprecated; please use TestCase.assertFloatsAlmostEqual",
796  DeprecationWarning, stacklevel=2)
797  return assertFloatsAlmostEqual(*args, **kwargs)
798 
799 
800 @inTestCase
801 def assertNotClose(*args, **kwargs):
802  """.. note:: Deprecated in 12_0"""
803  warnings.warn("assertNotClose is deprecated; please use TestCase.assertFloatsNotEqual",
804  DeprecationWarning, stacklevel=2)
805  return assertFloatsNotEqual(*args, **kwargs)
def suiteClassWrapper(tests)
Definition: tests.py:163
def assertExecutable(self, executable, root_dir=None, args=None, msg=None)
Definition: tests.py:249
def assertFloatsEqual(testCase, lhs, rhs, kwargs)
Definition: tests.py:766
def init()
Definition: tests.py:75
def plotImageDiff(lhs, rhs, bad=None, diff=None, plotFileName=None)
Definition: tests.py:544
def inTestCase(func)
Definition: tests.py:494
def assertClose(args, kwargs)
Definition: tests.py:793
daf::base::PropertySet * set
Definition: fits.cc:884
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def _build_test_method(cls, executable, root_dir)
Definition: tests.py:300
table::Key< int > type
Definition: Detector.cc:167
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)
Definition: tests.py:612
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
def assertFloatsNotEqual(testCase, lhs, rhs, kwds)
Definition: tests.py:739
def run(suite, exit=True)
Definition: tests.py:85
def getTempFilePath(ext, expectOutput=True)
Definition: tests.py:389
def debugger(exceptions)
Definition: tests.py:510
def assertNotClose(args, kwargs)
Definition: tests.py:801
def create_executable_tests(cls, ref_file, executables=None)
Definition: tests.py:332
def assertRaisesLsstCpp(testcase, excClass, callableObj, args, kwargs)
Definition: tests.py:503
def sort_tests(tests)
Definition: tests.py:120