1 """@namespace IMP::test
2 @brief Methods and classes for testing the IMP kernel and modules.
19 from unittest.util
import safe_repr
24 from pathlib
import Path
28 expectedFailure = unittest.expectedFailure
30 skipIf = unittest.skipIf
31 skipUnless = unittest.skipUnless
35 """Mark a test as 'unstable', i.e. that it fails randomly.
37 'unstable' tests are tests that do not reliably pass or fail, such
38 as 'science' tests that perform some sort of stochastic sampling or
39 optimization and then assert on the results. This decorator can be
40 used to mark such tests. They are then skipped if the
41 IMP_SKIP_UNSTABLE_TESTS environment variable is set."""
42 return skipIf(
'IMP_SKIP_UNSTABLE_TESTS' in os.environ, reason)
46 def __init__(self, dir=None):
47 self.tmpdir = tempfile.mkdtemp(dir=dir)
50 shutil.rmtree(self.tmpdir, ignore_errors=
True)
53 @contextlib.contextmanager
55 """Simple context manager to run in a temporary directory.
56 While the context manager is active (within the 'with' block)
57 the current working directory is set to a temporary directory.
58 When the context manager exists, the working directory is reset
59 and the temporary directory deleted."""
61 tmpdir = tempfile.mkdtemp()
65 shutil.rmtree(tmpdir, ignore_errors=
True)
68 @contextlib.contextmanager
70 """Simple context manager to make a temporary directory.
71 The temporary directory has the same lifetime as the context manager
72 (i.e. it is created at the start of the 'with' block, and deleted
73 at the end of the block).
74 @param dir If given, the temporary directory is made as a subdirectory
75 of that directory, rather than in the default temporary
76 directory location (e.g. /tmp)
77 @return the full path to the temporary directory.
79 tmpdir = tempfile.mkdtemp(dir=dir)
81 shutil.rmtree(tmpdir, ignore_errors=
True)
85 """Calculate the derivative of the single-value function `func` at
86 point `val`. The derivative is calculated using simple finite
87 differences starting with the given `step`; Richardson extrapolation
88 is then used to extrapolate the derivative at step=0."""
96 d = [[(f1 - f2) / (2.0 * step)]]
98 for i
in range(1, maxsteps):
99 d.append([0.] * (i + 1))
101 f1 = func(val + step)
102 f2 = func(val - step)
103 d[i][0] = (f1 - f2) / (2.0 * step)
105 for j
in range(1, i + 1):
106 d[i][j] = (d[i][j-1] * fac - d[i-1][j-1]) / (fac - 1.)
108 errt = max(abs(d[i][j] - d[i][j-1]),
109 abs(d[i][j] - d[i-1][j-1]))
113 if abs(d[i][i] - d[i-1][i-1]) >= safe * err:
116 raise ValueError(
"Cannot calculate numerical derivative")
121 """Calculate the x,y and z derivatives of the scoring function `sf`
122 on the `xyz` particle. The derivatives are approximated numerically
123 using the numerical_derivatives() function."""
124 class _XYZDerivativeFunc:
125 def __init__(self, sf, xyz, basis_vector):
128 self._basis_vector = basis_vector
129 self._starting_coordinates = xyz.get_coordinates()
131 def __call__(self, val):
132 self._xyz.set_coordinates(self._starting_coordinates +
133 self._basis_vector * val)
134 return self._sf.evaluate(
False)
139 for x
in ((1, 0, 0), (0, 1, 0), (0, 0, 1))])
143 """Super class for IMP test cases.
144 This provides a number of useful IMP-specific methods on top of
145 the standard Python `unittest.TestCase` class.
146 Test scripts should generally contain a subclass of this class,
147 conventionally called `Tests` (this makes it easier to run an
148 individual test from the command line) and use IMP::test::main()
149 as their main function."""
153 if not hasattr(unittest.TestCase,
'assertRegex'):
154 assertRegex = unittest.TestCase.assertRegexpMatches
155 assertNotRegex = unittest.TestCase.assertNotRegexpMatches
157 def __init__(self, *args, **keys):
158 unittest.TestCase.__init__(self, *args, **keys)
159 self._progname = Path(sys.argv[0]).absolute()
167 IMP.random_number_generator.seed(hash(time.time()) % 2**30)
173 if hasattr(self,
'_tmpdir'):
177 """Get the full name of an input file in the top-level
179 if self.__module__ ==
'__main__':
180 testdir = self._progname
182 testdir = Path(sys.modules[self.__module__].__file__)
183 for p
in testdir.parents:
186 ret = input / filename
188 raise IOError(
"Test input file %s does not exist" % ret)
190 raise IOError(
"No test input directory found")
193 """Open and return an input file in the top-level test directory."""
197 """Get the full name of an output file in the tmp directory.
198 The directory containing this file will be automatically
199 cleaned up when the test completes."""
200 if not hasattr(self,
'_tmpdir'):
201 self._tmpdir = _TempDir(os.environ.get(
'IMP_TMP_DIR'))
202 tmpdir = self._tmpdir.tmpdir
203 return str(Path(tmpdir) / filename)
206 """Get the magnitude of a list of floats"""
207 return sum(x*x
for x
in vector)**.5
210 """Assert that the given callable object raises UsageException.
211 This differs from unittest's assertRaises in that the test
212 is skipped in fast mode (where usage checks are turned off)."""
217 """Assert that the given callable object raises InternalException.
218 This differs from unittest's assertRaises in that the test
219 is skipped in fast mode (where internal checks are turned off)."""
224 """Assert that the given callable object is not implemented."""
229 """Assert that x,y,z analytical derivatives match numerical within
230 a tolerance, or a percentage (of the analytical value), whichever
231 is larger. `sf` should be a ScoringFunction or Restraint."""
233 derivs = xyz.get_derivatives()
235 pct = percentage / 100.0
236 self.assertAlmostEqual(
239 msg=
"Don't match "+str(derivs) + str(num_derivs))
240 self.assertAlmostEqual(derivs[0], num_derivs[0],
241 delta=max(tolerance, abs(derivs[0]) * pct))
242 self.assertAlmostEqual(derivs[1], num_derivs[1],
243 delta=max(tolerance, abs(derivs[1]) * pct))
244 self.assertAlmostEqual(derivs[2], num_derivs[2],
245 delta=max(tolerance, abs(derivs[2]) * pct))
248 """Fail if the given numpy array doesn't match expected"""
249 if IMP.IMP_KERNEL_HAS_NUMPY:
251 self.assertIsInstance(numpy_array, numpy.ndarray)
252 numpy.testing.assert_array_equal(numpy_array, exp_array)
254 self.assertEqual(numpy_array, exp_array)
258 """Fail if the difference between any two items in the two sequences
259 are exceed the specified number of places or delta. See
262 if delta
is not None and places
is not None:
263 raise TypeError(
"specify delta or places not both")
266 ftypename = ftype.__name__
268 stypename = stype.__name__
270 raise self.failureException(
271 'Sequences are of different types: %s != %s' % (
272 ftypename, stypename))
276 except (NotImplementedError, TypeError):
277 raise self.failureException(
278 'First %s has no length' % (ftypename))
281 except (NotImplementedError, TypeError):
282 raise self.failureException(
283 'Second %s has no length' % (stypename))
286 raise self.failureException(
287 'Sequences have non equal lengths: %d != %d' % (flen, slen))
290 for i
in range(min(flen, slen)):
291 differing =
'%ss differ: %s != %s\n' % (
292 ftypename.capitalize(), safe_repr(first),
297 except (TypeError, IndexError, NotImplementedError):
298 differing += (
'\nUnable to index element %d of first %s\n' %
304 except (TypeError, IndexError, NotImplementedError):
305 differing += (
'\nUnable to index element %d of second %s\n' %
310 self.assertAlmostEqual(
311 f, s, places=places, msg=msg, delta=delta)
312 except (TypeError, ValueError, NotImplementedError,
315 "\nFirst differing element "
316 "%d:\n%s\n%s\n") % (i, safe_repr(f), safe_repr(s))
321 standardMsg = differing
322 diffMsg =
'\n' +
'\n'.join(
323 difflib.ndiff(pprint.pformat(first).splitlines(),
324 pprint.pformat(second).splitlines()))
325 standardMsg = self._truncateMessage(standardMsg, diffMsg)
326 msg = self._formatMessage(msg, standardMsg)
327 raise self.failureException(msg)
329 def _read_cmake_cfg(self, cmake_cfg):
330 """Parse IMPConfig.cmake and extract info on the C++ compiler"""
331 cxx = flags = sysroot =
None
333 with open(cmake_cfg)
as fh:
335 if line.startswith(
'set(IMP_CXX_COMPILER '):
336 cxx = line.split(
'"')[1]
337 elif line.startswith(
'set(IMP_CXX_FLAGS '):
338 flags = line.split(
'"')[1]
339 elif line.startswith(
'set(IMP_OSX_SYSROOT '):
340 sysroot = line.split(
'"')[1]
341 elif line.startswith(
'SET(Boost_INCLUDE_DIR '):
342 includes.append(line.split(
'"')[1])
343 elif line.startswith(
'SET(EIGEN3_INCLUDE_DIR '):
344 includes.append(line.split(
'"')[1])
345 elif line.startswith(
'SET(cereal_INCLUDE_DIRS '):
346 includes.append(line.split(
'"')[1])
347 return cxx, flags, includes, sysroot
350 """Test that the given C++ code fails to compile with a static
352 if sys.platform ==
'win32':
353 self.skipTest(
"No support for Windows yet")
354 libdir = os.path.dirname(IMP.__file__)
355 cmake_cfg = os.path.join(libdir,
'..',
'..',
'IMPConfig.cmake')
356 if not os.path.exists(cmake_cfg):
357 self.skipTest(
"cannot find IMPConfig.cmake")
358 cxx, flags, includes, sysroot = self._read_cmake_cfg(cmake_cfg)
360 if sys.platform ==
'darwin' and sysroot:
361 flags = flags +
" -isysroot" + sysroot
362 includes.append(os.path.join(libdir,
'..',
'..',
'include'))
363 include =
" ".join(
"-I" + inc
for inc
in includes)
365 fname = os.path.join(tmpdir,
'test.cpp')
366 with open(fname,
'w')
as fh:
368 fh.write(
"#include <%s>\n" % h)
369 fh.write(
"\nint main() {\n" + body +
"\n return 0;\n}\n")
370 cmdline =
"%s %s %s %s" % (cxx, flags, include, fname)
372 p = subprocess.Popen(cmdline, shell=
True,
373 stdout=subprocess.PIPE,
374 stderr=subprocess.PIPE,
375 universal_newlines=
True)
376 out, err = p.communicate()
377 self.assertIn(
'error: static assertion failed', err)
380 """Make a particle with optimizable x, y and z attributes, and
381 add it to the model."""
389 """Help handle a test which is expected to fail some fraction of
390 the time. The test is run multiple times and an exception
391 is thrown only if it fails too many times.
392 @note Use of this function should be avoided. If there is a corner
393 case that results in a test 'occasionally' failing, write a
394 new test specifically for that corner case and assert that
395 it fails consistently (and remove the corner case from the
398 prob = chance_of_failure
402 prob = prob*chance_of_failure
403 for i
in range(0, tries):
411 raise AssertionError(
"Too many failures")
414 """Estimate how likely a given block of code is to raise an
418 while failures < 10
and tries < 1000:
424 return failures/tries
427 """Randomize the xyz coordinates of a list of particles"""
433 p.set_value(xkey, random.uniform(-deviation, deviation))
434 p.set_value(ykey, random.uniform(-deviation, deviation))
435 p.set_value(zkey, random.uniform(-deviation, deviation))
438 """Return distance between two given particles"""
442 dx = p1.get_value(xkey) - p2.get_value(xkey)
443 dy = p1.get_value(ykey) - p2.get_value(ykey)
444 dz = p1.get_value(zkey) - p2.get_value(zkey)
445 return math.sqrt(dx*dx + dy*dy + dz*dz)
448 """Check the unary function func's derivatives against numerical
449 approximations between lb and ub"""
450 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
451 (v, d) = func.evaluate_with_derivative(f)
453 self.assertAlmostEqual(d, da, delta=max(abs(.1 * d), 0.01))
456 """Make sure that the minimum of the unary function func over the
457 range between lb and ub is at expected_fmin"""
458 fmin, vmin = lb, func.evaluate(lb)
459 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
463 self.assertAlmostEqual(fmin, expected_fmin, delta=step)
466 """Check methods that every IMP::Object class should have"""
467 obj.set_was_used(
True)
470 self.assertIsNotNone(cls.get_from(obj))
471 self.assertRaises(ValueError, cls.get_from,
IMP.Model())
473 self.assertIsInstance(str(obj), str)
474 self.assertIsInstance(repr(obj), str)
476 verinf = obj.get_version_info()
485 """Create a bunch of particles in a box"""
490 for i
in range(0, num):
497 def _get_type(self, module, name):
498 return eval(
'type('+module+
"."+name+
')')
501 "Check that all the C++ classes in the module are values or objects."
503 ok = set(exceptions_list + module._value_types + module._object_types
504 + module._raii_types + module._plural_types)
508 if self._get_type(module.__name__, name) == type \
509 and not name.startswith(
"_"):
510 if name.find(
"SwigPyIterator") != -1:
513 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
514 % (module.__name__, name)):
521 "All IMP classes should be labeled as values or objects to get "
522 "memory management correct in Python. The following are not:\n%s\n"
523 "Please add an IMP_SWIG_OBJECT or IMP_SWIG_VALUE call to the "
524 "Python wrapper, or if the class has a good reason to be "
525 "neither, add the name to the value_object_exceptions list in "
526 "the IMPModuleTest call." % str(bad))
527 for e
in exceptions_list:
529 e
not in module._value_types + module._object_types
530 + module._raii_types + module._plural_types,
531 "Value/Object exception "+e+
" is not an exception")
533 def _check_spelling(self, word, words):
534 """Check that the word is spelled correctly"""
535 if "words" not in dir(self):
537 wordlist = fh.read().split("\n")
539 custom_words = [
"info",
"prechange",
"int",
"ints",
"optimizeds",
540 "graphviz",
"voxel",
"voxels",
"endian",
'rna',
541 'dna',
"xyzr",
"pdbs",
"fft",
"ccc",
"gaussian"]
544 exclude_words = set([
"adapter",
"grey"])
545 self.words = set(wordlist+custom_words) - exclude_words
547 for i
in "0123456789":
552 if word
in self.words:
560 """Check that all the classes in the module follow the IMP
561 naming conventions."""
565 cc = re.compile(
"([A-Z][a-z]*)")
567 if self._get_type(module.__name__, name) == type \
568 and not name.startswith(
"_"):
569 if name.find(
"SwigPyIterator") != -1:
571 for t
in re.findall(cc, name):
572 if not self._check_spelling(t.lower(), words):
573 misspelled.append(t.lower())
578 "All IMP classes should be properly spelled. The following "
579 "are not: %s.\nMisspelled words: %s. Add words to the "
580 "spelling_exceptions variable of the IMPModuleTest if needed."
581 % (str(bad),
", ".join(set(misspelled))))
584 if self._get_type(module.__name__, name) == type \
585 and not name.startswith(
"_"):
586 if name.find(
"SwigPyIterator") != -1:
588 if name.find(
'_') != -1:
590 if name.lower == name:
592 for t
in re.findall(cc, name):
593 if not self._check_spelling(t.lower(), words):
594 print(
"misspelled %s in %s" % (t, name))
598 "All IMP classes should have CamelCase names. The following "
599 "do not: %s." %
"\n".join(bad))
601 def _check_function_name(self, prefix, name, verbs, all, exceptions, words,
604 fullname = prefix+
"."+name
608 'unprotected_evaluate',
"unprotected_evaluate_if_good",
609 "unprotected_evaluate_if_below",
610 'unprotected_evaluate_moved',
"unprotected_evaluate_moved_if_good",
611 "unprotected_evaluate_moved_if_below",
612 "after_evaluate",
"before_evaluate",
"has_attribute",
613 "decorate_particle",
"particle_is_instance"]
614 if name
in old_exceptions:
616 if fullname
in exceptions:
618 if name.endswith(
"swigregister"):
620 if name.lower() != name:
621 if name[0].lower() != name[0]
and name.split(
'_')[0]
in all:
626 tokens = name.split(
"_")
627 if tokens[0]
not in verbs:
630 if not self._check_spelling(t, words):
632 print(
"misspelled %s in %s" % (t, name))
636 def _static_method(self, module, prefix, name):
637 """For static methods of the form Foo.bar SWIG creates free functions
638 named Foo_bar. Exclude these from spelling checks since the method
639 Foo.bar has already been checked."""
640 if prefix
is None and '_' in name:
641 modobj = eval(module)
642 cls, meth = name.split(
'_', 1)
643 if hasattr(modobj, cls):
644 clsobj = eval(module +
'.' + cls)
645 if hasattr(clsobj, meth):
648 def _check_function_names(self, module, prefix, names, verbs, all,
649 exceptions, words, misspelled):
652 typ = self._get_type(module, name)
653 if name.startswith(
"_")
or name ==
"weakref_proxy":
655 if typ
in (types.BuiltinMethodType, types.MethodType) \
656 or (typ == types.FunctionType
and
657 not self._static_method(module, prefix, name)):
658 bad.extend(self._check_function_name(prefix, name, verbs, all,
661 if typ == type
and "SwigPyIterator" not in name:
662 members = eval(
"dir("+module+
"."+name+
")")
663 bad.extend(self._check_function_names(module+
"."+name,
664 name, members, verbs, [],
670 """Check that all the functions in the module follow the IMP
671 naming conventions."""
673 verbs = set([
"add",
"remove",
"get",
"set",
"evaluate",
"compute",
674 "show",
"create",
"destroy",
"push",
"pop",
"write",
675 "read",
"do",
"show",
"load",
"save",
"reset",
"accept",
676 "reject",
"clear",
"handle",
"update",
"apply",
677 "optimize",
"reserve",
"dump",
"propose",
"setup",
678 "teardown",
"visit",
"find",
"run",
"swap",
"link",
679 "validate",
"erase",
"check"])
681 bad = self._check_function_names(module.__name__,
None, all, verbs,
682 all, exceptions, words, misspelled)
683 message = (
"All IMP methods and functions should have lower case "
684 "names separated by underscores and beginning with a "
685 "verb, preferably one of ['add', 'remove', 'get', 'set', "
686 "'create', 'destroy']. Each of the words should be a "
687 "properly spelled English word. The following do not "
688 "(given our limited list of verbs that we check for):\n"
689 "%(bad)s\nIf there is a good reason for them not to "
690 "(eg it does start with a verb, just one with a meaning "
691 "that is not covered by the normal list), add them to the "
692 "function_name_exceptions variable in the "
693 "standards_exceptions file. Otherwise, please fix. "
694 "The current verb list is %(verbs)s"
695 % {
"bad":
"\n".join(bad),
"verbs": verbs})
696 if len(misspelled) > 0:
697 message +=
"\nMisspelled words: " +
", ".join(set(misspelled)) \
698 +
". Add words to the spelling_exceptions variable " \
699 +
"of the standards_exceptions file if needed."
700 self.assertEqual(len(bad), 0, message)
703 """Check that all the classes in modulename have a show method"""
704 all = dir(modulename)
705 if hasattr(modulename,
'_raii_types'):
706 excludes = frozenset(
707 modulename._raii_types + modulename._plural_types)
710 excludes = frozenset()
717 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
718 % (modulename.__name__, f)):
720 if self._get_type(modulename.__name__, f) == type \
721 and not f.startswith(
"_") \
722 and not f.endswith(
"_swigregister")\
723 and f
not in exceptions\
724 and not f.endswith(
"Temp")
and not f.endswith(
"Iterator")\
725 and not f.endswith(
"Exception")
and\
727 if not hasattr(getattr(modulename, f),
'show'):
731 "All IMP classes should support show and __str__. The following "
732 "do not:\n%s\n If there is a good reason for them not to, add "
733 "them to the show_exceptions variable in the IMPModuleTest "
734 "call. Otherwise, please fix." %
"\n".join(not_found))
736 self.assertIn(e, all,
737 "Show exception "+e+
" is not a class in module")
738 self.assertTrue(
not hasattr(getattr(modulename, e),
'show'),
739 "Exception "+e+
" is not really a show exception")
742 """Run the named example script.
743 @return a dictionary of all the script's global variables.
744 This can be queried in a test case to make sure
745 the example performed correctly."""
751 path, name = os.path.split(filename)
752 oldsyspath = sys.path[:]
753 olssysargv = sys.argv[:]
754 sys.path.insert(0, path)
755 sys.argv = [filename]
758 exec(open(filename).read(), vars)
761 except SystemExit
as e:
762 if e.code != 0
and e.code
is not None:
764 "Example exit with code %s" % str(e.code))
767 sys.path = oldsyspath
768 sys.argv = olssysargv
770 return _ExecDictProxy(vars)
773 """Run a Python module as if with "python -m <modname>",
774 with the given list of arguments as sys.argv.
776 If module is an already-imported Python module, run its 'main'
777 function and return the result.
779 If module is a string, run the module in a subprocess and return
780 a subprocess.Popen-like object containing the child stdin,
783 def mock_setup_from_argv(*args, **kwargs):
786 if type(module) == type(os):
789 mod = __import__(module, {}, {}, [
''])
790 modpath = mod.__file__
791 if modpath.endswith(
'.pyc'):
792 modpath = modpath[:-1]
793 if type(module) == type(os):
794 old_sys_argv = sys.argv
796 old_setup = IMP.setup_from_argv
797 IMP.setup_from_argv = mock_setup_from_argv
799 sys.argv = [modpath] + args
802 IMP.setup_from_argv = old_setup
803 sys.argv = old_sys_argv
805 return _SubprocessWrapper(sys.executable, [modpath] + args)
808 """Check a Python module designed to be runnable with 'python -m'
809 to make sure it supports standard command line options."""
812 out, err = r.communicate()
813 self.assertEqual(r.returncode, 0)
814 self.assertNotEqual(err,
"")
815 self.assertEqual(out,
"")
818 class _ExecDictProxy:
819 """exec returns a Python dictionary, which contains IMP objects, other
820 Python objects, as well as base Python modules (such as sys and
821 __builtins__). If we just delete this dictionary, it is entirely
822 possible that base Python modules are removed from the dictionary
823 *before* some IMP objects. This will prevent the IMP objects' Python
824 destructors from running properly, so C++ objects will not be
825 cleaned up. This class proxies the base dict class, and on deletion
826 attempts to remove keys from the dictionary in an order that allows
827 IMP destructors to fire."""
828 def __init__(self, d):
833 module_type = type(IMP)
836 if type(d[k]) != module_type:
839 for meth
in [
'__contains__',
'__getitem__',
'__iter__',
'__len__',
840 'get',
'has_key',
'items',
'keys',
'values']:
841 exec(
"def %s(self, *args, **keys): "
842 "return self._d.%s(*args, **keys)" % (meth, meth))
845 class _TestResult(unittest.TextTestResult):
847 def __init__(self, stream=None, descriptions=None, verbosity=None):
848 super().__init__(stream, descriptions, verbosity)
851 def stopTestRun(self):
852 if 'IMP_TEST_DETAIL_DIR' in os.environ:
855 protocol = min(pickle.HIGHEST_PROTOCOL, 4)
856 fname = (Path(os.environ[
'IMP_TEST_DETAIL_DIR'])
857 / Path(sys.argv[0]).name)
861 if not fname.exists():
862 fname = Path(
"Z:") / fname
863 with open(str(fname),
'wb')
as fh:
864 pickle.dump(self.all_tests, fh, protocol)
865 super().stopTestRun()
867 def startTest(self, test):
868 super().startTest(test)
869 test.start_time = datetime.datetime.now()
871 def _test_finished(self, test, state, detail=None):
872 if hasattr(test,
'start_time'):
873 delta = datetime.datetime.now() - test.start_time
875 pv = delta.total_seconds()
876 except AttributeError:
877 pv = (float(delta.microseconds)
879 + delta.days * 24 * 3600) * 10**6) / 10**6
881 self.stream.write(
"in %.3fs ... " % pv)
886 if detail
is not None and not isinstance(detail, str):
887 detail = self._exc_info_to_string(detail, test)
888 test_doc = self.getDescription(test)
889 test_name = test.id()
890 if test_name.startswith(
'__main__.'):
891 test_name = test_name[9:]
892 self.all_tests.append({
'name': test_name,
893 'docstring': test_doc,
894 'time': pv,
'state': state,
'detail': detail})
896 def addSuccess(self, test):
897 self._test_finished(test,
'OK')
898 super().addSuccess(test)
900 def addError(self, test, err):
901 self._test_finished(test,
'ERROR', err)
902 super().addError(test, err)
904 def addFailure(self, test, err):
905 self._test_finished(test,
'FAIL', err)
906 super().addFailure(test, err)
908 def addSkip(self, test, reason):
909 self._test_finished(test,
'SKIP', reason)
910 super().addSkip(test, reason)
912 def addExpectedFailure(self, test, err):
913 self._test_finished(test,
'EXPFAIL', err)
914 super().addExpectedFailure(test, err)
916 def addUnexpectedSuccess(self, test):
917 self._test_finished(test,
'UNEXPSUC')
918 super().addUnexpectedSuccess(test)
920 def getDescription(self, test):
921 doc_first_line = test.shortDescription()
922 if self.descriptions
and doc_first_line:
923 return doc_first_line
928 class _TestRunner(unittest.TextTestRunner):
929 def _makeResult(self):
930 return _TestResult(self.stream, self.descriptions, self.verbosity)
934 """Run a set of tests; similar to unittest.main().
935 Obviates the need to separately import the 'unittest' module, and
936 ensures that main() is from the same unittest module that the
937 IMP.test testcases are. In addition, turns on some extra checks
938 (e.g. trying to use deprecated code will cause an exception
942 return unittest.main(testRunner=_TestRunner, *args, **keys)
945 class _SubprocessWrapper(subprocess.Popen):
946 def __init__(self, app, args, cwd=None):
949 if sys.platform ==
'win32' and app != sys.executable:
951 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
952 env = os.environ.copy()
953 env[
'PATH'] +=
';' + libdir
956 subprocess.Popen.__init__(self, [app]+list(args),
957 stdin=subprocess.PIPE,
958 stdout=subprocess.PIPE,
959 stderr=subprocess.PIPE, env=env, cwd=cwd,
960 universal_newlines=
True)
964 """Super class for simple IMP application test cases"""
965 def _get_application_file_name(self, filename):
968 if sys.platform ==
'win32':
973 """Run an application with the given list of arguments.
974 @return a subprocess.Popen-like object containing the child stdin,
977 filename = self._get_application_file_name(app)
978 if sys.platform ==
'win32':
980 return _SubprocessWrapper(os.path.join(os.environ[
'IMP_BIN_DIR'],
981 filename), args, cwd=cwd)
983 return _SubprocessWrapper(filename, args, cwd=cwd)
986 """Run a Python application with the given list of arguments.
987 The Python application should be self-runnable (i.e. it should
988 be executable and with a #! on the first line).
989 @return a subprocess.Popen-like object containing the child stdin,
993 if sys.executable !=
'/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
994 return _SubprocessWrapper(
996 [os.path.join(os.environ[
'IMP_BIN_DIR'], app)] + args)
998 return _SubprocessWrapper(app, args)
1001 """Import an installed Python application, rather than running it.
1002 This is useful to directly test components of the application.
1003 @return the Python module object."""
1004 import importlib.machinery
1005 import importlib.util
1006 name = os.path.splitext(app)[0]
1007 if name
in sys.modules:
1008 return sys.modules[name]
1009 pathname = os.path.join(os.environ[
'IMP_BIN_DIR'], app)
1010 loader = importlib.machinery.SourceFileLoader(name, pathname)
1011 spec = importlib.util.spec_from_loader(name, loader)
1012 module = importlib.util.module_from_spec(spec)
1013 sys.modules[name] = module
1014 spec.loader.exec_module(module)
1018 """Run an application with the given list of arguments.
1019 @return a subprocess.Popen-like object containing the child stdin,
1022 return _SubprocessWrapper(sys.executable, [app]+args)
1025 """Assert that the application exited cleanly (return value = 0)."""
1027 raise OSError(
"Application exited with signal %d\n" % -ret
1032 "Application exited uncleanly, with exit code %d\n" % ret
1036 """Read and return a set of shell commands from a doxygen file.
1037 Each command is assumed to be in a \code{.sh}...\endcode block.
1038 The doxygen file is specified relative to the test file itself.
1039 This is used to make sure the commands shown in an application
1040 example actually work (the testcase can also check the resulting
1041 files for correctness)."""
1042 def win32_normpath(p):
1045 return " ".join([os.path.normpath(x)
for x
in p.split()])
1047 def fix_win32_command(cmd):
1049 if cmd.startswith(
'cp -r '):
1050 return 'xcopy /E ' + win32_normpath(cmd[6:])
1051 elif cmd.startswith(
'cp '):
1052 return 'copy ' + win32_normpath(cmd[3:])
1055 d = os.path.dirname(sys.argv[0])
1056 doc = os.path.join(d, doxfile)
1060 with open(doc)
as fh:
1061 for line
in fh.readlines():
1062 if '\code{.sh}' in line:
1064 elif '\endcode' in line:
1067 cmds.append(line.rstrip(
'\r\n').replace(
1068 '<imp_example_path>', example_path))
1069 if sys.platform ==
'win32':
1070 cmds = [fix_win32_command(x)
for x
in cmds]
1074 "Print and run a shell command, as returned by read_shell_commands()"
1076 p = subprocess.call(cmd, shell=
True)
1078 raise OSError(
"%s failed with exit value %d" % (cmd, p))
1082 """Check to make sure the number of C++ object references is as expected"""
1084 def __init__(self, testcase):
1088 IMP._director_objects.cleanup()
1089 self.__testcase = testcase
1091 self.__basenum = IMP.Object.get_number_of_live_objects()
1095 "Make sure that the number of references matches the expected value."
1097 IMP._director_objects.cleanup()
1100 if x
not in self.__names]
1101 newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1102 t.assertEqual(newnum, expected,
1103 "Number of objects don't match: "
1104 + str(newnum) +
" != " + str(expected) +
" found "
1109 """Check to make sure the number of director references is as expected"""
1111 def __init__(self, testcase):
1112 IMP._director_objects.cleanup()
1113 self.__testcase = testcase
1114 self.__basenum = IMP._director_objects.get_object_count()
1117 """Make sure that the number of references matches the expected value.
1118 If force_cleanup is set, clean up any unused references first before
1119 doing the assertion.
1123 IMP._director_objects.cleanup()
1124 t.assertEqual(IMP._director_objects.get_object_count()
1125 - self.__basenum, expected)
1134 if sys.platform ==
'win32' and 'PYTHONPATH' in os.environ \
1135 and 'IMP_BIN_DIR' in os.environ:
1136 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
1137 bindir = os.environ[
'IMP_BIN_DIR']
1138 path = os.environ[
'PATH']
1139 if libdir
not in path
or bindir
not in path:
1140 os.environ[
'PATH'] = bindir +
';' + libdir +
';' + path
1143 __version__ =
"20250831.develop.50fdd7fa33"
1146 '''Return the version of this module, as a string'''
1147 return "20250831.develop.50fdd7fa33"
1150 '''Return the fully-qualified name of this module'''
1154 '''Return the full path to one of this module's data files'''
1156 return IMP._get_module_data_path(
"test", fname)
1159 '''Return the full path to one of this module's example files'''
1161 return IMP._get_module_example_path(
"test", fname)
def run_python_module
Run a Python module as if with "python -m <modname>", with the given list of arguments as sys...
def temporary_working_directory
Simple context manager to run in a temporary directory.
def assertApplicationExitedCleanly
Assert that the application exited cleanly (return value = 0).
CheckLevel get_check_level()
Get the current audit mode.
def import_python_application
Import an installed Python application, rather than running it.
def open_input_file
Open and return an input file in the top-level test directory.
def run_application
Run an application with the given list of arguments.
def randomize_particles
Randomize the xyz coordinates of a list of particles.
def get_module_version
Return the version of this module, as a string.
A general exception for an internal error in IMP.
def main
Run a set of tests; similar to unittest.main().
An exception for an invalid usage of IMP.
Super class for simple IMP application test cases.
def assertCompileFails
Test that the given C++ code fails to compile with a static assertion.
def assertRaisesInternalException
Assert that the given callable object raises InternalException.
def assert_number
Make sure that the number of references matches the expected value.
Check to make sure the number of director references is as expected.
def assertShow
Check that all the classes in modulename have a show method.
def get_data_path
Return the full path to one of this module's data files.
def get_example_path
Return the full path to one of this module's example files.
def run_shell_command
Print and run a shell command, as returned by read_shell_commands()
def assertRaisesUsageException
Assert that the given callable object raises UsageException.
Vector3D get_random_vector_in(const Cylinder3D &c)
Generate a random vector in a cylinder with uniform density.
def assertSequenceAlmostEqual
Fail if the difference between any two items in the two sequences are exceed the specified number of ...
Class for storing model, its restraints, constraints, and particles.
def run_python_application
Run a Python application with the given list of arguments.
def assert_number
Make sure that the number of references matches the expected value.
def unstable
Mark a test as 'unstable', i.e.
Strings get_live_object_names()
Return the names of all live objects.
def check_standard_object_methods
Check methods that every IMP::Object class should have.
def particle_distance
Return distance between two given particles.
def check_unary_function_deriv
Check the unary function func's derivatives against numerical approximations between lb and ub...
def get_tmp_file_name
Get the full name of an output file in the tmp directory.
Version and module information for Objects.
def run_example
Run the named example script.
def get_magnitude
Get the magnitude of a list of floats.
void set_deprecation_exceptions(bool tf)
Toggle whether an exception is thrown when a deprecated method is used.
def check_unary_function_min
Make sure that the minimum of the unary function func over the range between lb and ub is at expected...
def probabilistic_check
Help handle a test which is expected to fail some fraction of the time.
def create_particles_in_box
Create a bunch of particles in a box.
General purpose algebraic and geometric methods that are expected to be used by a wide variety of IMP...
def assertNotImplemented
Assert that the given callable object is not implemented.
The general base class for IMP exceptions.
def numerical_derivative
Calculate the derivative of the single-value function func at point val.
std::string get_example_path(std::string file_name)
Return the full path to one of this module's example files.
def xyz_numerical_derivatives
Calculate the x,y and z derivatives of the scoring function sf on the xyz particle.
def failure_probability
Estimate how likely a given block of code is to raise an AssertionError.
def assertClassNames
Check that all the classes in the module follow the IMP naming conventions.
def create_point_particle
Make a particle with optimizable x, y and z attributes, and add it to the model.
Class to handle individual particles of a Model object.
def read_shell_commands
Read and return a set of shell commands from a doxygen file.
Check to make sure the number of C++ object references is as expected.
def assertFunctionNames
Check that all the functions in the module follow the IMP naming conventions.
def assertValueObjects
Check that all the C++ classes in the module are values or objects.
def assertNumPyArrayEqual
Fail if the given numpy array doesn't match expected.
def get_module_name
Return the fully-qualified name of this module.
Super class for IMP test cases.
def assertXYZDerivativesInTolerance
Assert that x,y,z analytical derivatives match numerical within a tolerance, or a percentage (of the ...
def temporary_directory
Simple context manager to make a temporary directory.
def run_script
Run an application with the given list of arguments.
def get_input_file_name
Get the full name of an input file in the top-level test directory.
def check_runnable_python_module
Check a Python module designed to be runnable with 'python -m' to make sure it supports standard comm...
void set_check_level(CheckLevel tf)
Control runtime checks in the code.