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 def __init__(self, dir=None):
36 self.tmpdir = tempfile.mkdtemp(dir=dir)
39 shutil.rmtree(self.tmpdir, ignore_errors=
True)
42 @contextlib.contextmanager
44 """Simple context manager to run in a temporary directory.
45 While the context manager is active (within the 'with' block)
46 the current working directory is set to a temporary directory.
47 When the context manager exists, the working directory is reset
48 and the temporary directory deleted."""
50 tmpdir = tempfile.mkdtemp()
54 shutil.rmtree(tmpdir, ignore_errors=
True)
57 @contextlib.contextmanager
59 """Simple context manager to make a temporary directory.
60 The temporary directory has the same lifetime as the context manager
61 (i.e. it is created at the start of the 'with' block, and deleted
62 at the end of the block).
63 @param dir If given, the temporary directory is made as a subdirectory
64 of that directory, rather than in the default temporary
65 directory location (e.g. /tmp)
66 @return the full path to the temporary directory.
68 tmpdir = tempfile.mkdtemp(dir=dir)
70 shutil.rmtree(tmpdir, ignore_errors=
True)
74 """Calculate the derivative of the single-value function `func` at
75 point `val`. The derivative is calculated using simple finite
76 differences starting with the given `step`; Richardson extrapolation
77 is then used to extrapolate the derivative at step=0."""
85 d = [[(f1 - f2) / (2.0 * step)]]
87 for i
in range(1, maxsteps):
88 d.append([0.] * (i + 1))
92 d[i][0] = (f1 - f2) / (2.0 * step)
94 for j
in range(1, i + 1):
95 d[i][j] = (d[i][j-1] * fac - d[i-1][j-1]) / (fac - 1.)
97 errt = max(abs(d[i][j] - d[i][j-1]),
98 abs(d[i][j] - d[i-1][j-1]))
102 if abs(d[i][i] - d[i-1][i-1]) >= safe * err:
105 raise ValueError(
"Cannot calculate numerical derivative")
110 """Calculate the x,y and z derivatives of the scoring function `sf`
111 on the `xyz` particle. The derivatives are approximated numerically
112 using the numerical_derivatives() function."""
113 class _XYZDerivativeFunc:
114 def __init__(self, sf, xyz, basis_vector):
117 self._basis_vector = basis_vector
118 self._starting_coordinates = xyz.get_coordinates()
120 def __call__(self, val):
121 self._xyz.set_coordinates(self._starting_coordinates +
122 self._basis_vector * val)
123 return self._sf.evaluate(
False)
128 for x
in ((1, 0, 0), (0, 1, 0), (0, 0, 1))])
132 """Super class for IMP test cases.
133 This provides a number of useful IMP-specific methods on top of
134 the standard Python `unittest.TestCase` class.
135 Test scripts should generally contain a subclass of this class,
136 conventionally called `Tests` (this makes it easier to run an
137 individual test from the command line) and use IMP::test::main()
138 as their main function."""
142 if not hasattr(unittest.TestCase,
'assertRegex'):
143 assertRegex = unittest.TestCase.assertRegexpMatches
144 assertNotRegex = unittest.TestCase.assertNotRegexpMatches
146 def __init__(self, *args, **keys):
147 unittest.TestCase.__init__(self, *args, **keys)
148 self._progname = Path(sys.argv[0]).absolute()
156 IMP.random_number_generator.seed(hash(time.time()) % 2**30)
162 if hasattr(self,
'_tmpdir'):
166 """Get the full name of an input file in the top-level
168 if self.__module__ ==
'__main__':
169 testdir = self._progname
171 testdir = Path(sys.modules[self.__module__].__file__)
172 for p
in testdir.parents:
175 ret = input / filename
177 raise IOError(
"Test input file %s does not exist" % ret)
179 raise IOError(
"No test input directory found")
182 """Open and return an input file in the top-level test directory."""
186 """Get the full name of an output file in the tmp directory.
187 The directory containing this file will be automatically
188 cleaned up when the test completes."""
189 if not hasattr(self,
'_tmpdir'):
190 self._tmpdir = _TempDir(os.environ.get(
'IMP_TMP_DIR'))
191 tmpdir = self._tmpdir.tmpdir
192 return str(Path(tmpdir) / filename)
195 """Get the magnitude of a list of floats"""
196 return sum(x*x
for x
in vector)**.5
199 """Assert that the given callable object raises UsageException.
200 This differs from unittest's assertRaises in that the test
201 is skipped in fast mode (where usage checks are turned off)."""
206 """Assert that the given callable object raises InternalException.
207 This differs from unittest's assertRaises in that the test
208 is skipped in fast mode (where internal checks are turned off)."""
213 """Assert that the given callable object is not implemented."""
218 """Assert that x,y,z analytical derivatives match numerical within
219 a tolerance, or a percentage (of the analytical value), whichever
220 is larger. `sf` should be a ScoringFunction or Restraint."""
222 derivs = xyz.get_derivatives()
224 pct = percentage / 100.0
225 self.assertAlmostEqual(
228 msg=
"Don't match "+str(derivs) + str(num_derivs))
229 self.assertAlmostEqual(derivs[0], num_derivs[0],
230 delta=max(tolerance, abs(derivs[0]) * pct))
231 self.assertAlmostEqual(derivs[1], num_derivs[1],
232 delta=max(tolerance, abs(derivs[1]) * pct))
233 self.assertAlmostEqual(derivs[2], num_derivs[2],
234 delta=max(tolerance, abs(derivs[2]) * pct))
237 """Fail if the given numpy array doesn't match expected"""
238 if IMP.IMP_KERNEL_HAS_NUMPY:
240 self.assertIsInstance(numpy_array, numpy.ndarray)
241 numpy.testing.assert_array_equal(numpy_array, exp_array)
243 self.assertEqual(numpy_array, exp_array)
247 """Fail if the difference between any two items in the two sequences
248 are exceed the specified number of places or delta. See
251 if delta
is not None and places
is not None:
252 raise TypeError(
"specify delta or places not both")
255 ftypename = ftype.__name__
257 stypename = stype.__name__
259 raise self.failureException(
260 'Sequences are of different types: %s != %s' % (
261 ftypename, stypename))
265 except (NotImplementedError, TypeError):
266 raise self.failureException(
267 'First %s has no length' % (ftypename))
270 except (NotImplementedError, TypeError):
271 raise self.failureException(
272 'Second %s has no length' % (stypename))
275 raise self.failureException(
276 'Sequences have non equal lengths: %d != %d' % (flen, slen))
279 for i
in range(min(flen, slen)):
280 differing =
'%ss differ: %s != %s\n' % (
281 ftypename.capitalize(), safe_repr(first),
286 except (TypeError, IndexError, NotImplementedError):
287 differing += (
'\nUnable to index element %d of first %s\n' %
293 except (TypeError, IndexError, NotImplementedError):
294 differing += (
'\nUnable to index element %d of second %s\n' %
299 self.assertAlmostEqual(
300 f, s, places=places, msg=msg, delta=delta)
301 except (TypeError, ValueError, NotImplementedError,
304 "\nFirst differing element "
305 "%d:\n%s\n%s\n") % (i, safe_repr(f), safe_repr(s))
310 standardMsg = differing
311 diffMsg =
'\n' +
'\n'.join(
312 difflib.ndiff(pprint.pformat(first).splitlines(),
313 pprint.pformat(second).splitlines()))
314 standardMsg = self._truncateMessage(standardMsg, diffMsg)
315 msg = self._formatMessage(msg, standardMsg)
316 raise self.failureException(msg)
318 def _read_cmake_cfg(self, cmake_cfg):
319 """Parse IMPConfig.cmake and extract info on the C++ compiler"""
320 cxx = flags = sysroot =
None
322 with open(cmake_cfg)
as fh:
324 if line.startswith(
'set(IMP_CXX_COMPILER '):
325 cxx = line.split(
'"')[1]
326 elif line.startswith(
'set(IMP_CXX_FLAGS '):
327 flags = line.split(
'"')[1]
328 elif line.startswith(
'set(IMP_OSX_SYSROOT '):
329 sysroot = line.split(
'"')[1]
330 elif line.startswith(
'SET(Boost_INCLUDE_DIR '):
331 includes.append(line.split(
'"')[1])
332 elif line.startswith(
'SET(EIGEN3_INCLUDE_DIR '):
333 includes.append(line.split(
'"')[1])
334 elif line.startswith(
'SET(cereal_INCLUDE_DIRS '):
335 includes.append(line.split(
'"')[1])
336 return cxx, flags, includes, sysroot
339 """Test that the given C++ code fails to compile with a static
341 if sys.platform ==
'win32':
342 self.skipTest(
"No support for Windows yet")
343 libdir = os.path.dirname(IMP.__file__)
344 cmake_cfg = os.path.join(libdir,
'..',
'..',
'IMPConfig.cmake')
345 if not os.path.exists(cmake_cfg):
346 self.skipTest(
"cannot find IMPConfig.cmake")
347 cxx, flags, includes, sysroot = self._read_cmake_cfg(cmake_cfg)
349 if sys.platform ==
'darwin' and sysroot:
350 flags = flags +
" -isysroot" + sysroot
351 includes.append(os.path.join(libdir,
'..',
'..',
'include'))
352 include =
" ".join(
"-I" + inc
for inc
in includes)
354 fname = os.path.join(tmpdir,
'test.cpp')
355 with open(fname,
'w')
as fh:
357 fh.write(
"#include <%s>\n" % h)
358 fh.write(
"\nint main() {\n" + body +
"\n return 0;\n}\n")
359 cmdline =
"%s %s %s %s" % (cxx, flags, include, fname)
361 p = subprocess.Popen(cmdline, shell=
True,
362 stdout=subprocess.PIPE,
363 stderr=subprocess.PIPE,
364 universal_newlines=
True)
365 out, err = p.communicate()
366 self.assertIn(
'error: static assertion failed', err)
369 """Make a particle with optimizable x, y and z attributes, and
370 add it to the model."""
378 """Help handle a test which is expected to fail some fraction of
379 the time. The test is run multiple times and an exception
380 is thrown only if it fails too many times.
381 @note Use of this function should be avoided. If there is a corner
382 case that results in a test 'occasionally' failing, write a
383 new test specifically for that corner case and assert that
384 it fails consistently (and remove the corner case from the
387 prob = chance_of_failure
391 prob = prob*chance_of_failure
392 for i
in range(0, tries):
400 raise AssertionError(
"Too many failures")
403 """Estimate how likely a given block of code is to raise an
407 while failures < 10
and tries < 1000:
413 return failures/tries
416 """Randomize the xyz coordinates of a list of particles"""
422 p.set_value(xkey, random.uniform(-deviation, deviation))
423 p.set_value(ykey, random.uniform(-deviation, deviation))
424 p.set_value(zkey, random.uniform(-deviation, deviation))
427 """Return distance between two given particles"""
431 dx = p1.get_value(xkey) - p2.get_value(xkey)
432 dy = p1.get_value(ykey) - p2.get_value(ykey)
433 dz = p1.get_value(zkey) - p2.get_value(zkey)
434 return math.sqrt(dx*dx + dy*dy + dz*dz)
437 """Check the unary function func's derivatives against numerical
438 approximations between lb and ub"""
439 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
440 (v, d) = func.evaluate_with_derivative(f)
442 self.assertAlmostEqual(d, da, delta=max(abs(.1 * d), 0.01))
445 """Make sure that the minimum of the unary function func over the
446 range between lb and ub is at expected_fmin"""
447 fmin, vmin = lb, func.evaluate(lb)
448 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
452 self.assertAlmostEqual(fmin, expected_fmin, delta=step)
455 """Check methods that every IMP::Object class should have"""
456 obj.set_was_used(
True)
459 self.assertIsNotNone(cls.get_from(obj))
460 self.assertRaises(ValueError, cls.get_from,
IMP.Model())
462 self.assertIsInstance(str(obj), str)
463 self.assertIsInstance(repr(obj), str)
465 verinf = obj.get_version_info()
474 """Create a bunch of particles in a box"""
479 for i
in range(0, num):
486 def _get_type(self, module, name):
487 return eval(
'type('+module+
"."+name+
')')
490 "Check that all the C++ classes in the module are values or objects."
492 ok = set(exceptions_list + module._value_types + module._object_types
493 + module._raii_types + module._plural_types)
497 if self._get_type(module.__name__, name) == type \
498 and not name.startswith(
"_"):
499 if name.find(
"SwigPyIterator") != -1:
502 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
503 % (module.__name__, name)):
510 "All IMP classes should be labeled as values or objects to get "
511 "memory management correct in Python. The following are not:\n%s\n"
512 "Please add an IMP_SWIG_OBJECT or IMP_SWIG_VALUE call to the "
513 "Python wrapper, or if the class has a good reason to be "
514 "neither, add the name to the value_object_exceptions list in "
515 "the IMPModuleTest call." % str(bad))
516 for e
in exceptions_list:
518 e
not in module._value_types + module._object_types
519 + module._raii_types + module._plural_types,
520 "Value/Object exception "+e+
" is not an exception")
522 def _check_spelling(self, word, words):
523 """Check that the word is spelled correctly"""
524 if "words" not in dir(self):
526 wordlist = fh.read().split("\n")
528 custom_words = [
"info",
"prechange",
"int",
"ints",
"optimizeds",
529 "graphviz",
"voxel",
"voxels",
"endian",
'rna',
530 'dna',
"xyzr",
"pdbs",
"fft",
"ccc",
"gaussian"]
533 exclude_words = set([
"adapter",
"grey"])
534 self.words = set(wordlist+custom_words) - exclude_words
536 for i
in "0123456789":
541 if word
in self.words:
549 """Check that all the classes in the module follow the IMP
550 naming conventions."""
554 cc = re.compile(
"([A-Z][a-z]*)")
556 if self._get_type(module.__name__, name) == type \
557 and not name.startswith(
"_"):
558 if name.find(
"SwigPyIterator") != -1:
560 for t
in re.findall(cc, name):
561 if not self._check_spelling(t.lower(), words):
562 misspelled.append(t.lower())
567 "All IMP classes should be properly spelled. The following "
568 "are not: %s.\nMisspelled words: %s. Add words to the "
569 "spelling_exceptions variable of the IMPModuleTest if needed."
570 % (str(bad),
", ".join(set(misspelled))))
573 if self._get_type(module.__name__, name) == type \
574 and not name.startswith(
"_"):
575 if name.find(
"SwigPyIterator") != -1:
577 if name.find(
'_') != -1:
579 if name.lower == name:
581 for t
in re.findall(cc, name):
582 if not self._check_spelling(t.lower(), words):
583 print(
"misspelled %s in %s" % (t, name))
587 "All IMP classes should have CamelCase names. The following "
588 "do not: %s." %
"\n".join(bad))
590 def _check_function_name(self, prefix, name, verbs, all, exceptions, words,
593 fullname = prefix+
"."+name
597 'unprotected_evaluate',
"unprotected_evaluate_if_good",
598 "unprotected_evaluate_if_below",
599 'unprotected_evaluate_moved',
"unprotected_evaluate_moved_if_good",
600 "unprotected_evaluate_moved_if_below",
601 "after_evaluate",
"before_evaluate",
"has_attribute",
602 "decorate_particle",
"particle_is_instance"]
603 if name
in old_exceptions:
605 if fullname
in exceptions:
607 if name.endswith(
"swigregister"):
609 if name.lower() != name:
610 if name[0].lower() != name[0]
and name.split(
'_')[0]
in all:
615 tokens = name.split(
"_")
616 if tokens[0]
not in verbs:
619 if not self._check_spelling(t, words):
621 print(
"misspelled %s in %s" % (t, name))
625 def _static_method(self, module, prefix, name):
626 """For static methods of the form Foo.bar SWIG creates free functions
627 named Foo_bar. Exclude these from spelling checks since the method
628 Foo.bar has already been checked."""
629 if prefix
is None and '_' in name:
630 modobj = eval(module)
631 cls, meth = name.split(
'_', 1)
632 if hasattr(modobj, cls):
633 clsobj = eval(module +
'.' + cls)
634 if hasattr(clsobj, meth):
637 def _check_function_names(self, module, prefix, names, verbs, all,
638 exceptions, words, misspelled):
641 typ = self._get_type(module, name)
642 if name.startswith(
"_")
or name ==
"weakref_proxy":
644 if typ
in (types.BuiltinMethodType, types.MethodType) \
645 or (typ == types.FunctionType
and
646 not self._static_method(module, prefix, name)):
647 bad.extend(self._check_function_name(prefix, name, verbs, all,
650 if typ == type
and "SwigPyIterator" not in name:
651 members = eval(
"dir("+module+
"."+name+
")")
652 bad.extend(self._check_function_names(module+
"."+name,
653 name, members, verbs, [],
659 """Check that all the functions in the module follow the IMP
660 naming conventions."""
662 verbs = set([
"add",
"remove",
"get",
"set",
"evaluate",
"compute",
663 "show",
"create",
"destroy",
"push",
"pop",
"write",
664 "read",
"do",
"show",
"load",
"save",
"reset",
"accept",
665 "reject",
"clear",
"handle",
"update",
"apply",
666 "optimize",
"reserve",
"dump",
"propose",
"setup",
667 "teardown",
"visit",
"find",
"run",
"swap",
"link",
668 "validate",
"erase"])
670 bad = self._check_function_names(module.__name__,
None, all, verbs,
671 all, exceptions, words, misspelled)
672 message = (
"All IMP methods and functions should have lower case "
673 "names separated by underscores and beginning with a "
674 "verb, preferably one of ['add', 'remove', 'get', 'set', "
675 "'create', 'destroy']. Each of the words should be a "
676 "properly spelled English word. The following do not "
677 "(given our limited list of verbs that we check for):\n"
678 "%(bad)s\nIf there is a good reason for them not to "
679 "(eg it does start with a verb, just one with a meaning "
680 "that is not covered by the normal list), add them to the "
681 "function_name_exceptions variable in the "
682 "standards_exceptions file. Otherwise, please fix. "
683 "The current verb list is %(verbs)s"
684 % {
"bad":
"\n".join(bad),
"verbs": verbs})
685 if len(misspelled) > 0:
686 message +=
"\nMisspelled words: " +
", ".join(set(misspelled)) \
687 +
". Add words to the spelling_exceptions variable " \
688 +
"of the standards_exceptions file if needed."
689 self.assertEqual(len(bad), 0, message)
692 """Check that all the classes in modulename have a show method"""
693 all = dir(modulename)
694 if hasattr(modulename,
'_raii_types'):
695 excludes = frozenset(
696 modulename._raii_types + modulename._plural_types)
699 excludes = frozenset()
706 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
707 % (modulename.__name__, f)):
709 if self._get_type(modulename.__name__, f) == type \
710 and not f.startswith(
"_") \
711 and not f.endswith(
"_swigregister")\
712 and f
not in exceptions\
713 and not f.endswith(
"Temp")
and not f.endswith(
"Iterator")\
714 and not f.endswith(
"Exception")
and\
716 if not hasattr(getattr(modulename, f),
'show'):
720 "All IMP classes should support show and __str__. The following "
721 "do not:\n%s\n If there is a good reason for them not to, add "
722 "them to the show_exceptions variable in the IMPModuleTest "
723 "call. Otherwise, please fix." %
"\n".join(not_found))
725 self.assertIn(e, all,
726 "Show exception "+e+
" is not a class in module")
727 self.assertTrue(
not hasattr(getattr(modulename, e),
'show'),
728 "Exception "+e+
" is not really a show exception")
731 """Run the named example script.
732 @return a dictionary of all the script's global variables.
733 This can be queried in a test case to make sure
734 the example performed correctly."""
740 path, name = os.path.split(filename)
741 oldsyspath = sys.path[:]
742 olssysargv = sys.argv[:]
743 sys.path.insert(0, path)
744 sys.argv = [filename]
747 exec(open(filename).read(), vars)
750 except SystemExit
as e:
751 if e.code != 0
and e.code
is not None:
753 "Example exit with code %s" % str(e.code))
756 sys.path = oldsyspath
757 sys.argv = olssysargv
759 return _ExecDictProxy(vars)
762 """Run a Python module as if with "python -m <modname>",
763 with the given list of arguments as sys.argv.
765 If module is an already-imported Python module, run its 'main'
766 function and return the result.
768 If module is a string, run the module in a subprocess and return
769 a subprocess.Popen-like object containing the child stdin,
772 def mock_setup_from_argv(*args, **kwargs):
775 if type(module) == type(os):
778 mod = __import__(module, {}, {}, [
''])
779 modpath = mod.__file__
780 if modpath.endswith(
'.pyc'):
781 modpath = modpath[:-1]
782 if type(module) == type(os):
783 old_sys_argv = sys.argv
785 old_setup = IMP.setup_from_argv
786 IMP.setup_from_argv = mock_setup_from_argv
788 sys.argv = [modpath] + args
791 IMP.setup_from_argv = old_setup
792 sys.argv = old_sys_argv
794 return _SubprocessWrapper(sys.executable, [modpath] + args)
797 """Check a Python module designed to be runnable with 'python -m'
798 to make sure it supports standard command line options."""
801 out, err = r.communicate()
802 self.assertEqual(r.returncode, 0)
803 self.assertNotEqual(err,
"")
804 self.assertEqual(out,
"")
807 class _ExecDictProxy:
808 """exec returns a Python dictionary, which contains IMP objects, other
809 Python objects, as well as base Python modules (such as sys and
810 __builtins__). If we just delete this dictionary, it is entirely
811 possible that base Python modules are removed from the dictionary
812 *before* some IMP objects. This will prevent the IMP objects' Python
813 destructors from running properly, so C++ objects will not be
814 cleaned up. This class proxies the base dict class, and on deletion
815 attempts to remove keys from the dictionary in an order that allows
816 IMP destructors to fire."""
817 def __init__(self, d):
822 module_type = type(IMP)
825 if type(d[k]) != module_type:
828 for meth
in [
'__contains__',
'__getitem__',
'__iter__',
'__len__',
829 'get',
'has_key',
'items',
'keys',
'values']:
830 exec(
"def %s(self, *args, **keys): "
831 "return self._d.%s(*args, **keys)" % (meth, meth))
834 class _TestResult(unittest.TextTestResult):
836 def __init__(self, stream=None, descriptions=None, verbosity=None):
837 super().__init__(stream, descriptions, verbosity)
840 def stopTestRun(self):
841 if 'IMP_TEST_DETAIL_DIR' in os.environ:
844 protocol = min(pickle.HIGHEST_PROTOCOL, 4)
845 fname = (Path(os.environ[
'IMP_TEST_DETAIL_DIR'])
846 / Path(sys.argv[0]).name)
850 if not fname.exists():
851 fname = Path(
"Z:") / fname
852 with open(str(fname),
'wb')
as fh:
853 pickle.dump(self.all_tests, fh, protocol)
854 super().stopTestRun()
856 def startTest(self, test):
857 super().startTest(test)
858 test.start_time = datetime.datetime.now()
860 def _test_finished(self, test, state, detail=None):
861 if hasattr(test,
'start_time'):
862 delta = datetime.datetime.now() - test.start_time
864 pv = delta.total_seconds()
865 except AttributeError:
866 pv = (float(delta.microseconds)
868 + delta.days * 24 * 3600) * 10**6) / 10**6
870 self.stream.write(
"in %.3fs ... " % pv)
875 if detail
is not None and not isinstance(detail, str):
876 detail = self._exc_info_to_string(detail, test)
877 test_doc = self.getDescription(test)
878 test_name = test.id()
879 if test_name.startswith(
'__main__.'):
880 test_name = test_name[9:]
881 self.all_tests.append({
'name': test_name,
882 'docstring': test_doc,
883 'time': pv,
'state': state,
'detail': detail})
885 def addSuccess(self, test):
886 self._test_finished(test,
'OK')
887 super().addSuccess(test)
889 def addError(self, test, err):
890 self._test_finished(test,
'ERROR', err)
891 super().addError(test, err)
893 def addFailure(self, test, err):
894 self._test_finished(test,
'FAIL', err)
895 super().addFailure(test, err)
897 def addSkip(self, test, reason):
898 self._test_finished(test,
'SKIP', reason)
899 super().addSkip(test, reason)
901 def addExpectedFailure(self, test, err):
902 self._test_finished(test,
'EXPFAIL', err)
903 super().addExpectedFailure(test, err)
905 def addUnexpectedSuccess(self, test):
906 self._test_finished(test,
'UNEXPSUC')
907 super().addUnexpectedSuccess(test)
909 def getDescription(self, test):
910 doc_first_line = test.shortDescription()
911 if self.descriptions
and doc_first_line:
912 return doc_first_line
917 class _TestRunner(unittest.TextTestRunner):
918 def _makeResult(self):
919 return _TestResult(self.stream, self.descriptions, self.verbosity)
923 """Run a set of tests; similar to unittest.main().
924 Obviates the need to separately import the 'unittest' module, and
925 ensures that main() is from the same unittest module that the
926 IMP.test testcases are. In addition, turns on some extra checks
927 (e.g. trying to use deprecated code will cause an exception
931 return unittest.main(testRunner=_TestRunner, *args, **keys)
934 class _SubprocessWrapper(subprocess.Popen):
935 def __init__(self, app, args, cwd=None):
938 if sys.platform ==
'win32' and app != sys.executable:
940 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
941 env = os.environ.copy()
942 env[
'PATH'] +=
';' + libdir
945 subprocess.Popen.__init__(self, [app]+list(args),
946 stdin=subprocess.PIPE,
947 stdout=subprocess.PIPE,
948 stderr=subprocess.PIPE, env=env, cwd=cwd,
949 universal_newlines=
True)
953 """Super class for simple IMP application test cases"""
954 def _get_application_file_name(self, filename):
957 if sys.platform ==
'win32':
962 """Run an application with the given list of arguments.
963 @return a subprocess.Popen-like object containing the child stdin,
966 filename = self._get_application_file_name(app)
967 if sys.platform ==
'win32':
969 return _SubprocessWrapper(os.path.join(os.environ[
'IMP_BIN_DIR'],
970 filename), args, cwd=cwd)
972 return _SubprocessWrapper(filename, args, cwd=cwd)
975 """Run a Python application with the given list of arguments.
976 The Python application should be self-runnable (i.e. it should
977 be executable and with a #! on the first line).
978 @return a subprocess.Popen-like object containing the child stdin,
982 if sys.executable !=
'/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
983 return _SubprocessWrapper(
985 [os.path.join(os.environ[
'IMP_BIN_DIR'], app)] + args)
987 return _SubprocessWrapper(app, args)
990 """Import an installed Python application, rather than running it.
991 This is useful to directly test components of the application.
992 @return the Python module object."""
993 import importlib.machinery
994 name = os.path.splitext(app)[0]
995 pathname = os.path.join(os.environ[
'IMP_BIN_DIR'], app)
996 return importlib.machinery.SourceFileLoader(name,
997 pathname).load_module()
1000 """Run an application with the given list of arguments.
1001 @return a subprocess.Popen-like object containing the child stdin,
1004 return _SubprocessWrapper(sys.executable, [app]+args)
1007 """Assert that the application exited cleanly (return value = 0)."""
1009 raise OSError(
"Application exited with signal %d\n" % -ret
1014 "Application exited uncleanly, with exit code %d\n" % ret
1018 """Read and return a set of shell commands from a doxygen file.
1019 Each command is assumed to be in a \code{.sh}...\endcode block.
1020 The doxygen file is specified relative to the test file itself.
1021 This is used to make sure the commands shown in an application
1022 example actually work (the testcase can also check the resulting
1023 files for correctness)."""
1024 def win32_normpath(p):
1027 return " ".join([os.path.normpath(x)
for x
in p.split()])
1029 def fix_win32_command(cmd):
1031 if cmd.startswith(
'cp -r '):
1032 return 'xcopy /E ' + win32_normpath(cmd[6:])
1033 elif cmd.startswith(
'cp '):
1034 return 'copy ' + win32_normpath(cmd[3:])
1037 d = os.path.dirname(sys.argv[0])
1038 doc = os.path.join(d, doxfile)
1042 with open(doc)
as fh:
1043 for line
in fh.readlines():
1044 if '\code{.sh}' in line:
1046 elif '\endcode' in line:
1049 cmds.append(line.rstrip(
'\r\n').replace(
1050 '<imp_example_path>', example_path))
1051 if sys.platform ==
'win32':
1052 cmds = [fix_win32_command(x)
for x
in cmds]
1056 "Print and run a shell command, as returned by read_shell_commands()"
1058 p = subprocess.call(cmd, shell=
True)
1060 raise OSError(
"%s failed with exit value %d" % (cmd, p))
1064 """Check to make sure the number of C++ object references is as expected"""
1066 def __init__(self, testcase):
1070 IMP._director_objects.cleanup()
1071 self.__testcase = testcase
1073 self.__basenum = IMP.Object.get_number_of_live_objects()
1077 "Make sure that the number of references matches the expected value."
1079 IMP._director_objects.cleanup()
1082 if x
not in self.__names]
1083 newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1084 t.assertEqual(newnum, expected,
1085 "Number of objects don't match: "
1086 + str(newnum) +
" != " + str(expected) +
" found "
1091 """Check to make sure the number of director references is as expected"""
1093 def __init__(self, testcase):
1094 IMP._director_objects.cleanup()
1095 self.__testcase = testcase
1096 self.__basenum = IMP._director_objects.get_object_count()
1099 """Make sure that the number of references matches the expected value.
1100 If force_cleanup is set, clean up any unused references first before
1101 doing the assertion.
1105 IMP._director_objects.cleanup()
1106 t.assertEqual(IMP._director_objects.get_object_count()
1107 - self.__basenum, expected)
1116 if sys.platform ==
'win32' and 'PYTHONPATH' in os.environ \
1117 and 'IMP_BIN_DIR' in os.environ:
1118 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
1119 bindir = os.environ[
'IMP_BIN_DIR']
1120 path = os.environ[
'PATH']
1121 if libdir
not in path
or bindir
not in path:
1122 os.environ[
'PATH'] = bindir +
';' + libdir +
';' + path
1125 __version__ =
"20241121.develop.d97d4ead1f"
1128 '''Return the version of this module, as a string'''
1129 return "20241121.develop.d97d4ead1f"
1132 '''Return the fully-qualified name of this module'''
1136 '''Return the full path to one of this module's data files'''
1138 return IMP._get_module_data_path(
"test", fname)
1141 '''Return the full path to one of this module's example files'''
1143 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.
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.