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