-
Notifications
You must be signed in to change notification settings - Fork 3k
/
test.py
348 lines (289 loc) · 14.2 KB
/
test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#! /usr/bin/env python2
"""
mbed SDK
Copyright (c) 2011-2013 ARM Limited
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
TEST BUILD
"""
from __future__ import print_function, division, absolute_import
import sys
import os
import fnmatch
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, ROOT)
from tools.config import ConfigException, Config
from tools.test_configs import get_default_config
from tools.test_api import find_tests, get_test_config, print_tests, build_tests, test_spec_from_test_builds
from tools.options import get_default_options_parser, extract_profile, extract_mcus, argparse_profile_filestring_type
from tools.build_api import build_library
from tools.build_api import print_build_memory_usage
from tools.build_api import merge_build_data
from tools.build_api import find_valid_toolchain
from tools.notifier.term import TerminalNotifier
from tools.utils import ToolException, NotSupportedException, args_error, write_json_to_file
from tools.utils import NoValidToolchainException
from tools.test_exporters import ReportExporter, ResultExporterType
from tools.utils import argparse_filestring_type, argparse_lowercase_type, argparse_many
from tools.utils import argparse_dir_not_parent
from tools.utils import print_end_warnings
from tools.settings import ROOT
from tools.targets import Target
def main():
error = False
try:
# Parse Options
parser = get_default_options_parser(add_app_config=True)
parser.add_argument("-D",
action="append",
dest="macros",
help="Add a macro definition")
parser.add_argument("-j", "--jobs",
type=int,
dest="jobs",
default=0,
help="Number of concurrent jobs. Default: 0/auto (based on host machine's number of CPUs)")
parser.add_argument("--source", dest="source_dir",
type=argparse_filestring_type,
default=None, help="The source (input) directory (for sources other than tests). Defaults to current directory.", action="append")
parser.add_argument("--build", dest="build_dir", type=argparse_dir_not_parent(ROOT),
default=None, help="The build (output) directory")
parser.add_argument("-l", "--list", action="store_true", dest="list",
default=False, help="List (recursively) available tests in order and exit")
parser.add_argument("-p", "--paths", dest="paths",
type=argparse_many(argparse_filestring_type),
default=None, help="Limit the tests to those within the specified comma separated list of paths")
format_choices = ["list", "json"]
format_default_choice = "list"
format_help = "Change the format in which tests are listed. Choices include: %s. Default: %s" % (", ".join(format_choices), format_default_choice)
parser.add_argument("-f", "--format", dest="format",
type=argparse_lowercase_type(format_choices, "format"),
default=format_default_choice, help=format_help)
parser.add_argument("--continue-on-build-fail", action="store_true", dest="continue_on_build_fail",
default=None, help="Continue trying to build all tests if a build failure occurs")
#TODO validate the names instead of just passing through str
parser.add_argument("-n", "--names", dest="names", type=argparse_many(str),
default=None, help="Limit the tests to a comma separated list of names")
parser.add_argument("--test-config", dest="test_config", type=str,
default=None, help="Test config for a module")
parser.add_argument("--test-spec", dest="test_spec",
default=None, help="Destination path for a test spec file that can be used by the Greentea automated test tool")
parser.add_argument("--build-report-junit", dest="build_report_junit",
default=None, help="Destination path for a build report in the JUnit xml format")
parser.add_argument("--build-data",
dest="build_data",
default=None,
help="Dump build_data to this file")
parser.add_argument("-v", "--verbose",
action="store_true",
dest="verbose",
default=False,
help="Verbose diagnostic output")
parser.add_argument("--silent",
action="store_true",
dest="silent",
default=False,
help="Silent diagnostic output (no copy, compile notification)")
parser.add_argument("--stats-depth",
type=int,
dest="stats_depth",
default=2,
help="Depth level for static memory report")
parser.add_argument("--ignore", dest="ignore", type=argparse_many(str),
default=None, help="Comma separated list of patterns to add to mbedignore (eg. ./main.cpp)")
parser.add_argument("--coverage-filters", dest="coverage_patterns", nargs='+',
default=[], help="match patterns to build with debug compile and coverage linker options")
parser.add_argument("--icetea",
action="store_true",
dest="icetea",
default=False,
help="Only icetea tests")
parser.add_argument("--greentea",
action="store_true",
dest="greentea",
default=False,
help="Only greentea tests")
options = parser.parse_args()
# Filter tests by path if specified
if options.paths:
all_paths = options.paths
else:
all_paths = ["."]
all_tests = {}
tests = {}
end_warnings = []
# As default both test tools are enabled
if not (options.greentea or options.icetea):
options.greentea = True
options.icetea = True
# Target
if options.mcu is None:
args_error(parser, "argument -m/--mcu is required")
mcu = extract_mcus(parser, options)[0]
target = Target.get_target(mcu)
# Toolchain
if options.tool is None:
args_error(parser, "argument -t/--tool is required")
toolchain = options.tool[0]
try:
toolchain_name, internal_tc_name, end_warnings = find_valid_toolchain(
target, toolchain
)
except NoValidToolchainException as e:
print_end_warnings(e.end_warnings)
args_error(parser, str(e))
# Assign config file. Precedence: test_config>app_config
# TODO: merge configs if both given
if options.test_config:
config = get_test_config(options.test_config, mcu)
if not config:
args_error(parser, "argument --test-config contains invalid path or identifier")
elif options.app_config:
config = options.app_config
else:
config = Config.find_app_config(options.source_dir)
if not config:
config = get_default_config(options.source_dir or ['.'], mcu)
# Find all tests in the relevant paths
for path in all_paths:
all_tests.update(find_tests(
base_dir=path,
target_name=mcu,
toolchain_name=toolchain_name,
icetea=options.icetea,
greentea=options.greentea,
app_config=config))
# Filter tests by name if specified
if options.names:
all_names = options.names
all_names = [x.lower() for x in all_names]
for name in all_names:
if any(fnmatch.fnmatch(testname, name) for testname in all_tests):
for testname, test in all_tests.items():
if fnmatch.fnmatch(testname, name):
tests[testname] = test
else:
print("[Warning] Test with name '%s' was not found in the "
"available tests" % (name))
else:
tests = all_tests
if options.list:
# Print available tests in order and exit
print_tests(tests, options.format)
sys.exit(0)
else:
# Build all tests
if not options.build_dir:
args_error(parser, "argument --build is required")
base_source_paths = options.source_dir
# Default base source path is the current directory
if not base_source_paths:
base_source_paths = ['.']
# Coverage requires debug profile
if options.coverage_patterns:
if toolchain != u'GCC_ARM':
raise ToolException('Coverage supports only GCC_ARM toolchain')
options.profile.append(argparse_profile_filestring_type('debug'))
print("[Warning] Test building with coverage filters %s" % options.coverage_patterns)
build_report = {}
build_properties = {}
library_build_success = False
profile = extract_profile(parser, options, internal_tc_name)
try:
resource_filter = None
# Build sources
notify = TerminalNotifier(options.verbose, options.silent)
build_library(base_source_paths, options.build_dir, mcu,
toolchain_name, jobs=options.jobs,
clean=options.clean, report=build_report,
properties=build_properties, name="mbed-build",
macros=options.macros,
notify=notify, archive=False,
app_config=config,
build_profile=profile,
ignore=options.ignore,
coverage_patterns=options.coverage_patterns,
resource_filter=resource_filter
)
library_build_success = True
except ToolException as e:
# ToolException output is handled by the build log
print("[ERROR] " + str(e))
pass
except NotSupportedException as e:
# NotSupportedException is handled by the build log
print("[ERROR] " + str(e))
pass
except Exception as e:
if options.verbose:
import traceback
traceback.print_exc()
# Some other exception occurred, print the error message
print(e)
if not library_build_success:
print("Failed to build library")
else:
resource_filter = None
# Build all the tests
notify = TerminalNotifier(options.verbose, options.silent)
test_build_success, test_build = build_tests(
tests,
[os.path.relpath(options.build_dir)],
options.build_dir,
mcu,
toolchain_name,
clean=options.clean,
report=build_report,
properties=build_properties,
macros=options.macros,
notify=notify,
jobs=options.jobs,
continue_on_build_fail=options.continue_on_build_fail,
app_config=config,
build_profile=profile,
stats_depth=options.stats_depth,
ignore=options.ignore,
coverage_patterns=options.coverage_patterns,
resource_filter=resource_filter)
# If a path to a test spec is provided, write it to a file
if options.test_spec:
write_json_to_file(test_spec_from_test_builds(test_build), options.test_spec)
# If a path to a JUnit build report spec is provided, write it to a file
if options.build_report_junit:
report_exporter = ReportExporter(ResultExporterType.JUNIT, package="build")
report_exporter.report_to_file(build_report, options.build_report_junit, test_suite_properties=build_properties)
# Print memory map summary on screen
if build_report:
print()
print(print_build_memory_usage(build_report))
print_report_exporter = ReportExporter(ResultExporterType.PRINT, package="build")
status = print_report_exporter.report(build_report)
if options.build_data:
merge_build_data(options.build_data, build_report, "test")
if status:
sys.exit(0)
else:
sys.exit(1)
except KeyboardInterrupt as e:
print("\n[CTRL+c] exit")
except ConfigException as e:
# Catching ConfigException here to prevent a traceback
print("[ERROR] %s" % str(e))
error = True
except Exception as e:
import traceback
traceback.print_exc(file=sys.stdout)
print("[ERROR] %s" % str(e))
error = True
print_end_warnings(end_warnings)
if error:
sys.exit(1)
if __name__ == '__main__':
main()