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."""
153 def __init__(self, *args, **keys):
154 super().__init__(*args, **keys)
155 self._progname = Path(sys.argv[0]).absolute()
163 IMP.random_number_generator.seed(hash(time.time()) % 2**30)
169 if hasattr(self,
'_tmpdir'):
173 """Get the full name of an input file in the top-level
175 if self.__module__ ==
'__main__':
176 testdir = self._progname
178 testdir = Path(sys.modules[self.__module__].__file__)
179 for p
in testdir.parents:
182 ret = input / filename
184 raise IOError(
"Test input file %s does not exist" % ret)
186 raise IOError(
"No test input directory found")
189 """Open and return an input file in the top-level test directory."""
193 """Get the full name of an output file in the tmp directory.
194 The directory containing this file will be automatically
195 cleaned up when the test completes."""
196 if not hasattr(self,
'_tmpdir'):
197 self._tmpdir = _TempDir(os.environ.get(
'IMP_TMP_DIR'))
198 tmpdir = self._tmpdir.tmpdir
199 return str(Path(tmpdir) / filename)
202 """Get the magnitude of a list of floats"""
203 return sum(x*x
for x
in vector)**.5
206 """Assert that the given callable object raises UsageException.
207 This differs from unittest's assertRaises in that the test
208 is skipped in fast mode (where usage checks are turned off)."""
213 """Assert that the given callable object raises InternalException.
214 This differs from unittest's assertRaises in that the test
215 is skipped in fast mode (where internal checks are turned off)."""
220 """Assert that the given callable object is not implemented."""
225 """Assert that x,y,z analytical derivatives match numerical within
226 a tolerance, or a percentage (of the analytical value), whichever
227 is larger. `sf` should be a ScoringFunction or Restraint."""
229 derivs = xyz.get_derivatives()
231 pct = percentage / 100.0
232 self.assertAlmostEqual(
235 msg=
"Don't match "+str(derivs) + str(num_derivs))
236 self.assertAlmostEqual(derivs[0], num_derivs[0],
237 delta=max(tolerance, abs(derivs[0]) * pct))
238 self.assertAlmostEqual(derivs[1], num_derivs[1],
239 delta=max(tolerance, abs(derivs[1]) * pct))
240 self.assertAlmostEqual(derivs[2], num_derivs[2],
241 delta=max(tolerance, abs(derivs[2]) * pct))
244 """Fail if the given numpy array doesn't match expected"""
245 if IMP.IMP_KERNEL_HAS_NUMPY:
247 self.assertIsInstance(numpy_array, numpy.ndarray)
248 numpy.testing.assert_array_equal(numpy_array, exp_array)
250 self.assertEqual(numpy_array, exp_array)
254 """Fail if the difference between any two items in the two sequences
255 are exceed the specified number of places or delta. See
258 if delta
is not None and places
is not None:
259 raise TypeError(
"specify delta or places not both")
262 ftypename = ftype.__name__
264 stypename = stype.__name__
266 raise self.failureException(
267 'Sequences are of different types: %s != %s' % (
268 ftypename, stypename))
272 except (NotImplementedError, TypeError):
273 raise self.failureException(
274 'First %s has no length' % (ftypename))
277 except (NotImplementedError, TypeError):
278 raise self.failureException(
279 'Second %s has no length' % (stypename))
282 raise self.failureException(
283 'Sequences have non equal lengths: %d != %d' % (flen, slen))
286 for i
in range(min(flen, slen)):
287 differing =
'%ss differ: %s != %s\n' % (
288 ftypename.capitalize(), safe_repr(first),
293 except (TypeError, IndexError, NotImplementedError):
294 differing += (
'\nUnable to index element %d of first %s\n' %
300 except (TypeError, IndexError, NotImplementedError):
301 differing += (
'\nUnable to index element %d of second %s\n' %
306 self.assertAlmostEqual(
307 f, s, places=places, msg=msg, delta=delta)
308 except (TypeError, ValueError, NotImplementedError,
311 "\nFirst differing element "
312 "%d:\n%s\n%s\n") % (i, safe_repr(f), safe_repr(s))
317 standardMsg = differing
318 diffMsg =
'\n' +
'\n'.join(
319 difflib.ndiff(pprint.pformat(first).splitlines(),
320 pprint.pformat(second).splitlines()))
321 standardMsg = self._truncateMessage(standardMsg, diffMsg)
322 msg = self._formatMessage(msg, standardMsg)
323 raise self.failureException(msg)
325 def _read_cmake_cfg(self, cmake_cfg):
326 """Parse IMPConfig.cmake and extract info on the C++ compiler"""
327 cxx = flags = sysroot =
None
329 with open(cmake_cfg)
as fh:
331 if line.startswith(
'set(IMP_CXX_COMPILER '):
332 cxx = line.split(
'"')[1]
333 elif line.startswith(
'set(IMP_CXX_FLAGS '):
334 flags = line.split(
'"')[1]
335 elif line.startswith(
'set(IMP_OSX_SYSROOT '):
336 sysroot = line.split(
'"')[1]
337 elif line.startswith(
'SET(Boost_INCLUDE_DIR '):
338 includes.append(line.split(
'"')[1])
339 elif line.startswith(
'SET(EIGEN3_INCLUDE_DIR '):
340 includes.append(line.split(
'"')[1])
341 elif line.startswith(
'SET(cereal_INCLUDE_DIRS '):
342 includes.append(line.split(
'"')[1])
343 return cxx, flags, includes, sysroot
346 """Test that the given C++ code fails to compile with a static
348 if sys.platform ==
'win32':
349 self.skipTest(
"No support for Windows yet")
350 libdir = os.path.dirname(IMP.__file__)
351 cmake_cfg = os.path.join(libdir,
'..',
'..',
'IMPConfig.cmake')
352 if not os.path.exists(cmake_cfg):
353 self.skipTest(
"cannot find IMPConfig.cmake")
354 cxx, flags, includes, sysroot = self._read_cmake_cfg(cmake_cfg)
356 if sys.platform ==
'darwin' and sysroot:
357 flags = flags +
" -isysroot" + sysroot
358 includes.append(os.path.join(libdir,
'..',
'..',
'include'))
359 include =
" ".join(
"-I" + inc
for inc
in includes)
361 fname = os.path.join(tmpdir,
'test.cpp')
362 with open(fname,
'w')
as fh:
364 fh.write(
"#include <%s>\n" % h)
365 fh.write(
"\nint main() {\n" + body +
"\n return 0;\n}\n")
366 cmdline =
"%s %s %s %s" % (cxx, flags, include, fname)
368 p = subprocess.Popen(cmdline, shell=
True,
369 stdout=subprocess.PIPE,
370 stderr=subprocess.PIPE,
371 universal_newlines=
True)
372 out, err = p.communicate()
373 self.assertIn(
'error: static assertion failed', err)
376 """Make a particle with optimizable x, y and z attributes, and
377 add it to the model."""
385 """Help handle a test which is expected to fail some fraction of
386 the time. The test is run multiple times and an exception
387 is thrown only if it fails too many times.
388 @note Use of this function should be avoided. If there is a corner
389 case that results in a test 'occasionally' failing, write a
390 new test specifically for that corner case and assert that
391 it fails consistently (and remove the corner case from the
394 prob = chance_of_failure
398 prob = prob*chance_of_failure
399 for i
in range(0, tries):
407 raise AssertionError(
"Too many failures")
410 """Estimate how likely a given block of code is to raise an
414 while failures < 10
and tries < 1000:
420 return failures/tries
423 """Randomize the xyz coordinates of a list of particles"""
429 p.set_value(xkey, random.uniform(-deviation, deviation))
430 p.set_value(ykey, random.uniform(-deviation, deviation))
431 p.set_value(zkey, random.uniform(-deviation, deviation))
434 """Return distance between two given particles"""
438 dx = p1.get_value(xkey) - p2.get_value(xkey)
439 dy = p1.get_value(ykey) - p2.get_value(ykey)
440 dz = p1.get_value(zkey) - p2.get_value(zkey)
441 return math.sqrt(dx*dx + dy*dy + dz*dz)
444 """Check the unary function func's derivatives against numerical
445 approximations between lb and ub"""
446 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
447 (v, d) = func.evaluate_with_derivative(f)
449 self.assertAlmostEqual(d, da, delta=max(abs(.1 * d), 0.01))
452 """Make sure that the minimum of the unary function func over the
453 range between lb and ub is at expected_fmin"""
454 fmin, vmin = lb, func.evaluate(lb)
455 for f
in [lb + i * step
for i
in range(1, int((ub-lb)/step))]:
459 self.assertAlmostEqual(fmin, expected_fmin, delta=step)
462 """Check methods that every IMP::Object class should have"""
463 obj.set_was_used(
True)
466 self.assertIsNotNone(cls.get_from(obj))
467 self.assertRaises(ValueError, cls.get_from,
IMP.Model())
469 self.assertIsInstance(str(obj), str)
470 self.assertIsInstance(repr(obj), str)
472 verinf = obj.get_version_info()
481 """Create a bunch of particles in a box"""
483 lbv = IMP.algebra.Vector3D(lb[0], lb[1], lb[2])
484 ubv = IMP.algebra.Vector3D(ub[0], ub[1], ub[2])
486 for i
in range(0, num):
493 def _get_type(self, module, name):
494 return eval(
'type('+module+
"."+name+
')')
497 "Check that all the C++ classes in the module are values or objects."
499 ok = set(exceptions_list + module._value_types + module._object_types
500 + module._raii_types + module._plural_types)
504 if self._get_type(module.__name__, name) == type \
505 and not name.startswith(
"_"):
506 if name.find(
"SwigPyIterator") != -1:
509 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
510 % (module.__name__, name)):
517 "All IMP classes should be labeled as values or objects to get "
518 "memory management correct in Python. The following are not:\n%s\n"
519 "Please add an IMP_SWIG_OBJECT or IMP_SWIG_VALUE call to the "
520 "Python wrapper, or if the class has a good reason to be "
521 "neither, add the name to the value_object_exceptions list in "
522 "the IMPModuleTest call." % str(bad))
523 for e
in exceptions_list:
525 e
not in module._value_types + module._object_types
526 + module._raii_types + module._plural_types,
527 "Value/Object exception "+e+
" is not an exception")
529 def _check_spelling(self, word, words):
530 """Check that the word is spelled correctly"""
531 if "words" not in dir(self):
533 wordlist = fh.read().split("\n")
535 custom_words = [
"info",
"prechange",
"int",
"ints",
"optimizeds",
536 "graphviz",
"voxel",
"voxels",
"endian",
'rna',
537 'dna',
"xyzr",
"pdbs",
"fft",
"ccc",
"gaussian"]
540 exclude_words = set([
"adapter",
"grey"])
541 self.words = set(wordlist+custom_words) - exclude_words
543 for i
in "0123456789":
548 if word
in self.words:
556 """Check that all the classes in the module follow the IMP
557 naming conventions."""
561 cc = re.compile(
"([A-Z][a-z]*)")
563 if self._get_type(module.__name__, name) == type \
564 and not name.startswith(
"_"):
565 if name.find(
"SwigPyIterator") != -1:
567 for t
in re.findall(cc, name):
568 if not self._check_spelling(t.lower(), words):
569 misspelled.append(t.lower())
574 "All IMP classes should be properly spelled. The following "
575 "are not: %s.\nMisspelled words: %s. Add words to the "
576 "spelling_exceptions variable of the IMPModuleTest if needed."
577 % (str(bad),
", ".join(set(misspelled))))
580 if self._get_type(module.__name__, name) == type \
581 and not name.startswith(
"_"):
582 if name.find(
"SwigPyIterator") != -1:
584 if name.find(
'_') != -1:
586 if name.lower == name:
588 for t
in re.findall(cc, name):
589 if not self._check_spelling(t.lower(), words):
590 print(
"misspelled %s in %s" % (t, name))
594 "All IMP classes should have CamelCase names. The following "
595 "do not: %s." %
"\n".join(bad))
597 def _check_function_name(self, prefix, name, verbs, all, exceptions, words,
600 fullname = prefix+
"."+name
604 'unprotected_evaluate',
"unprotected_evaluate_if_good",
605 "unprotected_evaluate_if_below",
606 'unprotected_evaluate_moved',
"unprotected_evaluate_moved_if_good",
607 "unprotected_evaluate_moved_if_below",
608 "after_evaluate",
"before_evaluate",
"has_attribute",
609 "decorate_particle",
"particle_is_instance"]
610 if name
in old_exceptions:
612 if fullname
in exceptions:
614 if name.endswith(
"swigregister"):
616 if name.lower() != name:
617 if name[0].lower() != name[0]
and name.split(
'_')[0]
in all:
622 tokens = name.split(
"_")
623 if tokens[0]
not in verbs:
626 if not self._check_spelling(t, words):
628 print(
"misspelled %s in %s" % (t, name))
632 def _static_method(self, module, prefix, name):
633 """For static methods of the form Foo.bar SWIG creates free functions
634 named Foo_bar. Exclude these from spelling checks since the method
635 Foo.bar has already been checked."""
636 if prefix
is None and '_' in name:
637 modobj = eval(module)
638 cls, meth = name.split(
'_', 1)
639 if hasattr(modobj, cls):
640 clsobj = eval(module +
'.' + cls)
641 if hasattr(clsobj, meth):
644 def _check_function_names(self, module, prefix, names, verbs, all,
645 exceptions, words, misspelled):
648 typ = self._get_type(module, name)
649 if name.startswith(
"_")
or name ==
"weakref_proxy":
651 if typ
in (types.BuiltinMethodType, types.MethodType) \
652 or (typ == types.FunctionType
and
653 not self._static_method(module, prefix, name)):
654 bad.extend(self._check_function_name(prefix, name, verbs, all,
657 if typ == type
and "SwigPyIterator" not in name:
658 members = eval(
"dir("+module+
"."+name+
")")
659 bad.extend(self._check_function_names(module+
"."+name,
660 name, members, verbs, [],
666 """Check that all the functions in the module follow the IMP
667 naming conventions."""
669 verbs = set([
"add",
"remove",
"get",
"set",
"evaluate",
"compute",
670 "show",
"create",
"destroy",
"push",
"pop",
"write",
671 "read",
"do",
"show",
"load",
"save",
"reset",
"accept",
672 "reject",
"clear",
"handle",
"update",
"apply",
673 "optimize",
"reserve",
"dump",
"propose",
"setup",
674 "teardown",
"visit",
"find",
"run",
"swap",
"link",
675 "validate",
"erase",
"check"])
677 bad = self._check_function_names(module.__name__,
None, all, verbs,
678 all, exceptions, words, misspelled)
679 message = (
"All IMP methods and functions should have lower case "
680 "names separated by underscores and beginning with a "
681 "verb, preferably one of ['add', 'remove', 'get', 'set', "
682 "'create', 'destroy']. Each of the words should be a "
683 "properly spelled English word. The following do not "
684 "(given our limited list of verbs that we check for):\n"
685 "%(bad)s\nIf there is a good reason for them not to "
686 "(eg it does start with a verb, just one with a meaning "
687 "that is not covered by the normal list), add them to the "
688 "function_name_exceptions variable in the "
689 "standards_exceptions file. Otherwise, please fix. "
690 "The current verb list is %(verbs)s"
691 % {
"bad":
"\n".join(bad),
"verbs": verbs})
692 if len(misspelled) > 0:
693 message +=
"\nMisspelled words: " +
", ".join(set(misspelled)) \
694 +
". Add words to the spelling_exceptions variable " \
695 +
"of the standards_exceptions file if needed."
696 self.assertEqual(len(bad), 0, message)
699 """Check that all the classes in modulename have a show method"""
700 all = dir(modulename)
701 if hasattr(modulename,
'_raii_types'):
702 excludes = frozenset(
703 modulename._raii_types + modulename._plural_types)
706 excludes = frozenset()
713 if not eval(
'hasattr(%s.%s, "__swig_destroy__")'
714 % (modulename.__name__, f)):
716 if self._get_type(modulename.__name__, f) == type \
717 and not f.startswith(
"_") \
718 and not f.endswith(
"_swigregister")\
719 and f
not in exceptions\
720 and not f.endswith(
"Temp")
and not f.endswith(
"Iterator")\
721 and not f.endswith(
"Exception")
and\
723 if not hasattr(getattr(modulename, f),
'show'):
727 "All IMP classes should support show and __str__. The following "
728 "do not:\n%s\n If there is a good reason for them not to, add "
729 "them to the show_exceptions variable in the IMPModuleTest "
730 "call. Otherwise, please fix." %
"\n".join(not_found))
732 self.assertIn(e, all,
733 "Show exception "+e+
" is not a class in module")
734 self.assertTrue(
not hasattr(getattr(modulename, e),
'show'),
735 "Exception "+e+
" is not really a show exception")
738 """Run the named example script.
739 @return a dictionary of all the script's global variables.
740 This can be queried in a test case to make sure
741 the example performed correctly."""
747 path, name = os.path.split(filename)
748 oldsyspath = sys.path[:]
749 olssysargv = sys.argv[:]
750 sys.path.insert(0, path)
751 sys.argv = [filename]
754 exec(open(filename).read(), vars)
757 except SystemExit
as e:
758 if e.code != 0
and e.code
is not None:
760 "Example exit with code %s" % str(e.code))
763 sys.path = oldsyspath
764 sys.argv = olssysargv
766 return _ExecDictProxy(vars)
769 """Run a Python module as if with "python -m <modname>",
770 with the given list of arguments as sys.argv.
772 If module is an already-imported Python module, run its 'main'
773 function and return the result.
775 If module is a string, run the module in a subprocess and return
776 a subprocess.Popen-like object containing the child stdin,
779 def mock_setup_from_argv(*args, **kwargs):
782 if type(module) == type(os):
785 mod = __import__(module, {}, {}, [
''])
786 modpath = mod.__file__
787 if modpath.endswith(
'.pyc'):
788 modpath = modpath[:-1]
789 if type(module) == type(os):
790 old_sys_argv = sys.argv
792 old_setup = IMP.setup_from_argv
793 IMP.setup_from_argv = mock_setup_from_argv
795 sys.argv = [modpath] + args
798 IMP.setup_from_argv = old_setup
799 sys.argv = old_sys_argv
801 return _SubprocessWrapper(sys.executable, [modpath] + args)
804 """Check a Python module designed to be runnable with 'python -m'
805 to make sure it supports standard command line options."""
808 out, err = r.communicate()
809 self.assertEqual(r.returncode, 0)
810 self.assertNotEqual(err,
"")
811 self.assertEqual(out,
"")
814 class _ExecDictProxy:
815 """exec returns a Python dictionary, which contains IMP objects, other
816 Python objects, as well as base Python modules (such as sys and
817 __builtins__). If we just delete this dictionary, it is entirely
818 possible that base Python modules are removed from the dictionary
819 *before* some IMP objects. This will prevent the IMP objects' Python
820 destructors from running properly, so C++ objects will not be
821 cleaned up. This class proxies the base dict class, and on deletion
822 attempts to remove keys from the dictionary in an order that allows
823 IMP destructors to fire."""
824 def __init__(self, d):
829 module_type = type(IMP)
832 if type(d[k]) != module_type:
835 for meth
in [
'__contains__',
'__getitem__',
'__iter__',
'__len__',
836 'get',
'has_key',
'items',
'keys',
'values']:
837 exec(
"def %s(self, *args, **keys): "
838 "return self._d.%s(*args, **keys)" % (meth, meth))
841 class _TestResult(unittest.TextTestResult):
843 def __init__(self, stream=None, descriptions=None, verbosity=None):
844 super().__init__(stream, descriptions, verbosity)
847 def stopTestRun(self):
848 if 'IMP_TEST_DETAIL_DIR' in os.environ:
851 protocol = min(pickle.HIGHEST_PROTOCOL, 4)
852 fname = (Path(os.environ[
'IMP_TEST_DETAIL_DIR'])
853 / Path(sys.argv[0]).name)
857 if not fname.exists():
858 fname = Path(
"Z:") / fname
859 with open(str(fname),
'wb')
as fh:
860 pickle.dump(self.all_tests, fh, protocol)
861 super().stopTestRun()
863 def startTest(self, test):
864 super().startTest(test)
865 test.start_time = datetime.datetime.now()
867 def _test_finished(self, test, state, detail=None):
868 if hasattr(test,
'start_time'):
869 delta = datetime.datetime.now() - test.start_time
871 pv = delta.total_seconds()
872 except AttributeError:
873 pv = (float(delta.microseconds)
875 + delta.days * 24 * 3600) * 10**6) / 10**6
877 self.stream.write(
"in %.3fs ... " % pv)
882 if detail
is not None and not isinstance(detail, str):
883 detail = self._exc_info_to_string(detail, test)
884 test_doc = self.getDescription(test)
885 test_name = test.id()
886 if test_name.startswith(
'__main__.'):
887 test_name = test_name[9:]
888 self.all_tests.append({
'name': test_name,
889 'docstring': test_doc,
890 'time': pv,
'state': state,
'detail': detail})
892 def addSuccess(self, test):
893 self._test_finished(test,
'OK')
894 super().addSuccess(test)
896 def addError(self, test, err):
897 self._test_finished(test,
'ERROR', err)
898 super().addError(test, err)
900 def addFailure(self, test, err):
901 self._test_finished(test,
'FAIL', err)
902 super().addFailure(test, err)
904 def addSkip(self, test, reason):
905 self._test_finished(test,
'SKIP', reason)
906 super().addSkip(test, reason)
908 def addExpectedFailure(self, test, err):
909 self._test_finished(test,
'EXPFAIL', err)
910 super().addExpectedFailure(test, err)
912 def addUnexpectedSuccess(self, test):
913 self._test_finished(test,
'UNEXPSUC')
914 super().addUnexpectedSuccess(test)
916 def getDescription(self, test):
917 doc_first_line = test.shortDescription()
918 if self.descriptions
and doc_first_line:
919 return doc_first_line
924 class _TestRunner(unittest.TextTestRunner):
925 def _makeResult(self):
926 return _TestResult(self.stream, self.descriptions, self.verbosity)
930 """Run a set of tests; similar to unittest.main().
931 Obviates the need to separately import the 'unittest' module, and
932 ensures that main() is from the same unittest module that the
933 IMP.test testcases are. In addition, turns on some extra checks
934 (e.g. trying to use deprecated code will cause an exception
938 return unittest.main(testRunner=_TestRunner, *args, **keys)
941 class _SubprocessWrapper(subprocess.Popen):
942 def __init__(self, app, args, cwd=None):
945 if sys.platform ==
'win32' and app != sys.executable:
947 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
948 env = os.environ.copy()
949 env[
'PATH'] +=
';' + libdir
952 super().__init__([app]+list(args),
953 stdin=subprocess.PIPE,
954 stdout=subprocess.PIPE,
955 stderr=subprocess.PIPE, env=env, cwd=cwd,
956 universal_newlines=
True)
960 """Super class for simple IMP application test cases"""
961 def _get_application_file_name(self, filename):
964 if sys.platform ==
'win32':
969 """Run an application with the given list of arguments.
970 @return a subprocess.Popen-like object containing the child stdin,
973 filename = self._get_application_file_name(app)
974 if sys.platform ==
'win32':
976 return _SubprocessWrapper(os.path.join(os.environ[
'IMP_BIN_DIR'],
977 filename), args, cwd=cwd)
979 return _SubprocessWrapper(filename, args, cwd=cwd)
982 """Run a Python application with the given list of arguments.
983 The Python application should be self-runnable (i.e. it should
984 be executable and with a #! on the first line).
985 @return a subprocess.Popen-like object containing the child stdin,
989 if sys.executable !=
'/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
990 return _SubprocessWrapper(
992 [os.path.join(os.environ[
'IMP_BIN_DIR'], app)] + args)
994 return _SubprocessWrapper(app, args)
997 """Import an installed Python application, rather than running it.
998 This is useful to directly test components of the application.
999 @return the Python module object."""
1000 import importlib.machinery
1001 import importlib.util
1002 name = os.path.splitext(app)[0]
1003 if name
in sys.modules:
1004 return sys.modules[name]
1005 pathname = os.path.join(os.environ[
'IMP_BIN_DIR'], app)
1006 loader = importlib.machinery.SourceFileLoader(name, pathname)
1007 spec = importlib.util.spec_from_loader(name, loader)
1008 module = importlib.util.module_from_spec(spec)
1009 sys.modules[name] = module
1010 spec.loader.exec_module(module)
1014 """Run an application with the given list of arguments.
1015 @return a subprocess.Popen-like object containing the child stdin,
1018 return _SubprocessWrapper(sys.executable, [app]+args)
1021 """Assert that the application exited cleanly (return value = 0)."""
1023 raise OSError(
"Application exited with signal %d\n" % -ret
1028 "Application exited uncleanly, with exit code %d\n" % ret
1032 """Read and return a set of shell commands from a doxygen file.
1033 Each command is assumed to be in a \code{.sh}...\endcode block.
1034 The doxygen file is specified relative to the test file itself.
1035 This is used to make sure the commands shown in an application
1036 example actually work (the testcase can also check the resulting
1037 files for correctness)."""
1038 def win32_normpath(p):
1041 return " ".join([os.path.normpath(x)
for x
in p.split()])
1043 def fix_win32_command(cmd):
1045 if cmd.startswith(
'cp -r '):
1046 return 'xcopy /E ' + win32_normpath(cmd[6:])
1047 elif cmd.startswith(
'cp '):
1048 return 'copy ' + win32_normpath(cmd[3:])
1051 d = os.path.dirname(sys.argv[0])
1052 doc = os.path.join(d, doxfile)
1056 with open(doc)
as fh:
1057 for line
in fh.readlines():
1058 if '\code{.sh}' in line:
1060 elif '\endcode' in line:
1063 cmds.append(line.rstrip(
'\r\n').replace(
1064 '<imp_example_path>', example_path))
1065 if sys.platform ==
'win32':
1066 cmds = [fix_win32_command(x)
for x
in cmds]
1070 "Print and run a shell command, as returned by read_shell_commands()"
1072 p = subprocess.call(cmd, shell=
True)
1074 raise OSError(
"%s failed with exit value %d" % (cmd, p))
1078 """Check to make sure the number of C++ object references is as expected"""
1080 def __init__(self, testcase):
1084 IMP._director_objects.cleanup()
1085 self.__testcase = testcase
1087 self.__basenum = IMP.Object.get_number_of_live_objects()
1091 "Make sure that the number of references matches the expected value."
1093 IMP._director_objects.cleanup()
1096 if x
not in self.__names]
1097 newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1098 t.assertEqual(newnum, expected,
1099 "Number of objects don't match: "
1100 + str(newnum) +
" != " + str(expected) +
" found "
1105 """Check to make sure the number of director references is as expected"""
1107 def __init__(self, testcase):
1108 IMP._director_objects.cleanup()
1109 self.__testcase = testcase
1110 self.__basenum = IMP._director_objects.get_object_count()
1113 """Make sure that the number of references matches the expected value.
1114 If force_cleanup is set, clean up any unused references first before
1115 doing the assertion.
1119 IMP._director_objects.cleanup()
1120 t.assertEqual(IMP._director_objects.get_object_count()
1121 - self.__basenum, expected)
1130 if sys.platform ==
'win32' and 'PYTHONPATH' in os.environ \
1131 and 'IMP_BIN_DIR' in os.environ:
1132 libdir = os.environ[
'PYTHONPATH'].split(
';')[0]
1133 bindir = os.environ[
'IMP_BIN_DIR']
1134 path = os.environ[
'PATH']
1135 if libdir
not in path
or bindir
not in path:
1136 os.environ[
'PATH'] = bindir +
';' + libdir +
';' + path
1139 __version__ =
"20261109.develop.d36d3deffb"
1142 '''Return the version of this module, as a string'''
1143 return "20261109.develop.d36d3deffb"
1146 '''Return the fully-qualified name of this module'''
1150 '''Return the full path to one of this module's data files'''
1152 return IMP._get_module_data_path(
"test", fname)
1155 '''Return the full path to one of this module's example files'''
1157 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.