blob: bb77aa310c713dc55f6fa77eabac786ea59f5dfe [file] [log] [blame]
"""
Test discovery functions.
"""
import copy
import os
import sys
from lit.TestingConfig import TestingConfig
from lit import LitConfig, Test
def chooseConfigFileFromDir(dir, config_names):
for name in config_names:
p = os.path.join(dir, name)
if os.path.exists(p):
return p
return None
def dirContainsTestSuite(path, lit_config):
cfgpath = chooseConfigFileFromDir(path, lit_config.site_config_names)
if not cfgpath:
cfgpath = chooseConfigFileFromDir(path, lit_config.config_names)
return cfgpath
def getTestSuite(item, litConfig, cache):
"""getTestSuite(item, litConfig, cache) -> (suite, relative_path)
Find the test suite containing @arg item.
@retval (None, ...) - Indicates no test suite contains @arg item.
@retval (suite, relative_path) - The suite that @arg item is in, and its
relative path inside that suite.
"""
def search1(path):
# Check for a site config or a lit config.
cfgpath = dirContainsTestSuite(path, litConfig)
# If we didn't find a config file, keep looking.
if not cfgpath:
parent,base = os.path.split(path)
if parent == path:
return (None, ())
ts, relative = search(parent)
return (ts, relative + (base,))
# This is a private builtin parameter which can be used to perform
# translation of configuration paths. Specifically, this parameter
# can be set to a dictionary that the discovery process will consult
# when it finds a configuration it is about to load. If the given
# path is in the map, the value of that key is a path to the
# configuration to load instead.
config_map = litConfig.params.get('config_map')
if config_map:
cfgpath = os.path.realpath(cfgpath)
target = config_map.get(os.path.normcase(cfgpath))
if target:
cfgpath = target
# We found a test suite, create a new config for it and load it.
if litConfig.debug:
litConfig.note('loading suite config %r' % cfgpath)
cfg = TestingConfig.fromdefaults(litConfig)
cfg.load_from_path(cfgpath, litConfig)
source_root = os.path.realpath(cfg.test_source_root or path)
exec_root = os.path.realpath(cfg.test_exec_root or path)
return Test.TestSuite(cfg.name, source_root, exec_root, cfg), ()
def search(path):
# Check for an already instantiated test suite.
real_path = os.path.realpath(path)
res = cache.get(real_path)
if res is None:
cache[real_path] = res = search1(path)
return res
# Canonicalize the path.
item = os.path.normpath(os.path.join(os.getcwd(), item))
# Skip files and virtual components.
components = []
while not os.path.isdir(item):
parent,base = os.path.split(item)
if parent == item:
return (None, ())
components.append(base)
item = parent
components.reverse()
ts, relative = search(item)
return ts, tuple(relative + tuple(components))
def getLocalConfig(ts, path_in_suite, litConfig, cache):
def search1(path_in_suite):
# Get the parent config.
if not path_in_suite:
parent = ts.config
else:
parent = search(path_in_suite[:-1])
# Check if there is a local configuration file.
source_path = ts.getSourcePath(path_in_suite)
cfgpath = chooseConfigFileFromDir(source_path, litConfig.local_config_names)
# If not, just reuse the parent config.
if not cfgpath:
return parent
# Otherwise, copy the current config and load the local configuration
# file into it.
config = copy.deepcopy(parent)
if litConfig.debug:
litConfig.note('loading local config %r' % cfgpath)
config.load_from_path(cfgpath, litConfig)
return config
def search(path_in_suite):
key = (ts, path_in_suite)
res = cache.get(key)
if res is None:
cache[key] = res = search1(path_in_suite)
return res
return search(path_in_suite)
def getTests(path, litConfig, testSuiteCache,
localConfigCache, indirectlyRunCheck):
# Find the test suite for this input and its relative path.
ts,path_in_suite = getTestSuite(path, litConfig, testSuiteCache)
if ts is None:
litConfig.warning('unable to find test suite for %r' % path)
return (),()
if litConfig.debug:
litConfig.note('resolved input %r to %r::%r' % (path, ts.name,
path_in_suite))
return ts, getTestsInSuite(ts, path_in_suite, litConfig,
testSuiteCache, localConfigCache, indirectlyRunCheck)
def getTestsInSuite(ts, path_in_suite, litConfig,
testSuiteCache, localConfigCache, indirectlyRunCheck):
# Check that the source path exists (errors here are reported by the
# caller).
source_path = ts.getSourcePath(path_in_suite)
if not os.path.exists(source_path):
return
# Check if the user named a test directly.
if not os.path.isdir(source_path):
test_dir_in_suite = path_in_suite[:-1]
lc = getLocalConfig(ts, test_dir_in_suite, litConfig, localConfigCache)
test = Test.Test(ts, path_in_suite, lc)
# Issue a error if the specified test would not be run if
# the user had specified the containing directory instead of
# of naming the test directly. This helps to avoid writing
# tests which are not executed. The check adds some performance
# overhead which might be important if a large number of tests
# are being run directly.
# This check can be disabled by using --no-indirectly-run-check or
# setting the standalone_tests variable in the suite's configuration.
if (
indirectlyRunCheck
and lc.test_format is not None
and not lc.standalone_tests
):
found = False
for res in lc.test_format.getTestsInDirectory(ts, test_dir_in_suite,
litConfig, lc):
if test.getFullName() == res.getFullName():
found = True
break
if not found:
litConfig.error(
'%r would not be run indirectly: change name or LIT config'
'(e.g. suffixes or standalone_tests variables)'
% test.getFullName())
yield test
return
# Otherwise we have a directory to search for tests, start by getting the
# local configuration.
lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache)
# Directory contains tests to be run standalone. Do not try to discover.
if lc.standalone_tests:
if lc.suffixes or lc.excludes:
litConfig.warning(
'standalone_tests set in LIT config but suffixes or excludes'
' are also set'
)
return
# Search for tests.
if lc.test_format is not None:
for res in lc.test_format.getTestsInDirectory(ts, path_in_suite,
litConfig, lc):
yield res
# Search subdirectories.
for filename in os.listdir(source_path):
# FIXME: This doesn't belong here?
if filename in ('Output', '.svn', '.git') or filename in lc.excludes:
continue
# Ignore non-directories.
file_sourcepath = os.path.join(source_path, filename)
if not os.path.isdir(file_sourcepath):
continue
# Check for nested test suites, first in the execpath in case there is a
# site configuration and then in the source path.
subpath = path_in_suite + (filename,)
file_execpath = ts.getExecPath(subpath)
if dirContainsTestSuite(file_execpath, litConfig):
sub_ts, subpath_in_suite = getTestSuite(file_execpath, litConfig,
testSuiteCache)
elif dirContainsTestSuite(file_sourcepath, litConfig):
sub_ts, subpath_in_suite = getTestSuite(file_sourcepath, litConfig,
testSuiteCache)
else:
sub_ts = None
# If the this directory recursively maps back to the current test suite,
# disregard it (this can happen if the exec root is located inside the
# current test suite, for example).
if sub_ts is ts:
continue
# Otherwise, load from the nested test suite, if present.
if sub_ts is not None:
subiter = getTestsInSuite(sub_ts, subpath_in_suite, litConfig,
testSuiteCache, localConfigCache,
indirectlyRunCheck)
else:
subiter = getTestsInSuite(ts, subpath, litConfig, testSuiteCache,
localConfigCache, indirectlyRunCheck)
N = 0
for res in subiter:
N += 1
yield res
if sub_ts and not N:
litConfig.warning('test suite %r contained no tests' % sub_ts.name)
def find_tests_for_inputs(lit_config, inputs, indirectlyRunCheck):
"""
find_tests_for_inputs(lit_config, inputs) -> [Test]
Given a configuration object and a list of input specifiers, find all the
tests to execute.
"""
# Expand '@...' form in inputs.
actual_inputs = []
for input in inputs:
if input.startswith('@'):
f = open(input[1:])
try:
for ln in f:
ln = ln.strip()
if ln:
actual_inputs.append(ln)
finally:
f.close()
else:
actual_inputs.append(input)
# Load the tests from the inputs.
tests = []
test_suite_cache = {}
local_config_cache = {}
for input in actual_inputs:
prev = len(tests)
tests.extend(getTests(input, lit_config, test_suite_cache,
local_config_cache, indirectlyRunCheck)[1])
if prev == len(tests):
lit_config.warning('input %r contained no tests' % input)
# This data is no longer needed but keeping it around causes awful
# performance problems while the test suites run.
for k, suite in test_suite_cache.items():
if suite[0]:
suite[0].test_times = None
# If there were any errors during test discovery, exit now.
if lit_config.numErrors:
sys.stderr.write('%d errors, exiting.\n' % lit_config.numErrors)
sys.exit(2)
return tests