blob: 66d7d5cb62598b66931d62c40ced742df529fe24 [file] [log] [blame]
#!/usr/bin/env python
# utils/run-test - test runner for Swift -*- python -*-
#
# This source file is part of the Swift.org open source project
#
# Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
from __future__ import print_function
import multiprocessing
import os
import shutil
import sys
from build_swift.build_swift import argparse
from build_swift.build_swift.constants import SWIFT_SOURCE_ROOT
from swift_build_support.swift_build_support import shell
from swift_build_support.swift_build_support.targets import StdlibDeploymentTarget
TEST_MODES = [
'optimize_none',
'optimize',
'optimize_unchecked',
'only_executable',
'only_non_executable',
]
TEST_SUBSETS = [
'primary',
'validation',
'all',
'only_validation',
'only_long',
'only_stress',
]
SWIFT_SOURCE_DIR = os.path.join(SWIFT_SOURCE_ROOT, 'swift')
TEST_SOURCE_DIR = os.path.join(SWIFT_SOURCE_DIR, 'test')
VALIDATION_TEST_SOURCE_DIR = os.path.join(SWIFT_SOURCE_DIR, 'validation-test')
def _get_default_llvm_source_dir():
legacy_llvm_dir_path = os.path.join(SWIFT_SOURCE_ROOT, 'llvm')
if os.path.isdir(legacy_llvm_dir_path):
return legacy_llvm_dir_path
return os.path.join(SWIFT_SOURCE_ROOT, 'llvm-project', 'llvm')
# Default path for "lit.py" executable.
LIT_BIN_DEFAULT = os.path.join(os.environ.get("LLVM_SOURCE_DIR",
_get_default_llvm_source_dir()),
'utils', 'lit', 'lit.py')
host_target = StdlibDeploymentTarget.host_target().name
def error_exit(msg):
print("%s: %s" % (os.path.basename(sys.argv[0]), msg), file=sys.stderr)
sys.exit(1)
# Return true if the path looks like swift build directory.
def is_swift_build_dir(path, unified_build_dir):
if not unified_build_dir:
tests_path = [path, "test-%s" % host_target]
else:
tests_path = [path, "tools", "swift", "test-%s" % host_target]
return (os.path.exists(os.path.join(path, "CMakeCache.txt")) and
os.path.isdir(os.path.join(*tests_path)))
# Return true if the swift build directory is configured with `Xcode`
# generator.
def is_build_dir_xcode(path):
return os.path.exists(os.path.join(path, 'Swift.xcodeproj'))
# Return true if 'path' is sub path of 'd'
def is_subpath(path, d):
path, d = os.path.abspath(path), os.path.abspath(d)
if os.path.isdir(path):
path = os.path.join(path, '')
d = os.path.join(d, '')
return path.startswith(d)
# Convert test path in source directory to corresponding path in build
# directory. If the path is not sub path of test directories in source,
# return the path as is.
def normalize_test_path(path, build_dir, variant, unified_build_dir):
if not unified_build_dir:
tests_path = [build_dir]
else:
tests_path = [build_dir, "tools", "swift"]
for d, prefix in [(TEST_SOURCE_DIR, 'test-%s'),
(VALIDATION_TEST_SOURCE_DIR, 'validation-test-%s')]:
if is_subpath(path, d):
return os.path.normpath(os.path.join(*(
tests_path +
[prefix % variant,
os.path.relpath(path, d)])))
return path
def main():
parser = argparse.ArgumentParser()
parser.add_argument("paths", type=os.path.realpath,
nargs="*", metavar="PATH",
help="paths to test. Accept multiple. "
"If --build-dir is not specified, these paths "
"must be test paths in the Swift build "
"directory. (default: primary test suite if "
"--build-dir is specified, none otherwise)")
parser.add_argument("-v", "--verbose", action="store_true",
help="run test with verbose output")
parser.add_argument("--build-dir", type=os.path.realpath, metavar="PATH",
help="Swift build directory")
parser.add_argument("--build",
choices=["true", "verbose", "skip"], default='true',
help="build test dependencies before running tests "
"(default: true)")
parser.add_argument("--build-jobs",
type=int,
help="The number of parallel build jobs to use")
parser.add_argument("--target",
type=argparse.types.ShellSplitType(),
action=argparse.actions.AppendAction,
dest="targets",
help="stdlib deployment targets to test. Accept "
"multiple (default: " + host_target + ")")
parser.add_argument("--filter", type=str, metavar="REGEX",
help="only run tests with paths matching the given "
"regular expression")
parser.add_argument("--mode",
choices=TEST_MODES, default='optimize_none',
help="test mode (default: optimize_none)")
parser.add_argument("--subset",
choices=TEST_SUBSETS, default='primary',
help="test subset (default: primary)")
parser.add_argument("--param",
type=argparse.types.ShellSplitType(),
action=argparse.actions.AppendAction,
default=[],
help="key=value parameters they are directly passed "
"to lit command in addition to `mode` and "
"`subset`. Accept multiple.")
parser.add_argument("--result-dir", type=os.path.realpath, metavar="PATH",
help="directory to store test results (default: none)")
parser.add_argument("--lit", default=LIT_BIN_DEFAULT, metavar="PATH",
help="lit.py executable path "
"(default: ${LLVM_SOURCE_DIR}/utils/lit/lit.py)")
parser.add_argument("--unified", action="store_true",
help="The build directory is an unified LLVM build, "
"not a standalone Swift build")
args = parser.parse_args()
targets = args.targets
if not targets:
targets = [host_target]
paths = []
build_dir = args.build_dir
if build_dir is not None:
# Fixup build directory.
# build_dir can be:
# build-root/ # assuming we are to test host deployment target.
# build-root/swift-{tool-deployment_target}/
for d in [
build_dir,
os.path.join(build_dir, 'swift-%s' % host_target)]:
if is_swift_build_dir(d, args.unified):
build_dir = d
break
else:
error_exit("'%s' is not a swift build directory" % args.build_dir)
# If no path given, run primary test suite.
if not args.paths:
args.paths = [TEST_SOURCE_DIR]
# $ run-test --build-dir=<swift-build-dir> <test-dir-in-source> ... \
# --target macosx-x86_64 --target iphonesimulator-i386
for target in targets:
paths += [normalize_test_path(p, build_dir, target, args.unified)
for p in args.paths]
else:
# Otherwise, we assume all given paths are valid test paths in the
# build_dir.
paths = args.paths
if not paths:
parser.print_usage()
error_exit("error: too few arguments")
if args.build != 'skip':
# Building dependencies requires `build_dir` set.
# Traverse the first test path to find the `build_dir`
d = os.path.dirname(paths[0])
while d not in ['', os.sep]:
if is_swift_build_dir(d, args.unified):
build_dir = d
break
d = os.path.dirname(d)
else:
error_exit("Can't infer swift build directory")
# Ensure we have up to date test dependency
if args.build != 'skip' and is_build_dir_xcode(build_dir):
# We don't support Xcode Generator build yet.
print("warning: Building Xcode project is not supported yet. "
"Skipping...")
sys.stdout.flush()
elif args.build != 'skip':
dependency_targets = ["all", "SwiftUnitTests"]
upload_stdlib_targets = []
need_validation = any('/validation-test-' in path for path in paths)
for target in targets:
if args.mode != 'only_non_executable':
upload_stdlib_targets += ["upload-stdlib-%s" % target]
if need_validation:
dependency_targets += ["swift-stdlib-%s" % target]
else:
dependency_targets += ["swift-test-stdlib-%s" % target]
cmake_build = ['cmake', '--build', build_dir, '--']
if args.build == 'verbose':
cmake_build += ['-v']
if args.build_jobs is not None:
cmake_build += ['-j%d' % args.build_jobs]
else:
cmake_build += ['-j%d' % multiprocessing.cpu_count()]
print("--- Building test dependencies %s ---" %
', '.join(dependency_targets))
sys.stdout.flush()
shell.call(cmake_build + dependency_targets)
shell.call(cmake_build + upload_stdlib_targets)
print("--- Build finished ---")
sys.stdout.flush()
if args.result_dir is not None:
# Clear result directory
if os.path.exists(args.result_dir):
shutil.rmtree(args.result_dir)
os.makedirs(args.result_dir)
if args.verbose:
test_args = ["-a"]
else:
test_args = ["-sv"]
# Test parameters.
test_args += ['--param', 'swift_test_mode=%s' % args.mode,
'--param', 'swift_test_subset=%s' % args.subset]
for param in args.param:
test_args += ['--param', param]
if args.result_dir:
test_args += ['--param', 'swift_test_results_dir=%s' % args.result_dir,
'--xunit-xml-output=%s' % os.path.join(args.result_dir,
'lit-tests.xml')]
if args.filter:
test_args += ['--filter', args.filter]
test_cmd = [sys.executable, args.lit] + test_args + paths
# Do execute test
shell.call(test_cmd)
if __name__ == "__main__":
main()