IMP logo
IMP Reference Guide  2.20.0
The Integrative Modeling Platform
test/__init__.py
1 """@namespace IMP::test
2  @brief Methods and classes for testing the IMP kernel and modules.
3  @ingroup python
4 """
5 
6 import re
7 import math
8 import sys
9 import os
10 import tempfile
11 import random
12 import IMP
13 import time
14 import types
15 import shutil
16 import difflib
17 import pprint
18 import unittest
19 from unittest.util import safe_repr
20 import datetime
21 import pickle
22 import contextlib
23 import subprocess
24 try:
25  from pathlib import Path
26 except ImportError: # Use bundled pathlib on Python 2 without pathlib
27  from IMP._compat_pathlib import Path
28 
29 
30 # Expose some unittest decorators for convenience
31 expectedFailure = unittest.expectedFailure
32 skip = unittest.skip
33 skipIf = unittest.skipIf
34 skipUnless = unittest.skipUnless
35 
36 
37 class _TempDir(object):
38  def __init__(self, dir=None):
39  self.tmpdir = tempfile.mkdtemp(dir=dir)
40 
41  def __del__(self):
42  shutil.rmtree(self.tmpdir, ignore_errors=True)
43 
44 
45 @contextlib.contextmanager
47  """Simple context manager to run in a temporary directory.
48  While the context manager is active (within the 'with' block)
49  the current working directory is set to a temporary directory.
50  When the context manager exists, the working directory is reset
51  and the temporary directory deleted."""
52  origdir = os.getcwd()
53  tmpdir = tempfile.mkdtemp()
54  os.chdir(tmpdir)
55  yield tmpdir
56  os.chdir(origdir)
57  shutil.rmtree(tmpdir, ignore_errors=True)
58 
59 
60 @contextlib.contextmanager
61 def temporary_directory(dir=None):
62  """Simple context manager to make a temporary directory.
63  The temporary directory has the same lifetime as the context manager
64  (i.e. it is created at the start of the 'with' block, and deleted
65  at the end of the block).
66  @param dir If given, the temporary directory is made as a subdirectory
67  of that directory, rather than in the default temporary
68  directory location (e.g. /tmp)
69  @return the full path to the temporary directory.
70  """
71  tmpdir = tempfile.mkdtemp(dir=dir)
72  yield tmpdir
73  shutil.rmtree(tmpdir, ignore_errors=True)
74 
75 
76 def numerical_derivative(func, val, step):
77  """Calculate the derivative of the single-value function `func` at
78  point `val`. The derivative is calculated using simple finite
79  differences starting with the given `step`; Richardson extrapolation
80  is then used to extrapolate the derivative at step=0."""
81  maxsteps = 50
82  con = 1.4
83  safe = 2.0
84  err = 1.0e30
85  f1 = func(val + step)
86  f2 = func(val - step)
87  # create first element in triangular matrix d of derivatives
88  d = [[(f1 - f2) / (2.0 * step)]]
89  retval = None
90  for i in range(1, maxsteps):
91  d.append([0.] * (i + 1))
92  step /= con
93  f1 = func(val + step)
94  f2 = func(val - step)
95  d[i][0] = (f1 - f2) / (2.0 * step)
96  fac = con * con
97  for j in range(1, i + 1):
98  d[i][j] = (d[i][j-1] * fac - d[i-1][j-1]) / (fac - 1.)
99  fac *= con * con
100  errt = max(abs(d[i][j] - d[i][j-1]),
101  abs(d[i][j] - d[i-1][j-1]))
102  if errt <= err:
103  err = errt
104  retval = d[i][j]
105  if abs(d[i][i] - d[i-1][i-1]) >= safe * err:
106  break
107  if retval is None:
108  raise ValueError("Cannot calculate numerical derivative")
109  return retval
110 
111 
112 def xyz_numerical_derivatives(sf, xyz, step):
113  """Calculate the x,y and z derivatives of the scoring function `sf`
114  on the `xyz` particle. The derivatives are approximated numerically
115  using the numerical_derivatives() function."""
116  class _XYZDerivativeFunc(object):
117  def __init__(self, sf, xyz, basis_vector):
118  self._xyz = xyz
119  self._sf = sf
120  self._basis_vector = basis_vector
121  self._starting_coordinates = xyz.get_coordinates()
122 
123  def __call__(self, val):
124  self._xyz.set_coordinates(self._starting_coordinates +
125  self._basis_vector * val)
126  return self._sf.evaluate(False)
127 
128  return tuple([IMP.test.numerical_derivative(
129  _XYZDerivativeFunc(sf, xyz, IMP.algebra.Vector3D(*x)),
130  0, 0.01)
131  for x in ((1, 0, 0), (0, 1, 0), (0, 0, 1))])
132 
133 
134 class TestCase(unittest.TestCase):
135  """Super class for IMP test cases.
136  This provides a number of useful IMP-specific methods on top of
137  the standard Python `unittest.TestCase` class.
138  Test scripts should generally contain a subclass of this class,
139  conventionally called `Tests` (this makes it easier to run an
140  individual test from the command line) and use IMP::test::main()
141  as their main function."""
142 
143  # Provide assert(Not)Regex for Python 2 users (assertRegexMatches is
144  # deprecated in Python 3)
145  if not hasattr(unittest.TestCase, 'assertRegex'):
146  assertRegex = unittest.TestCase.assertRegexpMatches
147  assertNotRegex = unittest.TestCase.assertNotRegexpMatches
148 
149  def __init__(self, *args, **keys):
150  unittest.TestCase.__init__(self, *args, **keys)
151  self._progname = Path(sys.argv[0]).absolute()
152 
153  def setUp(self):
154  self.__check_level = IMP.get_check_level()
155  # Turn on expensive runtime checks while running the test suite:
156  IMP.set_check_level(IMP.USAGE_AND_INTERNAL)
157  # python ints are bigger than C++ ones, so we need to make sure it fits
158  # otherwise python throws fits
159  IMP.random_number_generator.seed(hash(time.time()) % 2**30)
160 
161  def tearDown(self):
162  # Restore original check level
163  IMP.set_check_level(self.__check_level)
164  # Clean up any temporary files
165  if hasattr(self, '_tmpdir'):
166  del self._tmpdir
167 
168  def get_input_file_name(self, filename):
169  """Get the full name of an input file in the top-level
170  test directory."""
171  if self.__module__ == '__main__':
172  testdir = self._progname
173  else:
174  testdir = Path(sys.modules[self.__module__].__file__)
175  for p in testdir.parents:
176  input = p / "input"
177  if input.is_dir():
178  ret = input / filename
179  if not ret.exists():
180  raise IOError("Test input file %s does not exist" % ret)
181  return str(ret)
182  raise IOError("No test input directory found")
183 
184  def open_input_file(self, filename, mode='rb'):
185  """Open and return an input file in the top-level test directory."""
186  return open(self.get_input_file_name(filename), mode)
187 
188  def get_tmp_file_name(self, filename):
189  """Get the full name of an output file in the tmp directory.
190  The directory containing this file will be automatically
191  cleaned up when the test completes."""
192  if not hasattr(self, '_tmpdir'):
193  self._tmpdir = _TempDir(os.environ.get('IMP_TMP_DIR'))
194  tmpdir = self._tmpdir.tmpdir
195  return str(Path(tmpdir) / filename)
196 
197  def get_magnitude(self, vector):
198  """Get the magnitude of a list of floats"""
199  return sum(x*x for x in vector)**.5
200 
201  def assertRaisesUsageException(self, c, *args, **keys):
202  """Assert that the given callable object raises UsageException.
203  This differs from unittest's assertRaises in that the test
204  is skipped in fast mode (where usage checks are turned off)."""
205  if IMP.get_check_level() >= IMP.USAGE:
206  return self.assertRaises(IMP.UsageException, c, *args, **keys)
207 
208  def assertRaisesInternalException(self, c, *args, **keys):
209  """Assert that the given callable object raises InternalException.
210  This differs from unittest's assertRaises in that the test
211  is skipped in fast mode (where internal checks are turned off)."""
212  if IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL:
213  return self.assertRaises(IMP.InternalException, c, *args, **keys)
214 
215  def assertNotImplemented(self, c, *args, **keys):
216  """Assert that the given callable object is not implemented."""
217  return self.assertRaises(IMP.InternalException, c, *args, **keys)
218 
219  def assertXYZDerivativesInTolerance(self, sf, xyz, tolerance=0,
220  percentage=0):
221  """Assert that x,y,z analytical derivatives match numerical within
222  a tolerance, or a percentage (of the analytical value), whichever
223  is larger. `sf` should be a ScoringFunction or Restraint."""
224  sf.evaluate(True)
225  derivs = xyz.get_derivatives()
226  num_derivs = xyz_numerical_derivatives(sf, xyz, 0.01)
227  pct = percentage / 100.0
228  self.assertAlmostEqual(
229  self.get_magnitude(derivs-num_derivs), 0,
230  delta=tolerance+percentage*self.get_magnitude(num_derivs),
231  msg="Don't match "+str(derivs) + str(num_derivs))
232  self.assertAlmostEqual(derivs[0], num_derivs[0],
233  delta=max(tolerance, abs(derivs[0]) * pct))
234  self.assertAlmostEqual(derivs[1], num_derivs[1],
235  delta=max(tolerance, abs(derivs[1]) * pct))
236  self.assertAlmostEqual(derivs[2], num_derivs[2],
237  delta=max(tolerance, abs(derivs[2]) * pct))
238 
239  def assertNumPyArrayEqual(self, numpy_array, exp_array):
240  """Fail if the given numpy array doesn't match expected"""
241  if IMP.IMP_KERNEL_HAS_NUMPY:
242  import numpy.testing
243  self.assertIsInstance(numpy_array, numpy.ndarray)
244  numpy.testing.assert_array_equal(numpy_array, exp_array)
245  else:
246  self.assertEqual(numpy_array, exp_array)
247 
248  def assertSequenceAlmostEqual(self, first, second, places=None, msg=None,
249  delta=None):
250  """Fail if the difference between any two items in the two sequences
251  are exceed the specified number of places or delta. See
252  `assertAlmostEqual`.
253  """
254  if delta is not None and places is not None:
255  raise TypeError("specify delta or places not both")
256 
257  ftype = type(first)
258  ftypename = ftype.__name__
259  stype = type(second)
260  stypename = stype.__name__
261  if ftype != stype:
262  raise self.failureException(
263  'Sequences are of different types: %s != %s' % (
264  ftypename, stypename))
265 
266  try:
267  flen = len(first)
268  except (NotImplementedError, TypeError):
269  raise self.failureException(
270  'First %s has no length' % (ftypename))
271  try:
272  slen = len(second)
273  except (NotImplementedError, TypeError):
274  raise self.failureException(
275  'Second %s has no length' % (stypename))
276 
277  if flen != slen:
278  raise self.failureException(
279  'Sequences have non equal lengths: %d != %d' % (flen, slen))
280 
281  differing = None
282  for i in range(min(flen, slen)):
283  differing = '%ss differ: %s != %s\n' % (
284  ftypename.capitalize(), safe_repr(first),
285  safe_repr(second))
286 
287  try:
288  f = first[i]
289  except (TypeError, IndexError, NotImplementedError):
290  differing += ('\nUnable to index element %d of first %s\n' %
291  (i, ftypename))
292  break
293 
294  try:
295  s = second[i]
296  except (TypeError, IndexError, NotImplementedError):
297  differing += ('\nUnable to index element %d of second %s\n' %
298  (i, stypename))
299  break
300 
301  try:
302  self.assertAlmostEqual(
303  f, s, places=places, msg=msg, delta=delta)
304  except (TypeError, ValueError, NotImplementedError,
305  AssertionError):
306  differing += (
307  "\nFirst differing element "
308  "%d:\n%s\n%s\n") % (i, safe_repr(f), safe_repr(s))
309  break
310  else:
311  return
312 
313  standardMsg = differing
314  diffMsg = '\n' + '\n'.join(
315  difflib.ndiff(pprint.pformat(first).splitlines(),
316  pprint.pformat(second).splitlines()))
317  standardMsg = self._truncateMessage(standardMsg, diffMsg)
318  msg = self._formatMessage(msg, standardMsg)
319  raise self.failureException(msg)
320 
321  def create_point_particle(self, model, x, y, z):
322  """Make a particle with optimizable x, y and z attributes, and
323  add it to the model."""
324  p = IMP.Particle(model)
325  p.add_attribute(IMP.FloatKey("x"), x, True)
326  p.add_attribute(IMP.FloatKey("y"), y, True)
327  p.add_attribute(IMP.FloatKey("z"), z, True)
328  return p
329 
330  def probabilistic_check(self, testcall, chance_of_failure):
331  """Help handle a test which is expected to fail some fraction of
332  the time. The test is run multiple times and an exception
333  is thrown only if it fails too many times.
334  @note Use of this function should be avoided. If there is a corner
335  case that results in a test 'occasionally' failing, write a
336  new test specifically for that corner case and assert that
337  it fails consistently (and remove the corner case from the
338  old test).
339  """
340  prob = chance_of_failure
341  tries = 1
342  while prob > .001:
343  tries += 1
344  prob = prob*chance_of_failure
345  for i in range(0, tries):
346  try:
347  eval(testcall)
348  except: # noqa: E722
349  pass
350  else:
351  return
352  eval(testcall)
353  raise AssertionError("Too many failures")
354 
355  def failure_probability(self, testcall):
356  """Estimate how likely a given block of code is to raise an
357  AssertionError."""
358  failures = 0
359  tries = 0.0
360  while failures < 10 and tries < 1000:
361  try:
362  eval(testcall)
363  except: # noqa: E722
364  failures += 1
365  tries = tries+1
366  return failures/tries
367 
368  def randomize_particles(self, particles, deviation):
369  """Randomize the xyz coordinates of a list of particles"""
370  # Note: cannot use XYZ here since that pulls in IMP.core
371  xkey = IMP.FloatKey("x")
372  ykey = IMP.FloatKey("y")
373  zkey = IMP.FloatKey("z")
374  for p in particles:
375  p.set_value(xkey, random.uniform(-deviation, deviation))
376  p.set_value(ykey, random.uniform(-deviation, deviation))
377  p.set_value(zkey, random.uniform(-deviation, deviation))
378 
379  def particle_distance(self, p1, p2):
380  """Return distance between two given particles"""
381  xkey = IMP.FloatKey("x")
382  ykey = IMP.FloatKey("y")
383  zkey = IMP.FloatKey("z")
384  dx = p1.get_value(xkey) - p2.get_value(xkey)
385  dy = p1.get_value(ykey) - p2.get_value(ykey)
386  dz = p1.get_value(zkey) - p2.get_value(zkey)
387  return math.sqrt(dx*dx + dy*dy + dz*dz)
388 
389  def check_unary_function_deriv(self, func, lb, ub, step):
390  """Check the unary function func's derivatives against numerical
391  approximations between lb and ub"""
392  for f in [lb + i * step for i in range(1, int((ub-lb)/step))]:
393  (v, d) = func.evaluate_with_derivative(f)
394  da = numerical_derivative(func.evaluate, f, step / 10.)
395  self.assertAlmostEqual(d, da, delta=max(abs(.1 * d), 0.01))
396 
397  def check_unary_function_min(self, func, lb, ub, step, expected_fmin):
398  """Make sure that the minimum of the unary function func over the
399  range between lb and ub is at expected_fmin"""
400  fmin, vmin = lb, func.evaluate(lb)
401  for f in [lb + i * step for i in range(1, int((ub-lb)/step))]:
402  v = func.evaluate(f)
403  if v < vmin:
404  fmin, vmin = f, v
405  self.assertAlmostEqual(fmin, expected_fmin, delta=step)
406 
408  """Check methods that every IMP::Object class should have"""
409  obj.set_was_used(True)
410  # Test get_from static method
411  cls = type(obj)
412  self.assertIsNotNone(cls.get_from(obj))
413  self.assertRaises(ValueError, cls.get_from, IMP.Model())
414  # Test __str__ and __repr__
415  self.assertIsInstance(str(obj), str)
416  self.assertIsInstance(repr(obj), str)
417  # Test get_version_info()
418  verinf = obj.get_version_info()
419  self.assertIsInstance(verinf, IMP.VersionInfo)
420  # Test SWIG thisown flag
421  o = obj.thisown
422  obj.thisown = o
423 
424  def create_particles_in_box(self, model, num=10,
425  lb=[0, 0, 0],
426  ub=[10, 10, 10]):
427  """Create a bunch of particles in a box"""
428  import IMP.algebra
429  lbv = IMP.algebra.Vector3D(lb[0], lb[1], lb[2])
430  ubv = IMP.algebra.Vector3D(ub[0], ub[1], ub[2])
431  ps = []
432  for i in range(0, num):
434  IMP.algebra.BoundingBox3D(lbv, ubv))
435  p = self.create_point_particle(model, v[0], v[1], v[2])
436  ps.append(p)
437  return ps
438 
439  def _get_type(self, module, name):
440  return eval('type('+module+"."+name+')')
441 
442  def assertValueObjects(self, module, exceptions_list):
443  "Check that all the C++ classes in the module are values or objects."
444  all = dir(module)
445  ok = set(exceptions_list + module._value_types + module._object_types
446  + module._raii_types + module._plural_types)
447 
448  bad = []
449  for name in all:
450  if self._get_type(module.__name__, name) == type \
451  and not name.startswith("_"):
452  if name.find("SwigPyIterator") != -1:
453  continue
454  # Exclude Python-only classes
455  if not eval('hasattr(%s.%s, "__swig_destroy__")'
456  % (module.__name__, name)):
457  continue
458  if name in ok:
459  continue
460  bad.append(name)
461  self.assertEqual(
462  len(bad), 0,
463  "All IMP classes should be labeled as values or objects to get "
464  "memory management correct in Python. The following are not:\n%s\n"
465  "Please add an IMP_SWIG_OBJECT or IMP_SWIG_VALUE call to the "
466  "Python wrapper, or if the class has a good reason to be "
467  "neither, add the name to the value_object_exceptions list in "
468  "the IMPModuleTest call." % str(bad))
469  for e in exceptions_list:
470  self.assertTrue(
471  e not in module._value_types + module._object_types
472  + module._raii_types + module._plural_types,
473  "Value/Object exception "+e+" is not an exception")
474 
475  def _check_spelling(self, word, words):
476  """Check that the word is spelled correctly"""
477  if "words" not in dir(self):
478  with open(IMP.test.get_data_path("linux.words"), "r") as fh:
479  wordlist = fh.read().split("\n")
480  # why is "all" missing on my mac?
481  custom_words = ["info", "prechange", "int", "ints", "optimizeds",
482  "graphviz", "voxel", "voxels", "endian", 'rna',
483  'dna', "xyzr", "pdbs", "fft", "ccc", "gaussian"]
484  # Exclude some common alternative spellings - we want to
485  # be consistent
486  exclude_words = set(["adapter", "grey"])
487  self.words = set(wordlist+custom_words) - exclude_words
488  if self.words:
489  for i in "0123456789":
490  if i in word:
491  return True
492  if word in words:
493  return True
494  if word in self.words:
495  return True
496  else:
497  return False
498  else:
499  return True
500 
501  def assertClassNames(self, module, exceptions, words):
502  """Check that all the classes in the module follow the IMP
503  naming conventions."""
504  all = dir(module)
505  misspelled = []
506  bad = []
507  cc = re.compile("([A-Z][a-z]*)")
508  for name in all:
509  if self._get_type(module.__name__, name) == type \
510  and not name.startswith("_"):
511  if name.find("SwigPyIterator") != -1:
512  continue
513  for t in re.findall(cc, name):
514  if not self._check_spelling(t.lower(), words):
515  misspelled.append(t.lower())
516  bad.append(name)
517 
518  self.assertEqual(
519  len(bad), 0,
520  "All IMP classes should be properly spelled. The following "
521  "are not: %s.\nMisspelled words: %s. Add words to the "
522  "spelling_exceptions variable of the IMPModuleTest if needed."
523  % (str(bad), ", ".join(set(misspelled))))
524 
525  for name in all:
526  if self._get_type(module.__name__, name) == type \
527  and not name.startswith("_"):
528  if name.find("SwigPyIterator") != -1:
529  continue
530  if name.find('_') != -1:
531  bad.append(name)
532  if name.lower == name:
533  bad.append(name)
534  for t in re.findall(cc, name):
535  if not self._check_spelling(t.lower(), words):
536  print("misspelled %s in %s" % (t, name))
537  bad.append(name)
538  self.assertEqual(
539  len(bad), 0,
540  "All IMP classes should have CamelCase names. The following "
541  "do not: %s." % "\n".join(bad))
542 
543  def _check_function_name(self, prefix, name, verbs, all, exceptions, words,
544  misspelled):
545  if prefix:
546  fullname = prefix+"."+name
547  else:
548  fullname = name
549  old_exceptions = [
550  'unprotected_evaluate', "unprotected_evaluate_if_good",
551  "unprotected_evaluate_if_below",
552  'unprotected_evaluate_moved', "unprotected_evaluate_moved_if_good",
553  "unprotected_evaluate_moved_if_below",
554  "after_evaluate", "before_evaluate", "has_attribute",
555  "decorate_particle", "particle_is_instance"]
556  if name in old_exceptions:
557  return []
558  if fullname in exceptions:
559  return []
560  if name.endswith("swigregister"):
561  return []
562  if name.lower() != name:
563  if name[0].lower() != name[0] and name.split('_')[0] in all:
564  # static methods
565  return []
566  else:
567  return [fullname]
568  tokens = name.split("_")
569  if tokens[0] not in verbs:
570  return [fullname]
571  for t in tokens:
572  if not self._check_spelling(t, words):
573  misspelled.append(t)
574  print("misspelled %s in %s" % (t, name))
575  return [fullname]
576  return []
577 
578  def _static_method(self, module, prefix, name):
579  """For static methods of the form Foo.bar SWIG creates free functions
580  named Foo_bar. Exclude these from spelling checks since the method
581  Foo.bar has already been checked."""
582  if prefix is None and '_' in name:
583  modobj = eval(module)
584  cls, meth = name.split('_', 1)
585  if hasattr(modobj, cls):
586  clsobj = eval(module + '.' + cls)
587  if hasattr(clsobj, meth):
588  return True
589 
590  def _check_function_names(self, module, prefix, names, verbs, all,
591  exceptions, words, misspelled):
592  bad = []
593  for name in names:
594  typ = self._get_type(module, name)
595  if name.startswith("_") or name == "weakref_proxy":
596  continue
597  if typ in (types.BuiltinMethodType, types.MethodType) \
598  or (typ == types.FunctionType and # noqa: E721
599  not self._static_method(module, prefix, name)):
600  bad.extend(self._check_function_name(prefix, name, verbs, all,
601  exceptions, words,
602  misspelled))
603  if typ == type and "SwigPyIterator" not in name:
604  members = eval("dir("+module+"."+name+")")
605  bad.extend(self._check_function_names(module+"."+name,
606  name, members, verbs, [],
607  exceptions, words,
608  misspelled))
609  return bad
610 
611  def assertFunctionNames(self, module, exceptions, words):
612  """Check that all the functions in the module follow the IMP
613  naming conventions."""
614  all = dir(module)
615  verbs = set(["add", "remove", "get", "set", "evaluate", "compute",
616  "show", "create", "destroy", "push", "pop", "write",
617  "read", "do", "show", "load", "save", "reset", "accept",
618  "reject", "clear", "handle", "update", "apply",
619  "optimize", "reserve", "dump", "propose", "setup",
620  "teardown", "visit", "find", "run", "swap", "link",
621  "validate", "erase"])
622  misspelled = []
623  bad = self._check_function_names(module.__name__, None, all, verbs,
624  all, exceptions, words, misspelled)
625  message = ("All IMP methods and functions should have lower case "
626  "names separated by underscores and beginning with a "
627  "verb, preferably one of ['add', 'remove', 'get', 'set', "
628  "'create', 'destroy']. Each of the words should be a "
629  "properly spelled English word. The following do not "
630  "(given our limited list of verbs that we check for):\n"
631  "%(bad)s\nIf there is a good reason for them not to "
632  "(eg it does start with a verb, just one with a meaning "
633  "that is not covered by the normal list), add them to the "
634  "function_name_exceptions variable in the "
635  "standards_exceptions file. Otherwise, please fix. "
636  "The current verb list is %(verbs)s"
637  % {"bad": "\n".join(bad), "verbs": verbs})
638  if len(misspelled) > 0:
639  message += "\nMisspelled words: " + ", ".join(set(misspelled)) \
640  + ". Add words to the spelling_exceptions variable " \
641  + "of the standards_exceptions file if needed."
642  self.assertEqual(len(bad), 0, message)
643 
644  def assertShow(self, modulename, exceptions):
645  """Check that all the classes in modulename have a show method"""
646  all = dir(modulename)
647  not_found = []
648  for f in all:
649  # Exclude SWIG C global variables object
650  if f == 'cvar':
651  continue
652  # Exclude Python-only classes; they are all showable
653  if not eval('hasattr(%s.%s, "__swig_destroy__")'
654  % (modulename.__name__, f)):
655  continue
656  if self._get_type(modulename.__name__, f) == type \
657  and not f.startswith("_") \
658  and not f.endswith("_swigregister")\
659  and f not in exceptions\
660  and not f.endswith("Temp") and not f.endswith("Iterator")\
661  and not f.endswith("Exception") and\
662  f not in modulename._raii_types and \
663  f not in modulename._plural_types:
664  if not hasattr(getattr(modulename, f), 'show'):
665  not_found.append(f)
666  self.assertEqual(
667  len(not_found), 0,
668  "All IMP classes should support show and __str__. The following "
669  "do not:\n%s\n If there is a good reason for them not to, add "
670  "them to the show_exceptions variable in the IMPModuleTest "
671  "call. Otherwise, please fix." % "\n".join(not_found))
672  for e in exceptions:
673  self.assertIn(e, all,
674  "Show exception "+e+" is not a class in module")
675  self.assertTrue(not hasattr(getattr(modulename, e), 'show'),
676  "Exception "+e+" is not really a show exception")
677 
678  def run_example(self, filename):
679  """Run the named example script.
680  @return a dictionary of all the script's global variables.
681  This can be queried in a test case to make sure
682  the example performed correctly."""
683  class _FatalError(Exception):
684  pass
685 
686  # Add directory containing the example to sys.path, so it can import
687  # other Python modules in the same directory
688  path, name = os.path.split(filename)
689  oldsyspath = sys.path[:]
690  olssysargv = sys.argv[:]
691  sys.path.insert(0, path)
692  sys.argv = [filename]
693  vars = {}
694  try:
695  exec(open(filename).read(), vars)
696  # Catch sys.exit() called from within the example; a non-zero exit
697  # value should cause the test case to fail
698  except SystemExit as e:
699  if e.code != 0 and e.code is not None:
700  raise _FatalError(
701  "Example exit with code %s" % str(e.code))
702  finally:
703  # Restore sys.path
704  sys.path = oldsyspath
705  sys.argv = olssysargv
706 
707  return _ExecDictProxy(vars)
708 
709  def run_python_module(self, module, args):
710  """Run a Python module as if with "python -m <modname>",
711  with the given list of arguments as sys.argv.
712 
713  If module is an already-imported Python module, run its 'main'
714  function and return the result.
715 
716  If module is a string, run the module in a subprocess and return
717  a subprocess.Popen-like object containing the child stdin,
718  stdout and stderr.
719  """
720  def mock_setup_from_argv(*args, **kwargs):
721  # do-nothing replacement for boost command line parser
722  pass
723  if type(module) == type(os): # noqa: E721
724  mod = module
725  else:
726  mod = __import__(module, {}, {}, [''])
727  modpath = mod.__file__
728  if modpath.endswith('.pyc'):
729  modpath = modpath[:-1]
730  if type(module) == type(os): # noqa: E721
731  old_sys_argv = sys.argv
732  # boost parser doesn't like being called multiple times per process
733  old_setup = IMP.setup_from_argv
734  IMP.setup_from_argv = mock_setup_from_argv
735  try:
736  sys.argv = [modpath] + args
737  return module.main()
738  finally:
739  IMP.setup_from_argv = old_setup
740  sys.argv = old_sys_argv
741  else:
742  return _SubprocessWrapper(sys.executable, [modpath] + args)
743 
744  def check_runnable_python_module(self, module):
745  """Check a Python module designed to be runnable with 'python -m'
746  to make sure it supports standard command line options."""
747  # --help should return with exit 0, no errors
748  r = self.run_python_module(module, ['--help'])
749  out, err = r.communicate()
750  self.assertEqual(r.returncode, 0)
751  self.assertNotEqual(err, "")
752  self.assertEqual(out, "")
753 
754 
755 class _ExecDictProxy(object):
756  """exec returns a Python dictionary, which contains IMP objects, other
757  Python objects, as well as base Python modules (such as sys and
758  __builtins__). If we just delete this dictionary, it is entirely
759  possible that base Python modules are removed from the dictionary
760  *before* some IMP objects. This will prevent the IMP objects' Python
761  destructors from running properly, so C++ objects will not be
762  cleaned up. This class proxies the base dict class, and on deletion
763  attempts to remove keys from the dictionary in an order that allows
764  IMP destructors to fire."""
765  def __init__(self, d):
766  self._d = d
767 
768  def __del__(self):
769  # Try to release example objects in a sensible order
770  module_type = type(IMP)
771  d = self._d
772  for k in d.keys():
773  if type(d[k]) != module_type: # noqa: E721
774  del d[k]
775 
776  for meth in ['__contains__', '__getitem__', '__iter__', '__len__',
777  'get', 'has_key', 'items', 'keys', 'values']:
778  exec("def %s(self, *args, **keys): "
779  "return self._d.%s(*args, **keys)" % (meth, meth))
780 
781 
782 class _TestResult(unittest.TextTestResult):
783 
784  def __init__(self, stream=None, descriptions=None, verbosity=None):
785  super(_TestResult, self).__init__(stream, descriptions, verbosity)
786  self.all_tests = []
787 
788  def stopTestRun(self):
789  if 'IMP_TEST_DETAIL_DIR' in os.environ:
790  # Various parts of the IMP build pipeline use Python 3.6,
791  # which predates pickle protocol 5
792  protocol = min(pickle.HIGHEST_PROTOCOL, 4)
793  fname = (Path(os.environ['IMP_TEST_DETAIL_DIR'])
794  / Path(sys.argv[0]).name)
795  with open(str(fname), 'wb') as fh:
796  pickle.dump(self.all_tests, fh, protocol)
797  super(_TestResult, self).stopTestRun()
798 
799  def startTest(self, test):
800  super(_TestResult, self).startTest(test)
801  test.start_time = datetime.datetime.now()
802 
803  def _test_finished(self, test, state, detail=None):
804  delta = datetime.datetime.now() - test.start_time
805  try:
806  pv = delta.total_seconds()
807  except AttributeError:
808  pv = (float(delta.microseconds)
809  + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6
810  if pv > 1:
811  self.stream.write("in %.3fs ... " % pv)
812  if detail is not None and not isinstance(detail, str):
813  detail = self._exc_info_to_string(detail, test)
814  test_doc = self.getDescription(test)
815  test_name = test.id()
816  if test_name.startswith('__main__.'):
817  test_name = test_name[9:]
818  self.all_tests.append({'name': test_name,
819  'docstring': test_doc,
820  'time': pv, 'state': state, 'detail': detail})
821 
822  def addSuccess(self, test):
823  self._test_finished(test, 'OK')
824  super(_TestResult, self).addSuccess(test)
825 
826  def addError(self, test, err):
827  self._test_finished(test, 'ERROR', err)
828  super(_TestResult, self).addError(test, err)
829 
830  def addFailure(self, test, err):
831  self._test_finished(test, 'FAIL', err)
832  super(_TestResult, self).addFailure(test, err)
833 
834  def addSkip(self, test, reason):
835  self._test_finished(test, 'SKIP', reason)
836  super(_TestResult, self).addSkip(test, reason)
837 
838  def addExpectedFailure(self, test, err):
839  self._test_finished(test, 'EXPFAIL', err)
840  super(_TestResult, self).addExpectedFailure(test, err)
841 
842  def addUnexpectedSuccess(self, test):
843  self._test_finished(test, 'UNEXPSUC')
844  super(_TestResult, self).addUnexpectedSuccess(test)
845 
846  def getDescription(self, test):
847  doc_first_line = test.shortDescription()
848  if self.descriptions and doc_first_line:
849  return doc_first_line
850  else:
851  return str(test)
852 
853 
854 class _TestRunner(unittest.TextTestRunner):
855  def _makeResult(self):
856  return _TestResult(self.stream, self.descriptions, self.verbosity)
857 
858 
859 def main(*args, **keys):
860  """Run a set of tests; similar to unittest.main().
861  Obviates the need to separately import the 'unittest' module, and
862  ensures that main() is from the same unittest module that the
863  IMP.test testcases are. In addition, turns on some extra checks
864  (e.g. trying to use deprecated code will cause an exception
865  to be thrown)."""
866  import IMP
868  return unittest.main(testRunner=_TestRunner, *args, **keys)
869 
870 
871 class _SubprocessWrapper(subprocess.Popen):
872  def __init__(self, app, args, cwd=None):
873  # For (non-Python) applications to work on Windows, the
874  # PATH must include the directory containing built DLLs
875  if sys.platform == 'win32' and app != sys.executable:
876  # Hack to find the location of build/lib/
877  libdir = os.environ['PYTHONPATH'].split(';')[0]
878  env = os.environ.copy()
879  env['PATH'] += ';' + libdir
880  else:
881  env = None
882  subprocess.Popen.__init__(self, [app]+list(args),
883  stdin=subprocess.PIPE,
884  stdout=subprocess.PIPE,
885  stderr=subprocess.PIPE, env=env, cwd=cwd,
886  universal_newlines=True)
887 
888 
890  """Super class for simple IMP application test cases"""
891  def _get_application_file_name(self, filename):
892  # If we ran from run-all-tests.py, it set an env variable for us with
893  # the top-level test directory
894  if sys.platform == 'win32':
895  filename += '.exe'
896  return filename
897 
898  def run_application(self, app, args, cwd=None):
899  """Run an application with the given list of arguments.
900  @return a subprocess.Popen-like object containing the child stdin,
901  stdout and stderr.
902  """
903  filename = self._get_application_file_name(app)
904  if sys.platform == 'win32':
905  # Cannot rely on PATH on wine builds, so use full pathname
906  return _SubprocessWrapper(os.path.join(os.environ['IMP_BIN_DIR'],
907  filename), args, cwd=cwd)
908  else:
909  return _SubprocessWrapper(filename, args, cwd=cwd)
910 
911  def run_python_application(self, app, args):
912  """Run a Python application with the given list of arguments.
913  The Python application should be self-runnable (i.e. it should
914  be executable and with a #! on the first line).
915  @return a subprocess.Popen-like object containing the child stdin,
916  stdout and stderr.
917  """
918  # Handle platforms where /usr/bin/python doesn't work
919  if sys.executable != '/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
920  return _SubprocessWrapper(
921  sys.executable,
922  [os.path.join(os.environ['IMP_BIN_DIR'], app)] + args)
923  else:
924  return _SubprocessWrapper(app, args)
925 
927  """Import an installed Python application, rather than running it.
928  This is useful to directly test components of the application.
929  @return the Python module object."""
930  try:
931  import importlib.machinery
932  imp = None
933  except ImportError:
934  import imp
935  name = os.path.splitext(app)[0]
936  pathname = os.path.join(os.environ['IMP_BIN_DIR'], app)
937  if imp is None:
938  return importlib.machinery.SourceFileLoader(name,
939  pathname).load_module()
940  else:
941  return imp.load_source(name, pathname)
942 
943  def run_script(self, app, args):
944  """Run an application with the given list of arguments.
945  @return a subprocess.Popen-like object containing the child stdin,
946  stdout and stderr.
947  """
948  return _SubprocessWrapper(sys.executable, [app]+args)
949 
950  def assertApplicationExitedCleanly(self, ret, error):
951  """Assert that the application exited cleanly (return value = 0)."""
952  if ret < 0:
953  raise OSError("Application exited with signal %d\n" % -ret
954  + error)
955  else:
956  self.assertEqual(
957  ret, 0,
958  "Application exited uncleanly, with exit code %d\n" % ret
959  + error)
960 
961  def read_shell_commands(self, doxfile):
962  """Read and return a set of shell commands from a doxygen file.
963  Each command is assumed to be in a \code{.sh}...\endcode block.
964  The doxygen file is specified relative to the test file itself.
965  This is used to make sure the commands shown in an application
966  example actually work (the testcase can also check the resulting
967  files for correctness)."""
968  def win32_normpath(p):
969  # Sometimes Windows can read Unix-style paths, but sometimes it
970  # gets confused... so normalize all paths to be sure
971  return " ".join([os.path.normpath(x) for x in p.split()])
972 
973  def fix_win32_command(cmd):
974  # Make substitutions so a Unix shell command works on Windows
975  if cmd.startswith('cp -r '):
976  return 'xcopy /E ' + win32_normpath(cmd[6:])
977  elif cmd.startswith('cp '):
978  return 'copy ' + win32_normpath(cmd[3:])
979  else:
980  return cmd
981  d = os.path.dirname(sys.argv[0])
982  doc = os.path.join(d, doxfile)
983  inline = False
984  cmds = []
985  example_path = os.path.abspath(IMP.get_example_path('..'))
986  with open(doc) as fh:
987  for line in fh.readlines():
988  if '\code{.sh}' in line:
989  inline = True
990  elif '\endcode' in line:
991  inline = False
992  elif inline:
993  cmds.append(line.rstrip('\r\n').replace(
994  '<imp_example_path>', example_path))
995  if sys.platform == 'win32':
996  cmds = [fix_win32_command(x) for x in cmds]
997  return cmds
998 
999  def run_shell_command(self, cmd):
1000  "Print and run a shell command, as returned by read_shell_commands()"
1001  print(cmd)
1002  p = subprocess.call(cmd, shell=True)
1003  if p != 0:
1004  raise OSError("%s failed with exit value %d" % (cmd, p))
1005 
1006 
1007 class RefCountChecker(object):
1008  """Check to make sure the number of C++ object references is as expected"""
1009 
1010  def __init__(self, testcase):
1011  # Make sure no director objects are hanging around; otherwise these
1012  # may be unexpectedly garbage collected later, decreasing the
1013  # live object count
1014  IMP._director_objects.cleanup()
1015  self.__testcase = testcase
1016  if IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL:
1017  self.__basenum = IMP.Object.get_number_of_live_objects()
1018  self.__names = IMP.get_live_object_names()
1019 
1020  def assert_number(self, expected):
1021  "Make sure that the number of references matches the expected value."
1022  t = self.__testcase
1023  IMP._director_objects.cleanup()
1024  if IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL:
1025  newnames = [x for x in IMP.get_live_object_names()
1026  if x not in self.__names]
1027  newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1028  t.assertEqual(newnum, expected,
1029  "Number of objects don't match: "
1030  + str(newnum) + " != " + str(expected) + " found "
1031  + str(newnames))
1032 
1033 
1035  """Check to make sure the number of director references is as expected"""
1036 
1037  def __init__(self, testcase):
1038  IMP._director_objects.cleanup()
1039  self.__testcase = testcase
1040  self.__basenum = IMP._director_objects.get_object_count()
1041 
1042  def assert_number(self, expected, force_cleanup=True):
1043  """Make sure that the number of references matches the expected value.
1044  If force_cleanup is set, clean up any unused references first before
1045  doing the assertion.
1046  """
1047  t = self.__testcase
1048  if force_cleanup:
1049  IMP._director_objects.cleanup()
1050  t.assertEqual(IMP._director_objects.get_object_count()
1051  - self.__basenum, expected)
1052 
1053 
1054 # Make sure that the IMP binary directory (build/bin) is in the PATH, if
1055 # we're running under wine (the imppy.sh script normally ensures this, but
1056 # wine overrides the PATH). This is needed so that tests of imported Python
1057 # applications can successfully spawn C++ applications (e.g. idock.py tries
1058 # to run recompute_zscore.exe). build/lib also needs to be in the PATH, since
1059 # that's how Windows locates dependent DLLs such as libimp.dll.
1060 if sys.platform == 'win32' and 'PYTHONPATH' in os.environ \
1061  and 'IMP_BIN_DIR' in os.environ:
1062  libdir = os.environ['PYTHONPATH'].split(';')[0]
1063  bindir = os.environ['IMP_BIN_DIR']
1064  path = os.environ['PATH']
1065  if libdir not in path or bindir not in path:
1066  os.environ['PATH'] = bindir + ';' + libdir + ';' + path
1067 
1068 
1069 __version__ = "2.20.0"
1070 
1072  '''Return the version of this module, as a string'''
1073  return "2.20.0"
1074 
1075 def get_module_name():
1076  '''Return the fully-qualified name of this module'''
1077  return "IMP::test"
1078 
1079 def get_data_path(fname):
1080  '''Return the full path to one of this module's data files'''
1081  import IMP
1082  return IMP._get_module_data_path("test", fname)
1083 
1084 def get_example_path(fname):
1085  '''Return the full path to one of this module's example files'''
1086  import IMP
1087  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.
Definition: exception.h:80
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.
Definition: exception.h:101
def main
Run a set of tests; similar to unittest.main().
An exception for an invalid usage of IMP.
Definition: exception.h:122
Super class for simple IMP application test cases.
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.
Definition: Model.h:86
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.
Definition: VersionInfo.h:29
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.
Definition: exception.h:48
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.
VectorD< 3 > Vector3D
Definition: VectorD.h:425
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.
Definition: Particle.h:43
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.
Definition: exception.h:72