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