LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
tests.py
Go to the documentation of this file.
1 ##
2 # @file tests.py
3 #
4 # Control which tests run, and how.
5 ##
6 from __future__ import print_function
7 import glob, os, re, sys
8 from SCons.Script import * # So that this file has the same namespace as SConstruct/SConscript
9 from . import state
10 
11 ##
12 # @brief A class to control unit tests.
13 #
14 # This class is unchanged from previous versions of sconsUtils, but it will now generally
15 # be called via scripts.BasicSConscript.tests().
16 ##
17 class Control(object):
18  _IGNORE = "IGNORE"
19  _EXPECT_FAILURE = "EXPECT_FAILURE"
20 
21  ##
22  # @brief Create an object to run tests
23  #
24  # @param env An SCons Environment (almost always lsst.sconsUtils.env).
25  # @param ignoreList A list of tests that should NOT be run --- useful in conjunction
26  # with glob patterns. If a file is listed as "@fileName", the @ is stripped and
27  # we don't bother to check if fileName exists (useful for machine-generated files).
28  # @param expectedFalures A dictionary; the keys are tests that are known to fail; the values
29  # are strings to print.
30  # @param args A dictionary with testnames as keys, and argument strings as values.
31  # As scons always runs from the top-level directory, tests has to fiddle with
32  # paths. If an argument is a file this is done automatically; if it's e.g.
33  # just a basename then you have to tell tests that it's really (part of a)
34  # filename by prefixing the name by "file:".
35  #
36  # @param tmpDir The location of the test outputs.
37  # @param verbose How chatty you want the test code to be.
38  #
39  # @code
40  # tests = lsst.tests.Control(
41  # env,
42  # args={
43  # "MaskIO_1" : "data/871034p_1_MI_msk.fits",
44  # "MaskedImage_1" : "file:data/871034p_1_MI foo",
45  # },
46  # ignoreList=["Measure_1"],
47  # expectedFailures={"BBox_1": "Problem with single-pixel BBox"}
48  # )
49  # @endcode
50  ##
51  def __init__(self, env, ignoreList=None, expectedFailures=None, args=None,
52  tmpDir=".tests", verbose=False):
53  if 'PYTHONPATH' in os.environ:
54  env.AppendENVPath('PYTHONPATH', os.environ['PYTHONPATH'])
55 
56  self._env = env
57 
58  self._tmpDir = tmpDir
59  self._cwd = os.path.abspath(os.path.curdir)
60 
61  self._verbose = verbose
62 
63  self._info = {} # information about processing targets
64  if ignoreList:
65  for f in ignoreList:
66  if re.search(r"^@", f): # @dfilename => don't complain if filename doesn't exist
67  f = f[1:]
68  else:
69  if not os.path.exists(f):
70  state.log.warn("You're ignoring a non-existent file, %s" % f)
71  self._info[f] = (self._IGNORE, None)
72 
73  if expectedFailures:
74  for f in expectedFailures:
75  self._info[f] = (self._EXPECT_FAILURE, expectedFailures[f])
76 
77  if args:
78  self._args = args # arguments for tests
79  else:
80  self._args = {}
81 
82  self.runExamples = True # should I run the examples?
83  try:
84  self.runExamples = (os.stat(self._tmpDir).st_mode & 0o700) != 0 # file is user read/write/executable
85  except OSError:
86  pass
87 
88  if not self.runExamples:
89  print("Not running examples; \"chmod 755 %s\" to run them again" % self._tmpDir,
90  file=sys.stderr)
91 
92  def args(self, test):
93  try:
94  return self._args[test]
95  except KeyError:
96  return ""
97 
98  def ignore(self, test):
99  if \
100  not re.search(r"\.py$", test) and \
101  len(self._env.Glob(test)) == 0: # we don't know how to build it
102  return True
103 
104  ignoreFile = test in self._info and self._info[test][0] == self._IGNORE
105 
106  if self._verbose and ignoreFile:
107  print("Skipping", test, file=sys.stderr)
108 
109  return ignoreFile
110 
111  def messages(self, test):
112  """Return the messages to be used in case of success/failure; the logicals
113  (note that they are strings) tell whether the test is expected to pass"""
114 
115  if test in self._info and self._info[test][0] == self._EXPECT_FAILURE:
116  msg = self._info[test][1]
117  return "false", "Passed, but should have failed: %s" % msg, \
118  "true", "Failed as expected: %s" % msg
119  else:
120  return "true", "passed", \
121  "false", "failed"
122 
123  def run(self, fileGlob):
124  if not isinstance(fileGlob, basestring): # env.Glob() returns an scons Node
125  fileGlob = str(fileGlob)
126  targets = []
127  if not self.runExamples:
128  return targets
129  for f in glob.glob(fileGlob):
130  interpreter = "" # interpreter to run test, if needed
131 
132  if re.search(r"\.cc", f): # look for executable
133  f = os.path.splitext(f)[0]
134  else:
135  interpreter = "python"
136 
137  if self.ignore(f):
138  continue
139 
140  target = os.path.join(self._tmpDir, f)
141 
142  args = []
143  for a in self.args(f).split(" "):
144  # if a is a file, make it an absolute name as scons runs from the root directory
145  filePrefix = "file:"
146  if re.search(r"^" + filePrefix, a): # they explicitly said that this was a file
147  a = os.path.join(self._cwd, a[len(filePrefix):])
148  else:
149  try: # see if it's a file
150  os.stat(a)
151  a = os.path.join(self._cwd, a)
152  except OSError:
153  pass
154 
155  args += [a]
156 
157  (should_pass, passedMsg, should_fail, failedMsg) = self.messages(f)
158 
159  # The TRAVIS environment variable is set to allow us to disable
160  # the matplotlib font cache. See ticket DM-3856.
161  # TODO: Work out better way of solving matplotlib issue in build.
162  expandedArgs = " ".join(args)
163  result = self._env.Command(target, f, """
164  @rm -f ${TARGET}.failed;
165  @printf "%%s" 'running ${SOURCES}... ';
166  @echo $SOURCES %s > $TARGET; echo >> $TARGET;
167  @if TRAVIS=1 %s $SOURCES %s >> $TARGET 2>&1; then \
168  if ! %s; then mv $TARGET ${TARGET}.failed; fi; \
169  echo "%s"; \
170  else \
171  if ! %s; then mv $TARGET ${TARGET}.failed; fi; \
172  echo "%s"; \
173  fi;
174  """ % (expandedArgs, interpreter, expandedArgs, should_pass, passedMsg, should_fail, failedMsg))
175 
176  targets.extend(result)
177 
178  self._env.Alias(os.path.basename(target), target)
179 
180  self._env.Clean(target, self._tmpDir)
181 
182  return targets
A class to control unit tests.
Definition: tests.py:17
def __init__
Create an object to run tests.
Definition: tests.py:52