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