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