IMP logo
IMP Reference Guide  2.19.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  try:
696  exec(open(filename).read(), vars)
697  # Catch sys.exit() called from within the example; a non-zero exit
698  # value should cause the test case to fail
699  except SystemExit as e:
700  if e.code != 0 and e.code is not None:
701  raise _FatalError(
702  "Example exit with code %s" % str(e.code))
703  finally:
704  # Restore sys.path (note that Python 2.3 does not allow
705  # try/except/finally, so we need to use nested trys)
706  sys.path = oldsyspath
707  sys.argv = olssysargv
708 
709  return _ExecDictProxy(vars)
710 
711  def run_python_module(self, module, args):
712  """Run a Python module as if with "python -m <modname>",
713  with the given list of arguments as sys.argv.
714 
715  If module is an already-imported Python module, run its 'main'
716  function and return the result.
717 
718  If module is a string, run the module in a subprocess and return
719  a subprocess.Popen-like object containing the child stdin,
720  stdout and stderr.
721  """
722  def mock_setup_from_argv(*args, **kwargs):
723  # do-nothing replacement for boost command line parser
724  pass
725  if type(module) == type(os):
726  mod = module
727  else:
728  mod = __import__(module, {}, {}, [''])
729  modpath = mod.__file__
730  if modpath.endswith('.pyc'):
731  modpath = modpath[:-1]
732  if type(module) == type(os):
733  old_sys_argv = sys.argv
734  # boost parser doesn't like being called multiple times per process
735  old_setup = IMP.setup_from_argv
736  IMP.setup_from_argv = mock_setup_from_argv
737  try:
738  sys.argv = [modpath] + args
739  return module.main()
740  finally:
741  IMP.setup_from_argv = old_setup
742  sys.argv = old_sys_argv
743  else:
744  return _SubprocessWrapper(sys.executable, [modpath] + args)
745 
746  def check_runnable_python_module(self, module):
747  """Check a Python module designed to be runnable with 'python -m'
748  to make sure it supports standard command line options."""
749  # --help should return with exit 0, no errors
750  r = self.run_python_module(module, ['--help'])
751  out, err = r.communicate()
752  self.assertEqual(r.returncode, 0)
753  self.assertNotEqual(err, "")
754  self.assertEqual(out, "")
755 
756 
757 class _ExecDictProxy(object):
758  """exec returns a Python dictionary, which contains IMP objects, other
759  Python objects, as well as base Python modules (such as sys and
760  __builtins__). If we just delete this dictionary, it is entirely
761  possible that base Python modules are removed from the dictionary
762  *before* some IMP objects. This will prevent the IMP objects' Python
763  destructors from running properly, so C++ objects will not be
764  cleaned up. This class proxies the base dict class, and on deletion
765  attempts to remove keys from the dictionary in an order that allows
766  IMP destructors to fire."""
767  def __init__(self, d):
768  self._d = d
769 
770  def __del__(self):
771  # Try to release example objects in a sensible order
772  module_type = type(IMP)
773  d = self._d
774  for k in d.keys():
775  if type(d[k]) != module_type:
776  del d[k]
777 
778  for meth in ['__contains__', '__getitem__', '__iter__', '__len__',
779  'get', 'has_key', 'items', 'keys', 'values']:
780  exec("def %s(self, *args, **keys): "
781  "return self._d.%s(*args, **keys)" % (meth, meth))
782 
783 
784 class _TestResult(unittest.TextTestResult):
785 
786  def __init__(self, stream=None, descriptions=None, verbosity=None):
787  super(_TestResult, self).__init__(stream, descriptions, verbosity)
788  self.all_tests = []
789 
790  def stopTestRun(self):
791  if 'IMP_TEST_DETAIL_DIR' in os.environ:
792  fname = (Path(os.environ['IMP_TEST_DETAIL_DIR'])
793  / Path(sys.argv[0]).name)
794  with open(str(fname), 'wb') as fh:
795  pickle.dump(self.all_tests, fh, -1)
796  super(_TestResult, self).stopTestRun()
797 
798  def startTest(self, test):
799  super(_TestResult, self).startTest(test)
800  test.start_time = datetime.datetime.now()
801 
802  def _test_finished(self, test, state, detail=None):
803  delta = datetime.datetime.now() - test.start_time
804  try:
805  pv = delta.total_seconds()
806  except AttributeError:
807  pv = (float(delta.microseconds)
808  + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6
809  if pv > 1:
810  self.stream.write("in %.3fs ... " % pv)
811  if detail is not None and not isinstance(detail, str):
812  detail = self._exc_info_to_string(detail, test)
813  test_doc = self.getDescription(test)
814  test_name = test.id()
815  if test_name.startswith('__main__.'):
816  test_name = test_name[9:]
817  self.all_tests.append({'name': test_name,
818  'docstring': test_doc,
819  'time': pv, 'state': state, 'detail': detail})
820 
821  def addSuccess(self, test):
822  self._test_finished(test, 'OK')
823  super(_TestResult, self).addSuccess(test)
824 
825  def addError(self, test, err):
826  self._test_finished(test, 'ERROR', err)
827  super(_TestResult, self).addError(test, err)
828 
829  def addFailure(self, test, err):
830  self._test_finished(test, 'FAIL', err)
831  super(_TestResult, self).addFailure(test, err)
832 
833  def addSkip(self, test, reason):
834  self._test_finished(test, 'SKIP', reason)
835  super(_TestResult, self).addSkip(test, reason)
836 
837  def addExpectedFailure(self, test, err):
838  self._test_finished(test, 'EXPFAIL', err)
839  super(_TestResult, self).addExpectedFailure(test, err)
840 
841  def addUnexpectedSuccess(self, test):
842  self._test_finished(test, 'UNEXPSUC')
843  super(_TestResult, self).addUnexpectedSuccess(test)
844 
845  def getDescription(self, test):
846  doc_first_line = test.shortDescription()
847  if self.descriptions and doc_first_line:
848  return doc_first_line
849  else:
850  return str(test)
851 
852 
853 class _TestRunner(unittest.TextTestRunner):
854  def _makeResult(self):
855  return _TestResult(self.stream, self.descriptions, self.verbosity)
856 
857 
858 def main(*args, **keys):
859  """Run a set of tests; similar to unittest.main().
860  Obviates the need to separately import the 'unittest' module, and
861  ensures that main() is from the same unittest module that the
862  IMP.test testcases are. In addition, turns on some extra checks
863  (e.g. trying to use deprecated code will cause an exception
864  to be thrown)."""
865  import IMP
867  return unittest.main(testRunner=_TestRunner, *args, **keys)
868 
869 
870 class _SubprocessWrapper(subprocess.Popen):
871  def __init__(self, app, args, cwd=None):
872  # For (non-Python) applications to work on Windows, the
873  # PATH must include the directory containing built DLLs
874  if sys.platform == 'win32' and app != sys.executable:
875  # Hack to find the location of build/lib/
876  libdir = os.environ['PYTHONPATH'].split(';')[0]
877  env = os.environ.copy()
878  env['PATH'] += ';' + libdir
879  else:
880  env = None
881  subprocess.Popen.__init__(self, [app]+list(args),
882  stdin=subprocess.PIPE,
883  stdout=subprocess.PIPE,
884  stderr=subprocess.PIPE, env=env, cwd=cwd,
885  universal_newlines=True)
886 
887 
889  """Super class for simple IMP application test cases"""
890  def _get_application_file_name(self, filename):
891  # If we ran from run-all-tests.py, it set an env variable for us with
892  # the top-level test directory
893  if sys.platform == 'win32':
894  filename += '.exe'
895  return filename
896 
897  def run_application(self, app, args, cwd=None):
898  """Run an application with the given list of arguments.
899  @return a subprocess.Popen-like object containing the child stdin,
900  stdout and stderr.
901  """
902  filename = self._get_application_file_name(app)
903  if sys.platform == 'win32':
904  # Cannot rely on PATH on wine builds, so use full pathname
905  return _SubprocessWrapper(os.path.join(os.environ['IMP_BIN_DIR'],
906  filename), args, cwd=cwd)
907  else:
908  return _SubprocessWrapper(filename, args, cwd=cwd)
909 
910  def run_python_application(self, app, args):
911  """Run a Python application with the given list of arguments.
912  The Python application should be self-runnable (i.e. it should
913  be executable and with a #! on the first line).
914  @return a subprocess.Popen-like object containing the child stdin,
915  stdout and stderr.
916  """
917  # Handle platforms where /usr/bin/python doesn't work
918  if sys.executable != '/usr/bin/python' and 'IMP_BIN_DIR' in os.environ:
919  return _SubprocessWrapper(
920  sys.executable,
921  [os.path.join(os.environ['IMP_BIN_DIR'], app)] + args)
922  else:
923  return _SubprocessWrapper(app, args)
924 
926  """Import an installed Python application, rather than running it.
927  This is useful to directly test components of the application.
928  @return the Python module object."""
929  try:
930  import importlib.machinery
931  imp = None
932  except ImportError:
933  import imp
934  name = os.path.splitext(app)[0]
935  pathname = os.path.join(os.environ['IMP_BIN_DIR'], app)
936  if imp is None:
937  return importlib.machinery.SourceFileLoader(name,
938  pathname).load_module()
939  else:
940  return imp.load_source(name, pathname)
941 
942  def run_script(self, app, args):
943  """Run an application with the given list of arguments.
944  @return a subprocess.Popen-like object containing the child stdin,
945  stdout and stderr.
946  """
947  return _SubprocessWrapper(sys.executable, [app]+args)
948 
949  def assertApplicationExitedCleanly(self, ret, error):
950  """Assert that the application exited cleanly (return value = 0)."""
951  if ret < 0:
952  raise OSError("Application exited with signal %d\n" % -ret
953  + error)
954  else:
955  self.assertEqual(
956  ret, 0,
957  "Application exited uncleanly, with exit code %d\n" % ret
958  + error)
959 
960  def read_shell_commands(self, doxfile):
961  """Read and return a set of shell commands from a doxygen file.
962  Each command is assumed to be in a \code{.sh}...\endcode block.
963  The doxygen file is specified relative to the test file itself.
964  This is used to make sure the commands shown in an application
965  example actually work (the testcase can also check the resulting
966  files for correctness)."""
967  def win32_normpath(p):
968  # Sometimes Windows can read Unix-style paths, but sometimes it
969  # gets confused... so normalize all paths to be sure
970  return " ".join([os.path.normpath(x) for x in p.split()])
971 
972  def fix_win32_command(cmd):
973  # Make substitutions so a Unix shell command works on Windows
974  if cmd.startswith('cp -r '):
975  return 'xcopy /E ' + win32_normpath(cmd[6:])
976  elif cmd.startswith('cp '):
977  return 'copy ' + win32_normpath(cmd[3:])
978  else:
979  return cmd
980  d = os.path.dirname(sys.argv[0])
981  doc = os.path.join(d, doxfile)
982  inline = False
983  cmds = []
984  example_path = os.path.abspath(IMP.get_example_path('..'))
985  with open(doc) as fh:
986  for line in fh.readlines():
987  if '\code{.sh}' in line:
988  inline = True
989  elif '\endcode' in line:
990  inline = False
991  elif inline:
992  cmds.append(line.rstrip('\r\n').replace(
993  '<imp_example_path>', example_path))
994  if sys.platform == 'win32':
995  cmds = [fix_win32_command(x) for x in cmds]
996  return cmds
997 
998  def run_shell_command(self, cmd):
999  "Print and run a shell command, as returned by read_shell_commands()"
1000  print(cmd)
1001  p = subprocess.call(cmd, shell=True)
1002  if p != 0:
1003  raise OSError("%s failed with exit value %d" % (cmd, p))
1004 
1005 
1006 class RefCountChecker(object):
1007  """Check to make sure the number of C++ object references is as expected"""
1008 
1009  def __init__(self, testcase):
1010  # Make sure no director objects are hanging around; otherwise these
1011  # may be unexpectedly garbage collected later, decreasing the
1012  # live object count
1013  IMP._director_objects.cleanup()
1014  self.__testcase = testcase
1015  if IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL:
1016  self.__basenum = IMP.Object.get_number_of_live_objects()
1017  self.__names = IMP.get_live_object_names()
1018 
1019  def assert_number(self, expected):
1020  "Make sure that the number of references matches the expected value."
1021  t = self.__testcase
1022  IMP._director_objects.cleanup()
1023  if IMP.get_check_level() >= IMP.USAGE_AND_INTERNAL:
1024  newnames = [x for x in IMP.get_live_object_names()
1025  if x not in self.__names]
1026  newnum = IMP.Object.get_number_of_live_objects()-self.__basenum
1027  t.assertEqual(newnum, expected,
1028  "Number of objects don't match: "
1029  + str(newnum) + " != " + str(expected) + " found "
1030  + str(newnames))
1031 
1032 
1034  """Check to make sure the number of director references is as expected"""
1035 
1036  def __init__(self, testcase):
1037  IMP._director_objects.cleanup()
1038  self.__testcase = testcase
1039  self.__basenum = IMP._director_objects.get_object_count()
1040 
1041  def assert_number(self, expected, force_cleanup=True):
1042  """Make sure that the number of references matches the expected value.
1043  If force_cleanup is set, clean up any unused references first before
1044  doing the assertion.
1045  """
1046  t = self.__testcase
1047  if force_cleanup:
1048  IMP._director_objects.cleanup()
1049  t.assertEqual(IMP._director_objects.get_object_count()
1050  - self.__basenum, expected)
1051 
1052 
1053 # Make sure that the IMP binary directory (build/bin) is in the PATH, if
1054 # we're running under wine (the imppy.sh script normally ensures this, but
1055 # wine overrides the PATH). This is needed so that tests of imported Python
1056 # applications can successfully spawn C++ applications (e.g. idock.py tries
1057 # to run recompute_zscore.exe). build/lib also needs to be in the PATH, since
1058 # that's how Windows locates dependent DLLs such as libimp.dll.
1059 if sys.platform == 'win32' and 'PYTHONPATH' in os.environ \
1060  and 'IMP_BIN_DIR' in os.environ:
1061  libdir = os.environ['PYTHONPATH'].split(';')[0]
1062  bindir = os.environ['IMP_BIN_DIR']
1063  path = os.environ['PATH']
1064  if libdir not in path or bindir not in path:
1065  os.environ['PATH'] = bindir + ';' + libdir + ';' + path
1066 
1067 
1068 __version__ = "2.19.0"
1069 
1071  '''Return the version of this module, as a string'''
1072  return "2.19.0"
1073 
1074 def get_module_name():
1075  '''Return the fully-qualified name of this module'''
1076  return "IMP::test"
1077 
1078 def get_data_path(fname):
1079  '''Return the full path to one of this module's data files'''
1080  import IMP
1081  return IMP._get_module_data_path("test", fname)
1082 
1083 def get_example_path(fname):
1084  '''Return the full path to one of this module's example files'''
1085  import IMP
1086  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