Skip to content

Commit 2b9a8dc

Browse files
committed
[test] Added utils/run-test, a small utility to run tests for Swift
usage: utils/run-test --build-dir <build_dir> <path_to_test> [<path_to_test> ...]
1 parent 6eb6365 commit 2b9a8dc

File tree

2 files changed

+271
-1
lines changed

2 files changed

+271
-1
lines changed

.pep8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[flake8]
2-
filename = *.py,80+-check,Benchmark_Driver,Benchmark_DTrace.in,Benchmark_GuardMalloc.in,Benchmark_RuntimeLeaksRunner.in,build-script,check-incremental,gyb,line-directive,mock-distcc,ns-html2rst,recursive-lipo,rth,submit-benchmark-results,update-checkout,viewcfg,backtrace-check
2+
filename = *.py,80+-check,Benchmark_Driver,Benchmark_DTrace.in,Benchmark_GuardMalloc.in,Benchmark_RuntimeLeaksRunner.in,build-script,check-incremental,gyb,line-directive,mock-distcc,ns-html2rst,recursive-lipo,rth,run-test,submit-benchmark-results,update-checkout,viewcfg,backtrace-check

utils/run-test

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#!/usr/bin/env python
2+
# utils/run-test - test runner for Swift -*- python -*-
3+
#
4+
# This source file is part of the Swift.org open source project
5+
#
6+
# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
7+
# Licensed under Apache License v2.0 with Runtime Library Exception
8+
#
9+
# See http://swift.org/LICENSE.txt for license information
10+
# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
11+
12+
from __future__ import print_function
13+
14+
import argparse
15+
import multiprocessing
16+
import os
17+
import shutil
18+
import sys
19+
20+
sys.path.append(os.path.dirname(__file__))
21+
sys.path.append(os.path.join(os.path.dirname(__file__), 'swift_build_support'))
22+
23+
from SwiftBuildSupport import SWIFT_SOURCE_ROOT # noqa (E402)
24+
from swift_build_support import arguments # noqa (E402)
25+
from swift_build_support import shell # noqa (E402)
26+
from swift_build_support.targets import StdlibDeploymentTarget # noqa (E402)
27+
28+
29+
TEST_MODES = [
30+
'optimize_none',
31+
'optimize',
32+
'optimize_unchecked',
33+
'only_executable',
34+
'only_non_executable',
35+
]
36+
TEST_SUBSETS = [
37+
'primary',
38+
'validation',
39+
'all',
40+
'only_validation',
41+
'only_long',
42+
]
43+
44+
SWIFT_SOURCE_DIR = os.path.join(SWIFT_SOURCE_ROOT, 'swift')
45+
TEST_SOURCE_DIR = os.path.join(SWIFT_SOURCE_DIR, 'test')
46+
VALIDATION_TEST_SOURCE_DIR = os.path.join(SWIFT_SOURCE_DIR, 'validation-test')
47+
48+
LIT_BIN_DEFAULT = os.path.join(SWIFT_SOURCE_ROOT, 'llvm',
49+
'utils', 'lit', 'lit.py')
50+
PROFDATA_MERGE = os.path.join(SWIFT_SOURCE_DIR,
51+
'utils', 'profdata_merge', 'main.py')
52+
host_target = StdlibDeploymentTarget.host_target().name
53+
54+
55+
def error_exit(msg):
56+
print("%s: %s" % (os.path.basename(sys.argv[0]), msg), file=sys.stderr)
57+
sys.exit(1)
58+
59+
60+
# Return true if the path looks like swift build directory.
61+
def is_swift_build_dir(path):
62+
return (os.path.exists(os.path.join(path, "CMakeCache.txt")) and
63+
os.path.isdir(os.path.join(path, "test-%s" % host_target)))
64+
65+
66+
# Return true if the swift build directory is configured with `Xcode`
67+
# generator.
68+
def is_build_dir_xcode(path):
69+
return os.path.exists(os.path.join(path, 'Swift.xcodeproj'))
70+
71+
72+
# Return true if 'path' is sub path of 'd'
73+
def is_subpath(path, d):
74+
path, d = os.path.abspath(path), os.path.abspath(d)
75+
if os.path.isdir(path):
76+
path = os.path.join(path, '')
77+
d = os.path.join(d, '')
78+
return path.startswith(d)
79+
80+
81+
# Convert test path in source directory to correspoding path in build
82+
# directory. If the path is not sub path of test directories in source,
83+
# return the path as is.
84+
def normalize_test_path(path, build_dir, variant):
85+
for d, prefix in [(TEST_SOURCE_DIR, 'test-%s'),
86+
(VALIDATION_TEST_SOURCE_DIR, 'validation-test-%s')]:
87+
if is_subpath(path, d):
88+
return os.path.normpath(os.path.join(
89+
build_dir,
90+
prefix % variant,
91+
os.path.relpath(path, d)))
92+
return path
93+
94+
95+
def main():
96+
parser = argparse.ArgumentParser()
97+
parser.add_argument("paths", type=os.path.realpath,
98+
nargs="*", metavar="PATH",
99+
help="paths to test. Accept multiple. "
100+
"If --build-dir is not specified, these paths "
101+
"must be test paths in the Swift build "
102+
"directory. (default: primary test suite if "
103+
"--build-dir is specified, none otherwise)")
104+
parser.add_argument("-v", "--verbose", action="store_true",
105+
help="run test with verbose output")
106+
parser.add_argument("--build-dir", type=os.path.realpath, metavar="PATH",
107+
help="Swift build directory")
108+
parser.add_argument("--build",
109+
choices=["true", "verbose", "skip"], default='true',
110+
help="build test dependencies before running tests "
111+
"(default: true)")
112+
parser.add_argument("--target",
113+
type=arguments.type.shell_split,
114+
action=arguments.action.concat,
115+
dest="targets",
116+
help="stdlib deployment targets to test. Accept "
117+
"multiple (default: " + host_target + ")")
118+
parser.add_argument("--mode",
119+
choices=TEST_MODES, default='optimize_none',
120+
help="test mode (default: optimize_none)")
121+
parser.add_argument("--subset",
122+
choices=TEST_SUBSETS, default='primary',
123+
help="test subset (default: primary)")
124+
parser.add_argument("--param",
125+
type=arguments.type.shell_split,
126+
action=arguments.action.concat,
127+
default=[],
128+
help="key=value paramters they are directly passed to "
129+
"lit command in addition to `mode` and `subset`. "
130+
"Accept multiple.")
131+
parser.add_argument("--result-dir", type=os.path.realpath, metavar="PATH",
132+
help="directory to store test results (default: none)")
133+
parser.add_argument("--merge-coverage",
134+
action=arguments.action.optional_bool,
135+
help="set to merge coverage profile")
136+
parser.add_argument("--lit", default=LIT_BIN_DEFAULT, metavar="PATH",
137+
help="lit.py executable path "
138+
"(default: ${LLVM_SOURCE_DIR}/utils/lit/lit.py)")
139+
140+
args = parser.parse_args()
141+
142+
if args.merge_coverage:
143+
if args.result_dir is None:
144+
error_exit('Coverage needs --result-dir')
145+
146+
targets = args.targets
147+
if targets is None:
148+
targets = [host_target]
149+
150+
paths = []
151+
152+
build_dir = args.build_dir
153+
if build_dir is not None:
154+
# Fixup build direcotry.
155+
# build_dir can be:
156+
# build-root/ # assuming we are to test host deployment target.
157+
# build-root/swift-{tool-deployment_target}/
158+
for d in [
159+
build_dir,
160+
os.path.join(build_dir, 'swift-%s' % host_target)]:
161+
if is_swift_build_dir(d):
162+
build_dir = d
163+
break
164+
else:
165+
error_exit("'%s' is not a swift build directory" % args.build_dir)
166+
167+
# If no path given, run primary test suite.
168+
if not args.paths:
169+
args.paths = [TEST_SOURCE_DIR]
170+
171+
# $ run-test --build-dir=<swift-build-dir> <test-dir-in-source> ... \
172+
# --target macosx-x86_64 --target iphonesimulator-i386
173+
for target in targets:
174+
paths += map(
175+
lambda p: normalize_test_path(p, build_dir, target),
176+
args.paths)
177+
178+
else:
179+
# Otherwise, we assume all given paths are valid test paths in the
180+
# build_dir.
181+
paths = args.paths
182+
if not paths:
183+
error_exit("error: too few arguments")
184+
185+
if args.build != 'skip':
186+
# Building dependencies requires `build_dir` set.
187+
# Traverse the first test path to find the `build_dir`
188+
d = os.path.dirname(paths[0])
189+
while d not in ['', os.sep]:
190+
if is_swift_build_dir(d):
191+
build_dir = d
192+
break
193+
d = os.path.dirname(d)
194+
else:
195+
error_exit("Can't infer swift build direcory")
196+
197+
# Ensure we have up to date test dependency
198+
if args.build != 'skip' and is_build_dir_xcode(build_dir):
199+
# We don't support Xcode Generator build yet.
200+
print("warning: Building Xcode project is not supported yet. "
201+
"Skipping...")
202+
sys.stdout.flush()
203+
204+
elif args.build != 'skip':
205+
dependency_targets = ["all", "SwiftUnitTests"]
206+
upload_stdlib_targets = []
207+
need_validation = any('/validation-test-' in path for path in paths)
208+
for target in targets:
209+
upload_stdlib_targets += ["upload-stdlib-%s" % target]
210+
if need_validation:
211+
dependency_targets += ["swift-stdlib-%s" % target]
212+
else:
213+
dependency_targets += ["swift-test-stdlib-%s" % target]
214+
215+
cmake_build = ['cmake', '--build', build_dir, '--']
216+
if args.build == 'verbose':
217+
cmake_build += ['-v']
218+
cmake_build += ['-j%d' % multiprocessing.cpu_count()]
219+
220+
print("--- Building test dependencies %s ---" %
221+
', '.join(dependency_targets))
222+
sys.stdout.flush()
223+
shell.call(cmake_build + dependency_targets)
224+
shell.call(cmake_build + upload_stdlib_targets)
225+
print("--- Build finished ---")
226+
sys.stdout.flush()
227+
228+
if args.result_dir is not None:
229+
# Clear result directory
230+
if os.path.exists(args.result_dir):
231+
shutil.rmtree(args.result_dir)
232+
os.makedirs(args.result_dir)
233+
234+
if args.verbose:
235+
test_args = ["-a"]
236+
else:
237+
test_args = ["-sv"]
238+
239+
# Test parameters.
240+
test_args += ['--param', 'swift_test_mode=%s' % args.mode,
241+
'--param', 'swift_test_subset=%s' % args.subset]
242+
243+
for param in args.param:
244+
test_args += ['--param', param]
245+
246+
if args.result_dir:
247+
test_args += ['--xunit-xml-output=%s' % os.path.join(args.result_dir,
248+
'lit-tests.xml')]
249+
250+
test_cmd = [sys.executable, args.lit] + test_args + paths
251+
252+
# Start merge worker
253+
if args.merge_coverage:
254+
merge_log = os.path.join(args.result_dir, 'profdata_merge.log')
255+
profdata_merge_start = [sys.executable, PROFDATA_MERGE,
256+
'-l', merge_log,
257+
'start',
258+
'-o', args.result_dir]
259+
shell.call(profdata_merge_start, echo=False)
260+
261+
# Do execute test
262+
shell.call(test_cmd)
263+
264+
# Stop merge worker
265+
if args.merge_coverage:
266+
profdata_merge_start = [sys.executable, PROFDATA_MERGE, 'stop']
267+
shell.call(shell.call, echo=False)
268+
269+
if __name__ == "__main__":
270+
main()

0 commit comments

Comments
 (0)