LSSTApplications  16.0-10-g0ee56ad+4,16.0-11-ga33d1f2+4,16.0-12-g3ef5c14+2,16.0-12-g71e5ef5+17,16.0-12-gbdf3636+2,16.0-13-g118c103+2,16.0-13-g8f68b0a+2,16.0-15-gbf5c1cb+3,16.0-16-gfd17674+2,16.0-17-g7c01f5c+2,16.0-18-g0a50484,16.0-20-ga20f992+7,16.0-21-g0e05fd4+5,16.0-21-g15e2d33+3,16.0-22-g62d8060+3,16.0-22-g847a80f+3,16.0-25-gf00d9b8,16.0-28-g3990c221+3,16.0-3-gf928089+2,16.0-32-g88a4f23+4,16.0-34-gd7987ad+2,16.0-37-gc7333cb+1,16.0-4-g10fc685+1,16.0-4-g18f3627+25,16.0-4-g5f3a788+25,16.0-5-gaf5c3d7+3,16.0-5-gcc1f4bb,16.0-6-g3b92700+3,16.0-6-g4412fcd+2,16.0-6-g7235603+3,16.0-69-g2562ce1b+1,16.0-7-g0913a87,16.0-8-g14ebd58+3,16.0-8-g2df868b,16.0-8-g4cec79c+5,16.0-8-gadf6c7a,16.0-82-g59ec2a54a,16.0-9-g5400cdc+1,16.0-9-ge6233d7+4,master-g2880f2d8cf+2,v17.0.rc1
LSSTDataManagementBasePackage
Public Member Functions | Public Attributes | List of all members
lsst.sconsUtils.tests.Control Class Reference

A class to control unit tests. More...

Public Member Functions

def __init__ (self, env, ignoreList=None, expectedFailures=None, args=None, tmpDir=".tests", verbose=False)
 Create an object to run tests. More...
 
def args (self, test)
 
def ignore (self, test)
 
def messages (self, test)
 
def run (self, fileGlob)
 
def runPythonTests (self, pyList)
 
def junitPrefix (self)
 

Public Attributes

 runExamples
 

Detailed Description

A class to control unit tests.

This class is unchanged from previous versions of sconsUtils, but it will now generally be called via scripts.BasicSConscript.tests().

Definition at line 21 of file tests.py.

Constructor & Destructor Documentation

◆ __init__()

def lsst.sconsUtils.tests.Control.__init__ (   self,
  env,
  ignoreList = None,
  expectedFailures = None,
  args = None,
  tmpDir = ".tests",
  verbose = False 
)

Create an object to run tests.

Parameters
envAn SCons Environment (almost always lsst.sconsUtils.env).
ignoreListA list of tests that should NOT be run — useful in conjunction with glob patterns. If a file is listed as "@fileName", the @ is stripped and we don't bother to check if fileName exists (useful for machine-generated files).
expectedFaluresA dictionary; the keys are tests that are known to fail; the values are strings to print.
argsA dictionary with testnames as keys, and argument strings as values. As scons always runs from the top-level directory, tests has to fiddle with paths. If an argument is a file this is done automatically; if it's e.g. just a basename then you have to tell tests that it's really (part of a) filename by prefixing the name by "file:".
tmpDirThe location of the test outputs.
verboseHow chatty you want the test code to be.
tests = lsst.tests.Control(
env,
args={
"MaskIO_1" : "data/871034p_1_MI_msk.fits",
"MaskedImage_1" : "file:data/871034p_1_MI foo",
},
ignoreList=["Measure_1"],
expectedFailures={"BBox_1": "Problem with single-pixel BBox"}
)

Definition at line 56 of file tests.py.

56  tmpDir=".tests", verbose=False):
57  if 'PYTHONPATH' in os.environ:
58  env.AppendENVPath('PYTHONPATH', os.environ['PYTHONPATH'])
59 
60  self._env = env
61 
62  self._tmpDir = tmpDir
63  self._cwd = os.path.abspath(os.path.curdir)
64 
65  # Calculate the absolute path for temp dir if it is relative.
66  # This assumes the temp dir is relative to where the tests SConscript
67  # file is located. SCons will know how to handle this itself but
68  # some options require the code to know where to write things.
69  if os.path.isabs(self._tmpDir):
70  self._tmpDirAbs = self._tmpDir
71  else:
72  self._tmpDirAbs = os.path.join(self._cwd, self._tmpDir)
73 
74  self._verbose = verbose
75 
76  self._info = {} # information about processing targets
77  if ignoreList:
78  for f in ignoreList:
79  if f.startswith("@"): # @dfilename => don't complain if filename doesn't exist
80  f = f[1:]
81  else:
82  if not os.path.exists(f):
83  state.log.warn("You're ignoring a non-existent file, %s" % f)
84  self._info[f] = (self._IGNORE, None)
85 
86  if expectedFailures:
87  for f in expectedFailures:
88  self._info[f] = (self._EXPECT_FAILURE, expectedFailures[f])
89 
90  if args:
91  self._args = args # arguments for tests
92  else:
93  self._args = {}
94 
95  self.runExamples = True # should I run the examples?
96  try:
97  # file is user read/write/executable
98  self.runExamples = (os.stat(self._tmpDir).st_mode & 0o700) != 0
99  except OSError:
100  pass
101 
102  if not self.runExamples:
103  print("Not running examples; \"chmod 755 %s\" to run them again" % self._tmpDir,
104  file=sys.stderr)
105 

Member Function Documentation

◆ args()

def lsst.sconsUtils.tests.Control.args (   self,
  test 
)

Definition at line 106 of file tests.py.

106  def args(self, test):
107  try:
108  return self._args[test]
109  except KeyError:
110  return ""
111 

◆ ignore()

def lsst.sconsUtils.tests.Control.ignore (   self,
  test 
)

Definition at line 112 of file tests.py.

112  def ignore(self, test):
113  if not test.endswith(".py") and \
114  len(self._env.Glob(test)) == 0: # we don't know how to build it
115  return True
116 
117  ignoreFile = test in self._info and self._info[test][0] == self._IGNORE
118 
119  if self._verbose and ignoreFile:
120  print("Skipping", test, file=sys.stderr)
121 
122  return ignoreFile
123 

◆ junitPrefix()

def lsst.sconsUtils.tests.Control.junitPrefix (   self)

Definition at line 315 of file tests.py.

315  def junitPrefix(self):
316  controlVar = "LSST_JUNIT_PREFIX"
317  prefix = self._env['eupsProduct']
318 
319  if controlVar in os.environ:
320  prefix += ".{0}".format(os.environ[controlVar])
321 
322  return prefix
323 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168

◆ messages()

def lsst.sconsUtils.tests.Control.messages (   self,
  test 
)
Return the messages to be used in case of success/failure; the logicals
(note that they are strings) tell whether the test is expected to pass

Definition at line 124 of file tests.py.

124  def messages(self, test):
125  """Return the messages to be used in case of success/failure; the logicals
126  (note that they are strings) tell whether the test is expected to pass"""
127 
128  if test in self._info and self._info[test][0] == self._EXPECT_FAILURE:
129  msg = self._info[test][1]
130  return ("false", "Passed, but should have failed: %s" % msg,
131  "true", "Failed as expected: %s" % msg)
132  else:
133  return ("true", "passed",
134  "false", "failed")
135 

◆ run()

def lsst.sconsUtils.tests.Control.run (   self,
  fileGlob 
)
Create a test target for each file matching the supplied glob.

Definition at line 136 of file tests.py.

136  def run(self, fileGlob):
137  """Create a test target for each file matching the supplied glob.
138  """
139 
140  if not isinstance(fileGlob, str): # env.Glob() returns an scons Node
141  fileGlob = str(fileGlob)
142  targets = []
143  if not self.runExamples:
144  return targets
145 
146  # Determine any library load path values that we have to prepend
147  # to the command.
148  libpathstr = utils.libraryLoaderEnvironment()
149 
150  for f in glob.glob(fileGlob):
151  interpreter = "" # interpreter to run test, if needed
152 
153  if f.endswith(".cc"): # look for executable
154  f = os.path.splitext(f)[0]
155  else:
156  interpreter = "pytest -Wd --junit-xml=${TARGET}.xml"
157  interpreter += " --junit-prefix={0}".format(self.junitPrefix())
158  interpreter += self._getPytestCoverageCommand()
159 
160  if self.ignore(f):
161  continue
162 
163  target = os.path.join(self._tmpDir, f)
164 
165  args = []
166  for a in self.args(f).split(" "):
167  # if a is a file, make it an absolute name as scons runs from the root directory
168  filePrefix = "file:"
169  if a.startswith(filePrefix): # they explicitly said that this was a file
170  a = os.path.join(self._cwd, a[len(filePrefix):])
171  else:
172  try: # see if it's a file
173  os.stat(a)
174  a = os.path.join(self._cwd, a)
175  except OSError:
176  pass
177 
178  args += [a]
179 
180  (should_pass, passedMsg, should_fail, failedMsg) = self.messages(f)
181 
182  # The TRAVIS environment variable is set to allow us to disable
183  # the matplotlib font cache. See ticket DM-3856.
184  # TODO: Work out better way of solving matplotlib issue in build.
185  expandedArgs = " ".join(args)
186  result = self._env.Command(target, f, """
187  @rm -f ${TARGET}.failed;
188  @printf "%%s" 'running ${SOURCES}... ';
189  @echo $SOURCES %s > $TARGET; echo >> $TARGET;
190  @if %s TRAVIS=1 %s $SOURCES %s >> $TARGET 2>&1; then \
191  if ! %s; then mv $TARGET ${TARGET}.failed; fi; \
192  echo "%s"; \
193  else \
194  if ! %s; then mv $TARGET ${TARGET}.failed; fi; \
195  echo "%s"; \
196  fi;
197  """ % (expandedArgs, libpathstr, interpreter, expandedArgs, should_pass,
198  passedMsg, should_fail, failedMsg))
199 
200  targets.extend(result)
201 
202  self._env.Alias(os.path.basename(target), target)
203 
204  self._env.Clean(target, self._tmpDir)
205 
206  return targets
207 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168
def run(suite, exit=True)
Definition: tests.py:85

◆ runPythonTests()

def lsst.sconsUtils.tests.Control.runPythonTests (   self,
  pyList 
)
Add a single target for testing all python files. pyList is
a list of nodes corresponding to python test files. The
IgnoreList is respected when scanning for entries. If pyList
is None, or an empty list, it uses automated test discovery
within pytest. This differs from the behavior of scripts.tests()
where a distinction is made. Returns a list containing a single
target.

Definition at line 208 of file tests.py.

208  def runPythonTests(self, pyList):
209  """Add a single target for testing all python files. pyList is
210  a list of nodes corresponding to python test files. The
211  IgnoreList is respected when scanning for entries. If pyList
212  is None, or an empty list, it uses automated test discovery
213  within pytest. This differs from the behavior of scripts.tests()
214  where a distinction is made. Returns a list containing a single
215  target."""
216 
217  if pyList is None:
218  pyList = []
219 
220  # Determine any library load path values that we have to prepend
221  # to the command.
222  libpathstr = utils.libraryLoaderEnvironment()
223 
224  # Get list of python files with the path included.
225  pythonTestFiles = []
226  for fileGlob in pyList:
227  if not isinstance(fileGlob, str): # env.Glob() returns an scons Node
228  fileGlob = str(fileGlob)
229  for f in glob.glob(fileGlob):
230  if self.ignore(f):
231  continue
232  pythonTestFiles.append(os.path.join(self._cwd, f))
233 
234  # Now set up the python testing target
235  # We always want to run this with the tests target.
236  # We have decided to use pytest caching so that on reruns we only
237  # run failed tests.
238  lfnfOpt = "none" if 'install' in SCons.Script.COMMAND_LINE_TARGETS else "all"
239  interpreter = f"pytest -Wd --lf --lfnf={lfnfOpt}"
240  interpreter += " --junit-xml=${TARGET} --session2file=${TARGET}.out"
241  interpreter += " --junit-prefix={0}".format(self.junitPrefix())
242  interpreter += self._getPytestCoverageCommand()
243 
244  target = os.path.join(self._tmpDir, "pytest-{}.xml".format(self._env['eupsProduct']))
245 
246  # Work out how many jobs scons has been configured to use
247  # and use that number with pytest. This could cause trouble
248  # if there are lots of binary tests to run and lots of singles.
249  njobs = self._env.GetOption("num_jobs")
250  print("Running pytest with {} process{}".format(njobs, "" if njobs == 1 else "es"))
251  if njobs > 1:
252  # We unambiguously specify the Python interpreter to be used to
253  # execute tests. This ensures that all pytest-xdist worker
254  # processes refer to the same Python as the xdist master, and
255  # hence avoids pollution of ``sys.path`` that can happen when we
256  # call the same interpreter by different paths (for example, if
257  # the master process calls ``miniconda/bin/python``, and the
258  # workers call ``current/bin/python``, the workers will end up
259  # with site-packages directories corresponding to both locations
260  # on ``sys.path``, even if the one is a symlink to the other).
261  executable = os.path.realpath(sys.executable)
262 
263  # if there is a space in the executable path we have to use the original
264  # method and hope things work okay. This will be rare but without
265  # this a space in the path is impossible because of how xdist
266  # currently parses the tx option
267  interpreter = interpreter + " --max-slave-restart=0"
268  if " " not in executable:
269  interpreter = (interpreter +
270  " -d --tx={}*popen//python={}".format(njobs, executable))
271  else:
272  interpreter = interpreter + " -n {}".format(njobs)
273 
274  # Remove target so that we always trigger pytest
275  if os.path.exists(target):
276  os.unlink(target)
277 
278  if not pythonTestFiles:
279  print("pytest: automated test discovery mode enabled.")
280  else:
281  nfiles = len(pythonTestFiles)
282  print("pytest: running on {} Python test file{}.".format(nfiles, "" if nfiles == 1 else "s"))
283 
284  # If we ran all the test, then copy the previous test
285  # execution products to `.all' files so we can retrieve later.
286  # If we skip the test (exit code 5), retrieve those `.all' files.
287  cmd = ""
288  if lfnfOpt == "all":
289  cmd += "@rm -f ${{TARGET}} ${{TARGET}}.failed;"
290  cmd += """
291  @printf "%s\\n" 'running global pytest... ';
292  @({2} TRAVIS=1 {0} {1}); \
293  export rc="$?"; \
294  if [ "$$rc" -eq 0 ]; then \
295  echo "Global pytest run completed successfully"; \
296  cp ${{TARGET}} ${{TARGET}}.all || true; \
297  cp ${{TARGET}}.out ${{TARGET}}.out.all || true; \
298  elif [ "$$rc" -eq 5 ]; then \
299  echo "Global pytest run completed successfully - no tests ran"; \
300  mv ${{TARGET}}.all ${{TARGET}} || true; \
301  mv ${{TARGET}}.out.all ${{TARGET}}.out || true; \
302  else \
303  echo "Global pytest run: failed with $$rc"; \
304  mv ${{TARGET}}.out ${{TARGET}}.failed; \
305  fi;
306  """
307  testfiles = " ".join([pipes.quote(p) for p in pythonTestFiles])
308  result = self._env.Command(target, None, cmd.format(interpreter, testfiles, libpathstr))
309 
310  self._env.Alias(os.path.basename(target), target)
311  self._env.Clean(target, self._tmpDir)
312 
313  return [result]
314 
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:168

Member Data Documentation

◆ runExamples

lsst.sconsUtils.tests.Control.runExamples

Definition at line 95 of file tests.py.


The documentation for this class was generated from the following file: