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 skipped by default. To run the
41 tests anyway, set the IMP_UNSTABLE_TESTS environment variable."""
42 reason =
"unstable test; enable by setting $IMP_UNSTABLE_TESTS"
43 tf = unittest.skipUnless(
'IMP_UNSTABLE_TESTS' in os.environ, reason)
48 def __init__(self, dir=None):
49 self.tmpdir = tempfile.mkdtemp(dir=dir)
52 shutil.rmtree(self.tmpdir, ignore_errors=
True)
55 @contextlib.contextmanager
57 """Simple context manager to run in a temporary directory.
58 While the context manager is active (within the 'with' block)
59 the current working directory is set to a temporary directory.
60 When the context manager exists, the working directory is reset
61 and the temporary directory deleted."""
63 tmpdir = tempfile.mkdtemp()
67 shutil.rmtree(tmpdir, ignore_errors=
True)
70 @contextlib.contextmanager
72 """Simple context manager to make a temporary directory.
73 The temporary directory has the same lifetime as the context manager
74 (i.e. it is created at the start of the 'with' block, and deleted
75 at the end of the block).
76 @param dir If given, the temporary directory is made as a subdirectory
77 of that directory, rather than in the default temporary
78 directory location (e.g. /tmp)
79 @return the full path to the temporary directory.
81 tmpdir = tempfile.mkdtemp(dir=dir)
83 shutil.rmtree(tmpdir, ignore_errors=
True)
87 """Calculate the derivative of the single-value function `func` at
88 point `val`. The derivative is calculated using simple finite
89 differences starting with the given `step`; Richardson extrapolation
90 is then used to extrapolate the derivative at step=0."""
98 d = [[(f1 - f2) / (2.0 * step)]]
100 for i
in range(1, maxsteps):
101 d.append([0.] * (i + 1))
103 f1 = func(val + step)
104 f2 = func(val - step)
105 d[i][0] = (f1 - f2) / (2.0 * step)
107 for j
in range(1, i + 1):
108 d[i][j] = (d[i][j-1] * fac - d[i-1][j-1]) / (fac - 1.)
110 errt = max(abs(d[i][j] - d[i][j-1]),
111 abs(d[i][j] - d[i-1][j-1]))
115 if abs(d[i][i] - d[i-1][i-1]) >= safe * err:
118 raise ValueError(
"Cannot calculate numerical derivative")
123 """Calculate the x,y and z derivatives of the scoring function `sf`
124 on the `xyz` particle. The derivatives are approximated numerically
125 using the numerical_derivatives() function."""
126 class _XYZDerivativeFunc:
127 def __init__(self, sf, xyz, basis_vector):
130 self._basis_vector = basis_vector
131 self._starting_coordinates = xyz.get_coordinates()
133 def __call__(self, val):
134 self._xyz.set_coordinates(self._starting_coordinates +
135 self._basis_vector * val)
136 return self._sf.evaluate(
False)
139 _XYZDerivativeFunc(sf, xyz, IMP.algebra.Vector3D(*x)),
141 for x
in ((1, 0, 0), (0, 1, 0), (0, 0, 1))])
145 """Super class for IMP test cases.
146 This provides a number of useful IMP-specific methods on top of
147 the standard Python `unittest.TestCase` class.
148 Test scripts should generally contain a subclass of this class,
149 conventionally called `Tests` (this makes it easier to run an
150 individual test from the command line) and use IMP::test::main()
151 as their main function."""
155 if not hasattr(unittest.TestCase,
'assertRegex'):
156 assertRegex = unittest.TestCase.assertRegexpMatches
157 assertNotRegex = unittest.TestCase.assertNotRegexpMatches
159 def __init__(self, *args, **keys):
160 super().__init__(*args, **keys)
161 self._progname = Path(sys.argv[0]).absolute()
169 IMP.random_number_generator.seed(hash(time.time()) % 2**30)
175 if hasattr(self,
'_tmpdir'):
179 """Get the full name of an input file in the top-level
181 if self.__module__ ==
'__main__':
182 testdir = self._progname
184 testdir = Path(sys.modules[self.__module__].__file__)
185 for p
in testdir.parents:
188 ret = input / filename
190 raise IOError(
"Test input file %s does not exist" % ret)
192 raise IOError(
"No test input directory found")
195 """Open and return an input file in the top-level test directory."""
199 """Get the full name of an output file in the tmp directory.
200 The directory containing this file will be automatically
201 cleaned up when the test completes."""
202 if not hasattr(self,
'_tmpdir'):
203 self._tmpdir = _TempDir(os.environ.get(
'IMP_TMP_DIR'))
204 tmpdir = self._tmpdir.tmpdir
205 return str(Path(tmpdir) / filename)
208 """Get the magnitude of a list of floats"""
209 return sum(x*x
for x
in vector)**.5
212 """Assert that the given callable object raises UsageException.
213 This differs from unittest's assertRaises in that the test
214 is skipped in fast mode (where usage checks are turned off)."""
219 """Assert that the given callable object raises InternalException.
220 This differs from unittest's assertRaises in that the test
221 is skipped in fast mode (where internal checks are turned off)."""
226 """Assert that the given callable object is not implemented."""
231 """Assert that x,y,z analytical derivatives match numerical within
232 a tolerance, or a percentage (of the analytical value), whichever
233 is larger. `sf` should be a ScoringFunction or Restraint."""
235 derivs = xyz.get_derivatives()
237 pct = percentage / 100.0
238 self.assertAlmostEqual(
241 msg=
"Don't match "+str(derivs) + str(num_derivs))
242 self.assertAlmostEqual(derivs[0], num_derivs[0],
243 delta=max(tolerance, abs(derivs[0]) * pct))
244 self.assertAlmostEqual(derivs[1], num_derivs[1],
245 delta=max(tolerance, abs(derivs[1]) * pct))
246 self.assertAlmostEqual(derivs[2], num_derivs[2],
247 delta=max(tolerance, abs(derivs[2]) * pct))
250 """Fail if the given numpy array doesn't match expected"""
251 if IMP.IMP_KERNEL_HAS_NUMPY:
253 self.assertIsInstance(numpy_array, numpy.ndarray)
254 numpy.testing.assert_array_equal(numpy_array, exp_array)
256 self.assertEqual(numpy_array, exp_array)
260 """Fail if the difference between any two items in the two sequences
261 are exceed the specified number of places or delta. See
264 if delta
is not None and places
is not None:
265 raise TypeError(
"specify delta or places not both")
268 ftypename = ftype.__name__
270 stypename = stype.__name__
272 raise self.failureException(
273 'Sequences are of different types: %s != %s' % (
274 ftypename, stypename))
278 except (NotImplementedError, TypeError):
279 raise self.failureException(
280 'First %s has no length' % (ftypename))
283 except (NotImplementedError, TypeError):
284 raise self.failureException(
285 'Second %s has no length' % (stypename))
288 raise self.failureException(
289 'Sequences have non equal lengths: %d != %d' % (flen, slen))
292 for i
in range(min(flen, slen)):
293 differing =
'%ss differ: %s != %s\n' % (
294 ftypename.capitalize(), safe_repr(first),
299 except (TypeError, IndexError, NotImplementedError):
300 differing += (
'\nUnable to index element %d of first %s\n' %
306 except (TypeError, IndexError, NotImplementedError):
307 differing += (
'\nUnable to index element %d of second %s\n' %
312 self.assertAlmostEqual(
313 f, s, places=places, msg=msg, delta=delta)
314 except (TypeError, ValueError, NotImplementedError,
317 "\nFirst differing element "
318 "%d:\n%s\n%s\n") % (i, safe_repr(f), safe_repr(s))
323 standardMsg = differing
324 diffMsg =
'\n' +
'\n'.join(
325 difflib.ndiff(pprint.pformat(first).splitlines(),
326 pprint.pformat(second).splitlines()))
327 standardMsg = self._truncateMessage(standardMsg, diffMsg)
328 msg = self._formatMessage(msg, standardMsg)
329 raise self.failureException(msg)
331 def _read_cmake_cfg(self, cmake_cfg):
332 """Parse IMPConfig.cmake and extract info on the C++ compiler"""
333 cxx = flags = sysroot =
None
335 with open(cmake_cfg)
as fh:
337 if line.startswith(
'set(IMP_CXX_COMPILER '):
338 cxx = line.split(
'"')[1]
339 elif line.startswith(
'set(IMP_CXX_FLAGS '):
340 flags = line.split(
'"')[1]
341 elif line.startswith(
'set(IMP_OSX_SYSROOT '):
342 sysroot = line.split(
'"')[1]
343 elif line.startswith(
'SET(Boost_INCLUDE_DIR '):
344 includes.append(line.split(
'"')[1])
345 elif line.startswith(
'SET(EIGEN3_INCLUDE_DIR '):
346 includes.append(line.split(
'"')[1])
347 elif line.startswith(
'SET(cereal_INCLUDE_DIRS '):
348 includes.append(line.split(
'"')[1])
349 return cxx, flags, includes, sysroot
352 """Test that the given C++ code fails to compile with a static
354 if sys.platform ==
'win32':
355 self.skipTest(
"No support for Windows yet")
356 libdir = os.path.dirname(IMP.__file__)
357 cmake_cfg = os.path.join(libdir,
'..',
'..',
'IMPConfig.cmake')
358 if not os.path.exists(cmake_cfg):
359 self.skipTest(
"cannot find IMPConfig.cmake")
360 cxx, flags, includes, sysroot = self._read_cmake_cfg(cmake_cfg)
362 if sys.platform ==
'darwin' and sysroot:
363 flags = flags +
" -isysroot" + sysroot
364 includes.append(os.path.join(libdir,
'..',
'..',
'include'))
365 include =
" ".join(
"-I" + inc
for inc
in includes)
367 fname = os.path.join(tmpdir,
'test.cpp')
368 with open(fname,
'w')
as fh:
370 fh.write(
"#include <%s>\n" % h)
371 fh.write(
"\nint main() {\n" + body +
"\n return 0;\n}\n")
372 cmdline =
"%s %s %s %s" % (cxx, flags, include, fname)
374 p = subprocess.Popen(cmdline, shell=
True,
375 stdout=subprocess.PIPE,
376 stderr=subprocess.PIPE,
377 universal_newlines=
True)
378 out, err = p.communicate()
379 self.assertIn(
'error: static assertion failed', err)
382 """Make a particle with optimizable x, y and z attributes, and
383 add it to the model."""
391 """Help handle a test which is expected to fail some fraction of
392 the time. The test is run multiple times and an exception
393 is thrown only if it fails too many times.
394 @note Use of this function should be avoided. If there is a corner
395 case that results in a test 'occasionally' failing, write a
396 new test specifically for that corner case and assert that
397 it fails consistently (and remove the corner case from the
400 prob = chance_of_failure
404 prob = prob*chance_of_failure
405 for i
in range(0, tries):
413 raise AssertionError(
"Too many failures")
416 """Estimate how likely a given block of code is to raise an
420 while failures < 10
and tries < 1000:
426 return failures/tries
429 """Randomize the xyz coordinates of a list of particles"""
435 p.set_value(xkey, random.uniform(-deviation, deviation))
436 p.set_value(ykey, random.uniform(-deviation, deviation))
437 p.set_value(zkey, random.uniform(-deviation, deviation))
440 """Return distance between two given particles"""
444 dx = p1.get_value(xkey) - p2.get_value(xkey)
445 dy = p1.get_value(ykey) - p2.get_value(ykey)
446 dz = p1.get_value(zkey) - p2.get_value(zkey)
447 return math.sqrt(dx*dx + dy*dy + dz*dz)
450 """Check the unary function func's derivatives against numerical
451 approximations between lb and ub"""
452 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
453 (v, d) = func.evaluate_with_derivative(f)
455 self.assertAlmostEqual(d, da, delta=max(abs(.1 * d), 0.01))
458 """Make sure that the minimum of the unary function func over the
459 range between lb and ub is at expected_fmin"""
460 fmin, vmin = lb, func.evaluate(lb)
461 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
465 self.assertAlmostEqual(fmin, expected_fmin, delta=step)
468 """Check methods that every IMP::Object class should have"""
469 obj.set_was_used(
True)
472 self.assertIsNotNone(cls.get_from(obj))
473 self.assertRaises(ValueError, cls.get_from,
IMP.Model())
475 self.assertIsInstance(str(obj), str)
476 self.assertIsInstance(repr(obj), str)
478 verinf = obj.get_version_info()
487 """Create a bunch of particles in a box"""
489 lbv = IMP.algebra.Vector3D(lb[0], lb[1], lb[2])
490 ubv = IMP.algebra.Vector3D(ub[0], ub[1], ub[2])
492 for i
in range(0, num):
499 def _get_type(self, module, name):
500 return eval(
'type('+module+
"."+name+
')')
503 "Check that all the C++ classes in the module are values or objects."
505 ok = set(exceptions_list + module._value_types + module._object_types
506 + module._raii_types + module._plural_types)
510 if self._get_type(module.__name__, name) == type \
511 and not name.startswith(
"_"):
512 if name.find(
"SwigPyIterator") != -1:
515 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
516 % (module.__name__, name)):
523 "All IMP classes should be labeled as values or objects to get "
524 "memory management correct in Python. The following are not:\n%s\n"
525 "Please add an IMP_SWIG_OBJECT or IMP_SWIG_VALUE call to the "
526 "Python wrapper, or if the class has a good reason to be "
527 "neither, add the name to the value_object_exceptions list in "
528 "the IMPModuleTest call." % str(bad))
529 for e
in exceptions_list:
531 e
not in module._value_types + module._object_types
532 + module._raii_types + module._plural_types,
533 "Value/Object exception "+e+
" is not an exception")
535 def _check_spelling(self, word, words):
536 """Check that the word is spelled correctly"""
537 if "words" not in dir(self):
539 wordlist = fh.read().split("\n")
541 custom_words = [
"info",
"prechange",
"int",
"ints",
"optimizeds",
542 "graphviz",
"voxel",
"voxels",
"endian",
'rna',
543 'dna',
"xyzr",
"pdbs",
"fft",
"ccc",
"gaussian"]
546 exclude_words = set([
"adapter",
"grey"])
547 self.words = set(wordlist+custom_words) - exclude_words
549 for i
in "0123456789":
554 if word
in self.words:
562 """Check that all the classes in the module follow the IMP
563 naming conventions."""
567 cc = re.compile(
"([A-Z][a-z]*)")
569 if self._get_type(module.__name__, name) == type \
570 and not name.startswith(
"_"):
571 if name.find(
"SwigPyIterator") != -1:
573 for t
in re.findall(cc, name):
574 if not self._check_spelling(t.lower(), words):
575 misspelled.append(t.lower())
580 "All IMP classes should be properly spelled. The following "
581 "are not: %s.\nMisspelled words: %s. Add words to the "
582 "spelling_exceptions variable of the IMPModuleTest if needed."
583 % (str(bad),
", ".join(set(misspelled))))
586 if self._get_type(module.__name__, name) == type \
587 and not name.startswith(
"_"):
588 if name.find(
"SwigPyIterator") != -1:
590 if name.find(
'_') != -1:
592 if name.lower == name:
594 for t
in re.findall(cc, name):
595 if not self._check_spelling(t.lower(), words):
596 print(
"misspelled %s in %s" % (t, name))
600 "All IMP classes should have CamelCase names. The following "
601 "do not: %s." %
"\n".join(bad))
603 def _check_function_name(self, prefix, name, verbs, all, exceptions, words,
606 fullname = prefix+
"."+name
610 'unprotected_evaluate',
"unprotected_evaluate_if_good",
611 "unprotected_evaluate_if_below",
612 'unprotected_evaluate_moved',
"unprotected_evaluate_moved_if_good",
613 "unprotected_evaluate_moved_if_below",
614 "after_evaluate",
"before_evaluate",
"has_attribute",
615 "decorate_particle",
"particle_is_instance"]
616 if name
in old_exceptions:
618 if fullname
in exceptions:
620 if name.endswith(
"swigregister"):
622 if name.lower() != name:
623 if name[0].lower() != name[0]
and name.split(
'_')[0]
in all:
628 tokens = name.split(
"_")
629 if tokens[0]
not in verbs:
632 if not self._check_spelling(t, words):
634 print(
"misspelled %s in %s" % (t, name))
638 def _static_method(self, module, prefix, name):
639 """For static methods of the form Foo.bar SWIG creates free functions
640 named Foo_bar. Exclude these from spelling checks since the method
641 Foo.bar has already been checked."""
642 if prefix
is None and '_' in name:
643 modobj = eval(module)
644 cls, meth = name.split(
'_', 1)
645 if hasattr(modobj, cls):
646 clsobj = eval(module +
'.' + cls)
647 if hasattr(clsobj, meth):
650 def _check_function_names(self, module, prefix, names, verbs, all,
651 exceptions, words, misspelled):
654 typ = self._get_type(module, name)
655 if name.startswith(
"_")
or name ==
"weakref_proxy":
657 if typ
in (types.BuiltinMethodType, types.MethodType) \
658 or (typ == types.FunctionType
and
659 not self._static_method(module, prefix, name)):
660 bad.extend(self._check_function_name(prefix, name, verbs, all,
663 if typ == type
and "SwigPyIterator" not in name:
664 members = eval(
"dir("+module+
"."+name+
")")
665 bad.extend(self._check_function_names(module+
"."+name,
666 name, members, verbs, [],
672 """Check that all the functions in the module follow the IMP
673 naming conventions."""
675 verbs = set([
"add",
"remove",
"get",
"set",
"evaluate",
"compute",
676 "show",
"create",
"destroy",
"push",
"pop",
"write",
677 "read",
"do",
"show",
"load",
"save",
"reset",
"accept",
678 "reject",
"clear",
"handle",
"update",
"apply",
679 "optimize",
"reserve",
"dump",
"propose",
"setup",
680 "teardown",
"visit",
"find",
"run",
"swap",
"link",
681 "validate",
"erase",
"check"])
683 bad = self._check_function_names(module.__name__,
None, all, verbs,
684 all, exceptions, words, misspelled)
685 message = (
"All IMP methods and functions should have lower case "
686 "names separated by underscores and beginning with a "
687 "verb, preferably one of ['add', 'remove', 'get', 'set', "
688 "'create', 'destroy']. Each of the words should be a "
689 "properly spelled English word. The following do not "
690 "(given our limited list of verbs that we check for):\n"
691 "%(bad)s\nIf there is a good reason for them not to "
692 "(eg it does start with a verb, just one with a meaning "
693 "that is not covered by the normal list), add them to the "
694 "function_name_exceptions variable in the "
695 "standards_exceptions file. Otherwise, please fix. "
696 "The current verb list is %(verbs)s"
697 % {
"bad":
"\n".join(bad),
"verbs": verbs})
698 if len(misspelled) > 0:
699 message +=
"\nMisspelled words: " +
", ".join(set(misspelled)) \
700 +
". Add words to the spelling_exceptions variable " \
701 +
"of the standards_exceptions file if needed."
702 self.assertEqual(len(bad), 0, message)
705 """Check that all the classes in modulename have a show method"""
706 all = dir(modulename)
707 if hasattr(modulename,
'_raii_types'):
708 excludes = frozenset(
709 modulename._raii_types + modulename._plural_types)
712 excludes = frozenset()
719 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
720 % (modulename.__name__, f)):
722 if self._get_type(modulename.__name__, f) == type \
723 and not f.startswith(
"_") \
724 and not f.endswith(
"_swigregister")\
725 and f
not in exceptions\
726 and not f.endswith(
"Temp")
and not f.endswith(
"Iterator")\
727 and not f.endswith(
"Exception")
and\
729 if not hasattr(getattr(modulename, f),
'show'):
733 "All IMP classes should support show and __str__. The following "
734 "do not:\n%s\n If there is a good reason for them not to, add "
735 "them to the show_exceptions variable in the IMPModuleTest "
736 "call. Otherwise, please fix." %
"\n".join(not_found))
738 self.assertIn(e, all,
739 "Show exception "+e+
" is not a class in module")
740 self.assertTrue(
not hasattr(getattr(modulename, e),
'show'),
741 "Exception "+e+
" is not really a show exception")
744 """Run the named example script.
745 @return a dictionary of all the script's global variables.
746 This can be queried in a test case to make sure
747 the example performed correctly."""
753 path, name = os.path.split(filename)
754 oldsyspath = sys.path[:]
755 olssysargv = sys.argv[:]
756 sys.path.insert(0, path)
757 sys.argv = [filename]
760 exec(open(filename).read(), vars)
763 except SystemExit
as e:
764 if e.code != 0
and e.code
is not None:
766 "Example exit with code %s" % str(e.code))
769 sys.path = oldsyspath
770 sys.argv = olssysargv
772 return _ExecDictProxy(vars)
775 """Run a Python module as if with "python -m <modname>",
776 with the given list of arguments as sys.argv.
778 If module is an already-imported Python module, run its 'main'
779 function and return the result.
781 If module is a string, run the module in a subprocess and return
782 a subprocess.Popen-like object containing the child stdin,
785 def mock_setup_from_argv(*args, **kwargs):
788 if type(module) == type(os):
791 mod = __import__(module, {}, {}, [
''])
792 modpath = mod.__file__
793 if modpath.endswith(
'.pyc'):
794 modpath = modpath[:-1]
795 if type(module) == type(os):
796 old_sys_argv = sys.argv
798 old_setup = IMP.setup_from_argv
799 IMP.setup_from_argv = mock_setup_from_argv
801 sys.argv = [modpath] + args
804 IMP.setup_from_argv = old_setup
805 sys.argv = old_sys_argv
807 return _SubprocessWrapper(sys.executable, [modpath] + args)
810 """Check a Python module designed to be runnable with 'python -m'
811 to make sure it supports standard command line options."""
814 out, err = r.communicate()
815 self.assertEqual(r.returncode, 0)
816 self.assertNotEqual(err,
"")
817 self.assertEqual(out,
"")
820 class _ExecDictProxy:
821 """exec returns a Python dictionary, which contains IMP objects, other
822 Python objects, as well as base Python modules (such as sys and
823 __builtins__). If we just delete this dictionary, it is entirely
824 possible that base Python modules are removed from the dictionary
825 *before* some IMP objects. This will prevent the IMP objects' Python
826 destructors from running properly, so C++ objects will not be
827 cleaned up. This class proxies the base dict class, and on deletion
828 attempts to remove keys from the dictionary in an order that allows
829 IMP destructors to fire."""
830 def __init__(self, d):
835 module_type = type(IMP)
838 if type(d[k]) != module_type:
841 for meth
in [
'__contains__',
'__getitem__',
'__iter__',
'__len__',
842 'get',
'has_key',
'items',
'keys',
'values']:
843 exec(
"def %s(self, *args, **keys): "
844 "return self._d.%s(*args, **keys)" % (meth, meth))
847 class _TestResult(unittest.TextTestResult):
849 def __init__(self, stream=None, descriptions=None, verbosity=None):
850 super().__init__(stream, descriptions, verbosity)
853 def stopTestRun(self):
854 if 'IMP_TEST_DETAIL_DIR' in os.environ:
857 protocol = min(pickle.HIGHEST_PROTOCOL, 4)
858 fname = (Path(os.environ[
'IMP_TEST_DETAIL_DIR'])
859 / Path(sys.argv[0]).name)
863 if not fname.exists():
864 fname = Path(
"Z:") / fname
865 with open(str(fname),
'wb')
as fh:
866 pickle.dump(self.all_tests, fh, protocol)
867 super().stopTestRun()
869 def startTest(self, test):
870 super().startTest(test)
871 test.start_time = datetime.datetime.now()
873 def _test_finished(self, test, state, detail=None):
874 if hasattr(test,
'start_time'):
875 delta = datetime.datetime.now() - test.start_time
877 pv = delta.total_seconds()
878 except AttributeError:
879 pv = (float(delta.microseconds)
881 + delta.days * 24 * 3600) * 10**6) / 10**6
883 self.stream.write(
"in %.3fs ... " % pv)
888 if detail
is not None and not isinstance(detail, str):
889 detail = self._exc_info_to_string(detail, test)
890 test_doc = self.getDescription(test)
891 test_name = test.id()
892 if test_name.startswith(
'__main__.'):
893 test_name = test_name[9:]
894 self.all_tests.append({
'name': test_name,
895 'docstring': test_doc,
896 'time': pv,
'state': state,
'detail': detail})
898 def addSuccess(self, test):
899 self._test_finished(test,
'OK')
900 super().addSuccess(test)
902 def addError(self, test, err):
903 self._test_finished(test,
'ERROR', err)
904 super().addError(test, err)
906 def addFailure(self, test, err):
907 self._test_finished(test,
'FAIL', err)
908 super().addFailure(test, err)
910 def addSkip(self, test, reason):
911 self._test_finished(test,
'SKIP', reason)
912 super().addSkip(test, reason)
914 def addExpectedFailure(self, test, err):
915 self._test_finished(test,
'EXPFAIL', err)
916 super().addExpectedFailure(test, err)
918 def addUnexpectedSuccess(self, test):
919 self._test_finished(test,
'UNEXPSUC')
920 super().addUnexpectedSuccess(test)
922 def getDescription(self, test):
923 doc_first_line = test.shortDescription()
924 if self.descriptions
and doc_first_line:
925 return doc_first_line
930 class _TestRunner(unittest.TextTestRunner):
931 def _makeResult(self):
932 return _TestResult(self.stream, self.descriptions, self.verbosity)
936 """Run a set of tests; similar to unittest.main().
937 Obviates the need to separately import the 'unittest' module, and
938 ensures that main() is from the same unittest module that the
939 IMP.test testcases are. In addition, turns on some extra checks
940 (e.g. trying to use deprecated code will cause an exception
944 return unittest.main(testRunner=_TestRunner, *args, **keys)
947 class _SubprocessWrapper(subprocess.Popen):
948 def __init__(self, app, args, cwd=None):
951 if sys.platform ==
'win32' and app != sys.executable:
953 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
954 env = os.environ.copy()
955 env[
'PATH'] +=
';' + libdir
958 super().__init__([app]+list(args),
959 stdin=subprocess.PIPE,
960 stdout=subprocess.PIPE,
961 stderr=subprocess.PIPE, env=env, cwd=cwd,
962 universal_newlines=
True)
966 """Super class for simple IMP application test cases"""
967 def _get_application_file_name(self, filename):
970 if sys.platform ==
'win32':
975 """Run an application with the given list of arguments.
976 @return a subprocess.Popen-like object containing the child stdin,
979 filename = self._get_application_file_name(app)
980 if sys.platform ==
'win32':
982 return _SubprocessWrapper(os.path.join(os.environ[
'IMP_BIN_DIR'],
983 filename), args, cwd=cwd)
985 return _SubprocessWrapper(filename, args, cwd=cwd)
988 """Run a Python application with the given list of arguments.
989 The Python application should be self-runnable (i.e. it should
990 be executable and with a #! on the first line).
991 @return a subprocess.Popen-like object containing the child stdin,
995 if sys.executable !=
'/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
996 return _SubprocessWrapper(
998 [os.path.join(os.environ[
'IMP_BIN_DIR'], app)] + args)
1000 return _SubprocessWrapper(app, args)
1003 """Import an installed Python application, rather than running it.
1004 This is useful to directly test components of the application.
1005 @return the Python module object."""
1006 import importlib.machinery
1007 import importlib.util
1008 name = os.path.splitext(app)[0]
1009 if name
in sys.modules:
1010 return sys.modules[name]
1011 pathname = os.path.join(os.environ[
'IMP_BIN_DIR'], app)
1012 loader = importlib.machinery.SourceFileLoader(name, pathname)
1013 spec = importlib.util.spec_from_loader(name, loader)
1014 module = importlib.util.module_from_spec(spec)
1015 sys.modules[name] = module
1016 spec.loader.exec_module(module)
1020 """Run an application with the given list of arguments.
1021 @return a subprocess.Popen-like object containing the child stdin,
1024 return _SubprocessWrapper(sys.executable, [app]+args)
1027 """Assert that the application exited cleanly (return value = 0)."""
1029 raise OSError(
"Application exited with signal %d\n" % -ret
1034 "Application exited uncleanly, with exit code %d\n" % ret
1038 """Read and return a set of shell commands from a doxygen file.
1039 Each command is assumed to be in a \code{.sh}...\endcode block.
1040 The doxygen file is specified relative to the test file itself.
1041 This is used to make sure the commands shown in an application
1042 example actually work (the testcase can also check the resulting
1043 files for correctness)."""
1044 def win32_normpath(p):
1047 return " ".join([os.path.normpath(x)
for x
in p.split()])
1049 def fix_win32_command(cmd):
1051 if cmd.startswith(
'cp -r '):
1052 return 'xcopy /E ' + win32_normpath(cmd[6:])
1053 elif cmd.startswith(
'cp '):
1054 return 'copy ' + win32_normpath(cmd[3:])
1057 d = os.path.dirname(sys.argv[0])
1058 doc = os.path.join(d, doxfile)
1062 with open(doc)
as fh:
1063 for line
in fh.readlines():
1064 if '\code{.sh}' in line:
1066 elif '\endcode' in line:
1069 cmds.append(line.rstrip(
'\r\n').replace(
1070 '<imp_example_path>', example_path))
1071 if sys.platform ==
'win32':
1072 cmds = [fix_win32_command(x)
for x
in cmds]
1076 "Print and run a shell command, as returned by read_shell_commands()"
1078 p = subprocess.call(cmd, shell=
True)
1080 raise OSError(
"%s failed with exit value %d" % (cmd, p))
1084 """Check to make sure the number of C++ object references is as expected"""
1086 def __init__(self, testcase):
1090 IMP._director_objects.cleanup()
1091 self.__testcase = testcase
1093 self.__basenum = IMP.Object.get_number_of_live_objects()
1097 "Make sure that the number of references matches the expected value."
1099 IMP._director_objects.cleanup()
1102 if x
not in self.__names]
1103 newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1104 t.assertEqual(newnum, expected,
1105 "Number of objects don't match: "
1106 + str(newnum) +
" != " + str(expected) +
" found "
1111 """Check to make sure the number of director references is as expected"""
1113 def __init__(self, testcase):
1114 IMP._director_objects.cleanup()
1115 self.__testcase = testcase
1116 self.__basenum = IMP._director_objects.get_object_count()
1119 """Make sure that the number of references matches the expected value.
1120 If force_cleanup is set, clean up any unused references first before
1121 doing the assertion.
1125 IMP._director_objects.cleanup()
1126 t.assertEqual(IMP._director_objects.get_object_count()
1127 - self.__basenum, expected)
1136 if sys.platform ==
'win32' and 'PYTHONPATH' in os.environ \
1137 and 'IMP_BIN_DIR' in os.environ:
1138 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
1139 bindir = os.environ[
'IMP_BIN_DIR']
1140 path = os.environ[
'PATH']
1141 if libdir
not in path
or bindir
not in path:
1142 os.environ[
'PATH'] = bindir +
';' + libdir +
';' + path
1145 __version__ =
"20260825.develop.6701db5d00"
1148 '''Return the version of this module, as a string'''
1149 return "20260825.develop.6701db5d00"
1152 '''Return the fully-qualified name of this module'''
1156 '''Return the full path to one of this module's data files'''
1158 return IMP._get_module_data_path(
"test", fname)
1161 '''Return the full path to one of this module's example files'''
1163 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.