| #!/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() |