This helps lit unit test performance by a lot, especially on windows. The performance gain comes from launching one gtest executable for many subtests instead of one (this is the current situation). The shards are executed by the test runner and the results are stored in the json format supported by the GoogleTest. Later in the test reporting stage, all test results in the json file are retrieved to continue the test results summary etc. On my Win10 desktop, before this patch: `check-clang-unit`: 177s, `check-llvm-unit`: 38s; after this patch: `check-clang-unit`: 37s, `check-llvm-unit`: 11s. On my Linux machine, before this patch: `check-clang-unit`: 46s, `check-llvm-unit`: 8s; after this patch: `check-clang-unit`: 7s, `check-llvm-unit`: 4s. Reviewed By: yln, rnk, abrachet Differential Revision: https://reviews.llvm.org/D122251
204 lines
7.6 KiB
Python
204 lines
7.6 KiB
Python
from __future__ import absolute_import
|
|
import inspect
|
|
import os
|
|
import platform
|
|
import sys
|
|
|
|
import lit.Test
|
|
import lit.formats
|
|
import lit.TestingConfig
|
|
import lit.util
|
|
|
|
# LitConfig must be a new style class for properties to work
|
|
class LitConfig(object):
|
|
"""LitConfig - Configuration data for a 'lit' test runner instance, shared
|
|
across all tests.
|
|
|
|
The LitConfig object is also used to communicate with client configuration
|
|
files, it is always passed in as the global variable 'lit' so that
|
|
configuration files can access common functionality and internal components
|
|
easily.
|
|
"""
|
|
|
|
def __init__(self, progname, path, quiet,
|
|
useValgrind, valgrindLeakCheck, valgrindArgs,
|
|
noExecute, debug, isWindows, order,
|
|
params, config_prefix = None,
|
|
maxIndividualTestTime = 0,
|
|
parallelism_groups = {},
|
|
echo_all_commands = False):
|
|
# The name of the test runner.
|
|
self.progname = progname
|
|
# The items to add to the PATH environment variable.
|
|
self.path = [str(p) for p in path]
|
|
self.quiet = bool(quiet)
|
|
self.useValgrind = bool(useValgrind)
|
|
self.valgrindLeakCheck = bool(valgrindLeakCheck)
|
|
self.valgrindUserArgs = list(valgrindArgs)
|
|
self.noExecute = noExecute
|
|
self.debug = debug
|
|
self.isWindows = bool(isWindows)
|
|
self.order = order
|
|
self.params = dict(params)
|
|
self.bashPath = None
|
|
|
|
# Configuration files to look for when discovering test suites.
|
|
self.config_prefix = config_prefix or 'lit'
|
|
self.suffixes = ['cfg.py', 'cfg']
|
|
self.config_names = ['%s.%s' % (self.config_prefix,x) for x in self.suffixes]
|
|
self.site_config_names = ['%s.site.%s' % (self.config_prefix,x) for x in self.suffixes]
|
|
self.local_config_names = ['%s.local.%s' % (self.config_prefix,x) for x in self.suffixes]
|
|
|
|
self.numErrors = 0
|
|
self.numWarnings = 0
|
|
|
|
self.valgrindArgs = []
|
|
if self.useValgrind:
|
|
self.valgrindArgs = ['valgrind', '-q', '--run-libc-freeres=no',
|
|
'--tool=memcheck', '--trace-children=yes',
|
|
'--error-exitcode=123']
|
|
if self.valgrindLeakCheck:
|
|
self.valgrindArgs.append('--leak-check=full')
|
|
else:
|
|
# The default is 'summary'.
|
|
self.valgrindArgs.append('--leak-check=no')
|
|
self.valgrindArgs.extend(self.valgrindUserArgs)
|
|
|
|
self.maxIndividualTestTime = maxIndividualTestTime
|
|
self.parallelism_groups = parallelism_groups
|
|
self.echo_all_commands = echo_all_commands
|
|
|
|
@property
|
|
def maxIndividualTestTime(self):
|
|
"""
|
|
Interface for getting maximum time to spend executing
|
|
a single test
|
|
"""
|
|
return self._maxIndividualTestTime
|
|
|
|
@property
|
|
def maxIndividualTestTimeIsSupported(self):
|
|
"""
|
|
Returns a tuple (<supported> , <error message>)
|
|
where
|
|
`<supported>` is True if setting maxIndividualTestTime is supported
|
|
on the current host, returns False otherwise.
|
|
`<error message>` is an empty string if `<supported>` is True,
|
|
otherwise is contains a string describing why setting
|
|
maxIndividualTestTime is not supported.
|
|
"""
|
|
return lit.util.killProcessAndChildrenIsSupported()
|
|
|
|
@maxIndividualTestTime.setter
|
|
def maxIndividualTestTime(self, value):
|
|
"""
|
|
Interface for setting maximum time to spend executing
|
|
a single test
|
|
"""
|
|
if not isinstance(value, int):
|
|
self.fatal('maxIndividualTestTime must set to a value of type int.')
|
|
self._maxIndividualTestTime = value
|
|
if self.maxIndividualTestTime > 0:
|
|
# The current implementation needs psutil on some platforms to set
|
|
# a timeout per test. Check it's available.
|
|
# See lit.util.killProcessAndChildren()
|
|
supported, errormsg = self.maxIndividualTestTimeIsSupported
|
|
if not supported:
|
|
self.fatal('Setting a timeout per test not supported. ' +
|
|
errormsg)
|
|
elif self.maxIndividualTestTime < 0:
|
|
self.fatal('The timeout per test must be >= 0 seconds')
|
|
|
|
def load_config(self, config, path):
|
|
"""load_config(config, path) - Load a config object from an alternate
|
|
path."""
|
|
if self.debug:
|
|
self.note('load_config from %r' % path)
|
|
config.load_from_path(path, self)
|
|
return config
|
|
|
|
def getBashPath(self):
|
|
"""getBashPath - Get the path to 'bash'"""
|
|
if self.bashPath is not None:
|
|
return self.bashPath
|
|
|
|
self.bashPath = lit.util.which('bash', os.pathsep.join(self.path))
|
|
if self.bashPath is None:
|
|
self.bashPath = lit.util.which('bash')
|
|
|
|
if self.bashPath is None:
|
|
self.bashPath = ''
|
|
|
|
# Check whether the found version of bash is able to cope with paths in
|
|
# the host path format. If not, don't return it as it can't be used to
|
|
# run scripts. For example, WSL's bash.exe requires '/mnt/c/foo' rather
|
|
# than 'C:\\foo' or 'C:/foo'.
|
|
if self.isWindows and self.bashPath:
|
|
command = [self.bashPath, '-c',
|
|
'[[ -f "%s" ]]' % self.bashPath.replace('\\', '\\\\')]
|
|
_, _, exitCode = lit.util.executeCommand(command)
|
|
if exitCode:
|
|
self.note('bash command failed: %s' % (
|
|
' '.join('"%s"' % c for c in command)))
|
|
self.bashPath = ''
|
|
|
|
if not self.bashPath:
|
|
self.warning('Unable to find a usable version of bash.')
|
|
|
|
return self.bashPath
|
|
|
|
def getToolsPath(self, dir, paths, tools):
|
|
if dir is not None and os.path.isabs(dir) and os.path.isdir(dir):
|
|
if not lit.util.checkToolsPath(dir, tools):
|
|
return None
|
|
else:
|
|
dir = lit.util.whichTools(tools, paths)
|
|
|
|
# bash
|
|
self.bashPath = lit.util.which('bash', dir)
|
|
if self.bashPath is None:
|
|
self.bashPath = ''
|
|
|
|
return dir
|
|
|
|
def _write_message(self, kind, message):
|
|
# Get the file/line where this message was generated.
|
|
f = inspect.currentframe()
|
|
# Step out of _write_message, and then out of wrapper.
|
|
f = f.f_back.f_back
|
|
file = os.path.abspath(inspect.getsourcefile(f))
|
|
line = inspect.getlineno(f)
|
|
sys.stderr.write('%s: %s:%d: %s: %s\n' % (self.progname, file, line,
|
|
kind, message))
|
|
if self.isWindows:
|
|
# In a git bash terminal, the writes to sys.stderr aren't visible
|
|
# on screen immediately. Flush them here to avoid broken/misoredered
|
|
# output.
|
|
sys.stderr.flush()
|
|
|
|
def substitute(self, string):
|
|
"""substitute - Interpolate params into a string"""
|
|
try:
|
|
return string % self.params
|
|
except KeyError as e:
|
|
key, = e.args
|
|
self.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (
|
|
key,key))
|
|
|
|
def note(self, message):
|
|
if not self.quiet:
|
|
self._write_message('note', message)
|
|
|
|
def warning(self, message):
|
|
if not self.quiet:
|
|
self._write_message('warning', message)
|
|
self.numWarnings += 1
|
|
|
|
def error(self, message):
|
|
self._write_message('error', message)
|
|
self.numErrors += 1
|
|
|
|
def fatal(self, message):
|
|
self._write_message('fatal', message)
|
|
sys.exit(2)
|