blob: d460541a8bc309a394d0c26b0aeaf80a4c19952b [file] [log] [blame]
Andrew Boie08ce5a52016-02-22 13:28:10 -08001#!/usr/bin/env python3
Andrew Boie6acbe632015-07-17 12:03:52 -07002"""Zephyr Sanity Tests
3
4This script scans for the set of unit test applications in the git
5repository and attempts to execute them. By default, it tries to
6build each test case on one platform per architecture, using a precedence
7list defined in an archtecture configuration file, and if possible
8run the tests in the QEMU emulator.
9
10Test cases are detected by the presence of a 'testcase.ini' file in
11the application's project directory. This file may contain one or
12more blocks, each identifying a test scenario. The title of the block
13is a name for the test case, which only needs to be unique for the
14test cases specified in that testcase.ini file. The full canonical
15name for each test case is <path to test case under samples/>/<block>.
16
17Each testcase.ini block can define the following key/value pairs:
18
19 tags = <list of tags> (required)
20 A set of string tags for the testcase. Usually pertains to
21 functional domains but can be anything. Command line invocations
22 of this script can filter the set of tests to run based on tag.
23
Andrew Boie6bb087c2016-02-10 13:39:00 -080024 skip = <True|False> (default False)
Anas Nashif2bd99bc2015-10-12 13:10:57 -040025 skip testcase unconditionally. This can be used for broken tests.
26
Andrew Boie6bb087c2016-02-10 13:39:00 -080027 slow = <True|False> (default False)
28 Don't run this test case unless --enable-slow was passed in on the
29 command line. Intended for time-consuming test cases that are only
30 run under certain circumstances, like daily builds. These test cases
31 are still compiled.
32
Andrew Boie6acbe632015-07-17 12:03:52 -070033 extra_args = <list of extra arguments>
34 Extra arguments to pass to Make when building or running the
35 test case.
36
Andrew Boie6bb087c2016-02-10 13:39:00 -080037 build_only = <True|False> (default False)
Andrew Boie6acbe632015-07-17 12:03:52 -070038 If true, don't try to run the test under QEMU even if the
39 selected platform supports it.
40
41 timeout = <number of seconds>
42 Length of time to run test in QEMU before automatically killing it.
43 Default to 60 seconds.
44
45 arch_whitelist = <list of arches, such as x86, arm, arc>
46 Set of architectures that this test case should only be run for.
47
Anas Nashif30d13872015-10-05 10:02:45 -040048 arch_exclude = <list of arches, such as x86, arm, arc>
49 Set of architectures that this test case should not run on.
50
Andrew Boie6acbe632015-07-17 12:03:52 -070051 platform_whitelist = <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040052 Set of platforms that this test case should only be run for.
53
54 platform_exclude = <list of platforms>
55 Set of platforms that this test case should not run on.
Andrew Boie6acbe632015-07-17 12:03:52 -070056
Andrew Boie52fef672016-11-29 12:21:59 -080057 extra_sections = <list of extra binary sections>
58 When computing sizes, sanitycheck will report errors if it finds
59 extra, unexpected sections in the Zephyr binary unless they are named
60 here. They will not be included in the size calculation.
61
Andrew Boie3ea78922016-03-24 14:46:00 -070062 filter = <expression>
63 Filter whether the testcase should be run by evaluating an expression
64 against an environment containing the following values:
65
66 { ARCH : <architecture>,
67 PLATFORM : <platform>,
Javier B Perez79414542016-08-08 12:24:59 -050068 <all CONFIG_* key/value pairs in the test's generated defconfig>,
69 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070070 }
71
72 The grammar for the expression language is as follows:
73
74 expression ::= expression "and" expression
75 | expression "or" expression
76 | "not" expression
77 | "(" expression ")"
78 | symbol "==" constant
79 | symbol "!=" constant
80 | symbol "<" number
81 | symbol ">" number
82 | symbol ">=" number
83 | symbol "<=" number
84 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -070085 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -070086 | symbol
87
88 list ::= "[" list_contents "]"
89
90 list_contents ::= constant
91 | list_contents "," constant
92
93 constant ::= number
94 | string
95
96
97 For the case where expression ::= symbol, it evaluates to true
98 if the symbol is defined to a non-empty string.
99
100 Operator precedence, starting from lowest to highest:
101
102 or (left associative)
103 and (left associative)
104 not (right associative)
105 all comparison operators (non-associative)
106
107 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
108 are all syntactic sugar for these expressions. For instance
109
110 arch_exclude = x86 arc
111
112 Is the same as:
113
114 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700115
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700116 The ':' operator compiles the string argument as a regular expression,
117 and then returns a true value only if the symbol's value in the environment
118 matches. For example, if CONFIG_SOC="quark_se" then
119
120 filter = CONFIG_SOC : "quark.*"
121
122 Would match it.
123
Andrew Boie6acbe632015-07-17 12:03:52 -0700124Architectures and platforms are defined in an archtecture configuration
125file which are stored by default in scripts/sanity_chk/arches/. These
126each define an [arch] block with the following key/value pairs:
127
128 name = <arch name>
129 The name of the arch. Example: x86
130
131 platforms = <list of supported platforms in order of precedence>
132 List of supported platforms for this arch. The ordering here
133 is used to select a default platform to build for that arch.
134
135For every platform defined, there must be a corresponding block for it
136in the arch configuration file. This block can be empty if there are
137no special definitions for that arch. Options are:
138
139 qemu_support = <True|False> (default False)
140 Indicates whether binaries for this platform can run under QEMU
141
Andrew Boie6acbe632015-07-17 12:03:52 -0700142The set of test cases that actually run depends on directives in the
143testcase and archtecture .ini file and options passed in on the command
144line. If there is every any confusion, running with -v or --discard-report
145can help show why particular test cases were skipped.
146
147Metrics (such as pass/fail state and binary size) for the last code
148release are stored in scripts/sanity_chk/sanity_last_release.csv.
149To update this, pass the --all --release options.
150
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500151To load arguments from a file, write '+' before the file name, e.g.,
152+file_name. File content must be one or more valid arguments separated by
153line break instead of white spaces.
154
Andrew Boie6acbe632015-07-17 12:03:52 -0700155Most everyday users will run with no arguments.
156"""
157
158import argparse
159import os
160import sys
Andrew Boie08ce5a52016-02-22 13:28:10 -0800161import configparser
Andrew Boie6acbe632015-07-17 12:03:52 -0700162import re
163import tempfile
164import subprocess
165import multiprocessing
166import select
167import shutil
168import signal
169import threading
170import time
171import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700172import glob
Daniel Leung6b170072016-04-07 12:10:25 -0700173import concurrent
174import concurrent.futures
Andrew Boie6acbe632015-07-17 12:03:52 -0700175
Anas Nashifc0ddcb72016-02-01 13:18:14 -0500176os.environ["DISABLE_TRYRUN"] = "1"
Andrew Boie6acbe632015-07-17 12:03:52 -0700177if "ZEPHYR_BASE" not in os.environ:
Anas Nashif427cdd32015-08-06 07:25:42 -0400178 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700179 exit(1)
180ZEPHYR_BASE = os.environ["ZEPHYR_BASE"]
Andrew Boie3ea78922016-03-24 14:46:00 -0700181
182sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
183
184import expr_parser
185
Andrew Boie6acbe632015-07-17 12:03:52 -0700186VERBOSE = 0
187LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
188 "last_sanity.csv")
189RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
190 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700191CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700192
193if os.isatty(sys.stdout.fileno()):
194 TERMINAL = True
195 COLOR_NORMAL = '\033[0m'
196 COLOR_RED = '\033[91m'
197 COLOR_GREEN = '\033[92m'
198 COLOR_YELLOW = '\033[93m'
199else:
200 TERMINAL = False
201 COLOR_NORMAL = ""
202 COLOR_RED = ""
203 COLOR_GREEN = ""
204 COLOR_YELLOW = ""
205
206class SanityCheckException(Exception):
207 pass
208
209class SanityRuntimeError(SanityCheckException):
210 pass
211
212class ConfigurationError(SanityCheckException):
213 def __init__(self, cfile, message):
214 self.cfile = cfile
215 self.message = message
216
217 def __str__(self):
218 return repr(self.cfile + ": " + self.message)
219
220class MakeError(SanityCheckException):
221 pass
222
223class BuildError(MakeError):
224 pass
225
226class ExecutionError(MakeError):
227 pass
228
229# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800230def info(what):
231 sys.stdout.write(what + "\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700232
233def error(what):
234 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
235
Andrew Boie08ce5a52016-02-22 13:28:10 -0800236def debug(what):
237 if VERBOSE >= 1:
238 info(what)
239
Andrew Boie6acbe632015-07-17 12:03:52 -0700240def verbose(what):
241 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800242 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700243
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300244class Handler:
245 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
246 RUN_FAILED = "PROJECT EXECUTION FAILED"
247 def __init__(self, name, outdir, log_fn, timeout, unit=False):
248 """Constructor
249
250 @param name Arbitrary name of the created thread
251 @param outdir Working directory, should be where qemu.pid gets created
252 by kbuild
253 @param log_fn Absolute path to write out QEMU's log data
254 @param timeout Kill the QEMU process if it doesn't finish up within
255 the given number of seconds
256 """
257 self.lock = threading.Lock()
258 self.state = "waiting"
259 self.metrics = {}
260 self.metrics["qemu_time"] = 0
261 self.metrics["ram_size"] = 0
262 self.metrics["rom_size"] = 0
263 self.unit = unit
264
265 def set_state(self, state, metrics):
266 self.lock.acquire()
267 self.state = state
268 self.metrics.update(metrics)
269 self.lock.release()
270
271 def get_state(self):
272 self.lock.acquire()
273 ret = (self.state, self.metrics)
274 self.lock.release()
275 return ret
276
277class UnitHandler(Handler):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300278 def __init__(self, name, sourcedir, outdir, run_log, valgrind_log, timeout):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300279 """Constructor
280
281 @param name Arbitrary name of the created thread
282 @param outdir Working directory containing the test binary
283 @param run_log Absolute path to runtime logs
284 @param valgrind Absolute path to valgrind's log
285 @param timeout Kill the QEMU process if it doesn't finish up within
286 the given number of seconds
287 """
288 super().__init__(name, outdir, run_log, timeout, True)
289
290 self.timeout = timeout
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300291 self.sourcedir = sourcedir
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300292 self.outdir = outdir
293 self.run_log = run_log
294 self.valgrind_log = valgrind_log
295 self.returncode = 0
296 self.set_state("running", {})
297
298 def handle(self):
299 out_state = "failed"
300
301 with open(self.run_log, "wt") as rl, open(self.valgrind_log, "wt") as vl:
302 try:
303 binary = os.path.join(self.outdir, "testbinary")
304 command = [binary]
305 if shutil.which("valgrind"):
306 command = ["valgrind", "--error-exitcode=2",
307 "--leak-check=full"] + command
308 returncode = subprocess.call(command, timeout=self.timeout,
309 stdout=rl, stderr=vl)
310 self.returncode = returncode
311 if returncode != 0:
312 if self.returncode == 1:
313 out_state = "failed"
314 else:
315 out_state = "failed valgrind"
316 else:
317 out_state = "passed"
318 except subprocess.TimeoutExpired:
319 out_state = "timeout"
320 self.returncode = 1
321
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300322 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
323
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300324 self.set_state(out_state, {})
325
326class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700327 """Spawns a thread to monitor QEMU output from pipes
328
329 We pass QEMU_PIPE to 'make qemu' and monitor the pipes for output.
330 We need to do this as once qemu starts, it runs forever until killed.
331 Test cases emit special messages to the console as they run, we check
332 for these to collect whether the test passed or failed.
333 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700334
335 @staticmethod
336 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results):
337 fifo_in = fifo_fn + ".in"
338 fifo_out = fifo_fn + ".out"
339
340 # These in/out nodes are named from QEMU's perspective, not ours
341 if os.path.exists(fifo_in):
342 os.unlink(fifo_in)
343 os.mkfifo(fifo_in)
344 if os.path.exists(fifo_out):
345 os.unlink(fifo_out)
346 os.mkfifo(fifo_out)
347
348 # We don't do anything with out_fp but we need to open it for
349 # writing so that QEMU doesn't block, due to the way pipes work
350 out_fp = open(fifo_in, "wb")
351 # Disable internal buffering, we don't
352 # want read() or poll() to ever block if there is data in there
353 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800354 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700355
356 start_time = time.time()
357 timeout_time = start_time + timeout
358 p = select.poll()
359 p.register(in_fp, select.POLLIN)
360
361 metrics = {}
362 line = ""
363 while True:
364 this_timeout = int((timeout_time - time.time()) * 1000)
365 if this_timeout < 0 or not p.poll(this_timeout):
366 out_state = "timeout"
367 break
368
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500369 try:
370 c = in_fp.read(1).decode("utf-8")
371 except UnicodeDecodeError:
372 # Test is writing something weird, fail
373 out_state = "unexpected byte"
374 break
375
Andrew Boie6acbe632015-07-17 12:03:52 -0700376 if c == "":
377 # EOF, this shouldn't happen unless QEMU crashes
378 out_state = "unexpected eof"
379 break
380 line = line + c
381 if c != "\n":
382 continue
383
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300384 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700385 log_out_fp.write(line)
386 log_out_fp.flush()
387 line = line.strip()
388 verbose("QEMU: %s" % line)
389
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300390 if line == handler.RUN_PASSED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700391 out_state = "passed"
392 break
393
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300394 if line == handler.RUN_FAILED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700395 out_state = "failed"
396 break
397
398 # TODO: Add support for getting numerical performance data
399 # from test cases. Will involve extending test case reporting
400 # APIs. Add whatever gets reported to the metrics dictionary
401 line = ""
402
403 metrics["qemu_time"] = time.time() - start_time
404 verbose("QEMU complete (%s) after %f seconds" %
405 (out_state, metrics["qemu_time"]))
406 handler.set_state(out_state, metrics)
407
408 log_out_fp.close()
409 out_fp.close()
410 in_fp.close()
411
412 pid = int(open(pid_fn).read())
413 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800414 try:
415 os.kill(pid, signal.SIGTERM)
416 except ProcessLookupError:
417 # Oh well, as long as it's dead! User probably sent Ctrl-C
418 pass
419
Andrew Boie6acbe632015-07-17 12:03:52 -0700420 os.unlink(fifo_in)
421 os.unlink(fifo_out)
422
Andrew Boie6acbe632015-07-17 12:03:52 -0700423 def __init__(self, name, outdir, log_fn, timeout):
424 """Constructor
425
426 @param name Arbitrary name of the created thread
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300427 @param outdir Working directory, should be where qemu.pid gets created
Andrew Boie6acbe632015-07-17 12:03:52 -0700428 by kbuild
429 @param log_fn Absolute path to write out QEMU's log data
430 @param timeout Kill the QEMU process if it doesn't finish up within
431 the given number of seconds
432 """
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300433 super().__init__(name, outdir, log_fn, timeout)
Andrew Boie6acbe632015-07-17 12:03:52 -0700434 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700435
436 # We pass this to QEMU which looks for fifos with .in and .out
437 # suffixes.
438 self.fifo_fn = os.path.join(outdir, "qemu-fifo")
439
440 self.pid_fn = os.path.join(outdir, "qemu.pid")
441 if os.path.exists(self.pid_fn):
442 os.unlink(self.pid_fn)
443
444 self.log_fn = log_fn
445 self.thread = threading.Thread(name=name, target=QEMUHandler._thread,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300446 args=(self, timeout, outdir,
447 self.log_fn, self.fifo_fn,
448 self.pid_fn, self.results))
Andrew Boie6acbe632015-07-17 12:03:52 -0700449 self.thread.daemon = True
450 verbose("Spawning QEMU process for %s" % name)
451 self.thread.start()
452
Andrew Boie6acbe632015-07-17 12:03:52 -0700453 def get_fifo(self):
454 return self.fifo_fn
455
Andrew Boie6acbe632015-07-17 12:03:52 -0700456class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700457
458 alloc_sections = ["bss", "noinit"]
459 rw_sections = ["datas", "initlevel", "_k_mem_map_ptr", "_k_pipe_ptr",
Andrew Boie3b930212016-06-07 13:11:45 -0700460 "_k_task_ptr", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500461 "_k_memory_pool", "exceptions", "initshell",
462 "_static_thread_area", "_k_timer_area",
463 "_k_mem_slab_area", "_k_mem_pool_area",
464 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
465 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
466 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700467 # These get copied into RAM only on non-XIP
Andrew Boie3b930212016-06-07 13:11:45 -0700468 ro_sections = ["text", "ctors", "init_array", "reset",
Tomasz Bursztykaa6cf6032016-09-12 08:53:55 +0200469 "rodata", "devconfig"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700470
Andrew Boie52fef672016-11-29 12:21:59 -0800471 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700472 """Constructor
473
Andrew Boiebbd670c2015-08-17 13:16:11 -0700474 @param filename Path to the output binary
475 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700476 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700477 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700478 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700479 magic = f.read(4)
480
Andrew Boie08ce5a52016-02-22 13:28:10 -0800481 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700482 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700483
484 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
485 # GREP can not be used as it returns an error if the symbol is not found.
Andrew Boiebbd670c2015-08-17 13:16:11 -0700486 is_xip_command = "nm " + filename + " | awk '/CONFIG_XIP/ { print $3 }'"
Andrew Boie8f0211d2016-03-02 20:40:29 -0800487 is_xip_output = subprocess.check_output(is_xip_command, shell=True,
488 stderr=subprocess.STDOUT).decode("utf-8").strip()
489 if is_xip_output.endswith("no symbols"):
490 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700491 self.is_xip = (len(is_xip_output) != 0)
492
Andrew Boiebbd670c2015-08-17 13:16:11 -0700493 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700494 self.sections = []
495 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700496 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800497 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700498
499 self._calculate_sizes()
500
501 def get_ram_size(self):
502 """Get the amount of RAM the application will use up on the device
503
504 @return amount of RAM, in bytes
505 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700506 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700507
508 def get_rom_size(self):
509 """Get the size of the data that this application uses on device's flash
510
511 @return amount of ROM, in bytes
512 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700513 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700514
515 def unrecognized_sections(self):
516 """Get a list of sections inside the binary that weren't recognized
517
518 @return list of unrecogized section names
519 """
520 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700521 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700522 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700523 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700524 return slist
525
526 def _calculate_sizes(self):
527 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700528 objdump_command = "objdump -h " + self.filename
Andrew Boie6acbe632015-07-17 12:03:52 -0700529 objdump_output = subprocess.check_output(objdump_command,
Andrew Boie08ce5a52016-02-22 13:28:10 -0800530 shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700531
532 for line in objdump_output:
533 words = line.split()
534
535 if (len(words) == 0): # Skip lines that are too short
536 continue
537
538 index = words[0]
539 if (not index[0].isdigit()): # Skip lines that do not start
540 continue # with a digit
541
542 name = words[1] # Skip lines with section names
543 if (name[0] == '.'): # starting with '.'
544 continue
545
Andrew Boie73b4ee62015-10-07 11:33:22 -0700546 # TODO this doesn't actually reflect the size in flash or RAM as
547 # it doesn't include linker-imposed padding between sections.
548 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700549 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700550 if size == 0:
551 continue
552
Andrew Boie73b4ee62015-10-07 11:33:22 -0700553 load_addr = int(words[4], 16)
554 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700555
556 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700557 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700558 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700559 if name in SizeCalculator.alloc_sections:
560 self.ram_size += size
561 stype = "alloc"
562 elif name in SizeCalculator.rw_sections:
563 self.ram_size += size
564 self.rom_size += size
565 stype = "rw"
566 elif name in SizeCalculator.ro_sections:
567 self.rom_size += size
568 if not self.is_xip:
569 self.ram_size += size
570 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700571 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700572 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800573 if name not in self.extra_sections:
574 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700575
Andrew Boie73b4ee62015-10-07 11:33:22 -0700576 self.sections.append({"name" : name, "load_addr" : load_addr,
577 "size" : size, "virt_addr" : virt_addr,
Andy Ross8d801a52016-10-04 11:48:21 -0700578 "type" : stype, "recognized" : recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700579
580
581class MakeGoal:
582 """Metadata class representing one of the sub-makes called by MakeGenerator
583
584 MakeGenerator returns a dictionary of these which can then be associdated
585 with TestInstances to get a complete picture of what happened during a test.
586 MakeGenerator is used for tasks outside of building tests (such as
587 defconfigs) which is why MakeGoal is a separate class from TestInstance.
588 """
589 def __init__(self, name, text, qemu, make_log, build_log, run_log,
590 qemu_log):
591 self.name = name
592 self.text = text
593 self.qemu = qemu
594 self.make_log = make_log
595 self.build_log = build_log
596 self.run_log = run_log
597 self.qemu_log = qemu_log
598 self.make_state = "waiting"
599 self.failed = False
600 self.finished = False
601 self.reason = None
602 self.metrics = {}
603
604 def get_error_log(self):
605 if self.make_state == "waiting":
606 # Shouldn't ever see this; breakage in the main Makefile itself.
607 return self.make_log
608 elif self.make_state == "building":
609 # Failure when calling the sub-make to build the code
610 return self.build_log
611 elif self.make_state == "running":
612 # Failure in sub-make for "make qemu", qemu probably failed to start
613 return self.run_log
614 elif self.make_state == "finished":
615 # QEMU finished, but timed out or otherwise wasn't successful
616 return self.qemu_log
617
618 def fail(self, reason):
619 self.failed = True
620 self.finished = True
621 self.reason = reason
622
623 def success(self):
624 self.finished = True
625
626 def __str__(self):
627 if self.finished:
628 if self.failed:
629 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
630 self.get_error_log())
631 else:
632 return "[%s] passed" % self.name
633 else:
634 return "[%s] in progress (%s)" % (self.name, self.make_state)
635
636
637class MakeGenerator:
638 """Generates a Makefile which just calls a bunch of sub-make sessions
639
640 In any given test suite we may need to build dozens if not hundreds of
641 test cases. The cleanest way to parallelize this is to just let Make
642 do the parallelization, sharing the jobserver among all the different
643 sub-make targets.
644 """
645
646 GOAL_HEADER_TMPL = """.PHONY: {goal}
647{goal}:
648"""
649
650 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Kumar Galad5719a62016-10-26 12:30:26 -0500651\t$(MAKE) -C {directory} USE_CCACHE={use_ccache} O={outdir} V={verb} EXTRA_CFLAGS="-Werror {cflags} -Wno-deprecated-declarations" EXTRA_ASMFLAGS=-Wa,--fatal-warnings EXTRA_LDFLAGS=--fatal-warnings {args} >{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -0700652"""
653
654 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
655
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300656 re_make = re.compile("sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700657
Kumar Galad5719a62016-10-26 12:30:26 -0500658 def __init__(self, base_outdir, asserts=False, ccache=0):
Andrew Boie6acbe632015-07-17 12:03:52 -0700659 """MakeGenerator constructor
660
661 @param base_outdir Intended to be the base out directory. A make.log
662 file will be created here which contains the output of the
663 top-level Make session, as well as the dynamic control Makefile
664 @param verbose If true, pass V=1 to all the sub-makes which greatly
665 increases their verbosity
666 """
667 self.goals = {}
668 if not os.path.exists(base_outdir):
669 os.makedirs(base_outdir)
670 self.logfile = os.path.join(base_outdir, "make.log")
671 self.makefile = os.path.join(base_outdir, "Makefile")
Andrew Boie55121052016-07-20 11:52:04 -0700672 self.asserts = asserts
Kumar Galad5719a62016-10-26 12:30:26 -0500673 self.ccache = ccache
Andrew Boie6acbe632015-07-17 12:03:52 -0700674
675 def _get_rule_header(self, name):
676 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
677
678 def _get_sub_make(self, name, phase, workdir, outdir, logfile, args):
679 verb = "1" if VERBOSE else "0"
680 args = " ".join(args)
Andrew Boie55121052016-07-20 11:52:04 -0700681 if self.asserts:
682 cflags="-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
683 else:
684 cflags=""
Kumar Galad5719a62016-10-26 12:30:26 -0500685 return MakeGenerator.MAKE_RULE_TMPL.format(phase=phase, goal=name, use_ccache=self.ccache,
Andrew Boie55121052016-07-20 11:52:04 -0700686 outdir=outdir, cflags=cflags,
Andrew Boie6acbe632015-07-17 12:03:52 -0700687 directory=workdir, verb=verb,
688 args=args, logfile=logfile)
689
690 def _get_rule_footer(self, name):
691 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
692
693 def _add_goal(self, outdir):
694 if not os.path.exists(outdir):
695 os.makedirs(outdir)
696
697 def add_build_goal(self, name, directory, outdir, args):
698 """Add a goal to invoke a Kbuild session
699
700 @param name A unique string name for this build goal. The results
701 dictionary returned by execute() will be keyed by this name.
702 @param directory Absolute path to working directory, will be passed
703 to make -C
704 @param outdir Absolute path to output directory, will be passed to
705 Kbuild via -O=<path>
706 @param args Extra command line arguments to pass to 'make', typically
707 environment variables or specific Make goals
708 """
709 self._add_goal(outdir)
710 build_logfile = os.path.join(outdir, "build.log")
711 text = (self._get_rule_header(name) +
712 self._get_sub_make(name, "building", directory,
713 outdir, build_logfile, args) +
714 self._get_rule_footer(name))
715 self.goals[name] = MakeGoal(name, text, None, self.logfile, build_logfile,
716 None, None)
717
718 def add_qemu_goal(self, name, directory, outdir, args, timeout=30):
719 """Add a goal to build a Zephyr project and then run it under QEMU
720
721 The generated make goal invokes Make twice, the first time it will
722 build the default goal, and the second will invoke the 'qemu' goal.
723 The output of the QEMU session will be monitored, and terminated
724 either upon pass/fail result of the test program, or the timeout
725 is reached.
726
727 @param name A unique string name for this build goal. The results
728 dictionary returned by execute() will be keyed by this name.
729 @param directory Absolute path to working directory, will be passed
730 to make -C
731 @param outdir Absolute path to output directory, will be passed to
732 Kbuild via -O=<path>
733 @param args Extra command line arguments to pass to 'make', typically
734 environment variables. Do not pass specific Make goals here.
735 @param timeout Maximum length of time QEMU session should be allowed
736 to run before automatically killing it. Default is 30 seconds.
737 """
738
739 self._add_goal(outdir)
740 build_logfile = os.path.join(outdir, "build.log")
741 run_logfile = os.path.join(outdir, "run.log")
742 qemu_logfile = os.path.join(outdir, "qemu.log")
743
744 q = QEMUHandler(name, outdir, qemu_logfile, timeout)
745 args.append("QEMU_PIPE=%s" % q.get_fifo())
746 text = (self._get_rule_header(name) +
747 self._get_sub_make(name, "building", directory,
748 outdir, build_logfile, args) +
749 self._get_sub_make(name, "running", directory,
750 outdir, run_logfile,
751 args + ["qemu"]) +
752 self._get_rule_footer(name))
753 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
754 run_logfile, qemu_logfile)
755
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300756 def add_unit_goal(self, name, directory, outdir, args, timeout=30, coverage=False):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300757 self._add_goal(outdir)
758 build_logfile = os.path.join(outdir, "build.log")
759 run_logfile = os.path.join(outdir, "run.log")
760 qemu_logfile = os.path.join(outdir, "qemu.log")
761 valgrind_logfile = os.path.join(outdir, "valgrind.log")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300762 if coverage:
763 args += ["COVERAGE=1"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300764
765 # we handle running in the UnitHandler class
766 text = (self._get_rule_header(name) +
767 self._get_sub_make(name, "building", directory,
768 outdir, build_logfile, args) +
769 self._get_rule_footer(name))
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300770 q = UnitHandler(name, directory, outdir, run_logfile, valgrind_logfile, timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300771 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
772 run_logfile, valgrind_logfile)
773
Andrew Boie6acbe632015-07-17 12:03:52 -0700774
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300775 def add_test_instance(self, ti, build_only=False, enable_slow=False, coverage=False,
Andrew Boieba612002016-09-01 10:41:03 -0700776 extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -0700777 """Add a goal to build/test a TestInstance object
778
779 @param ti TestInstance object to build. The status dictionary returned
780 by execute() will be keyed by its .name field.
781 """
782 args = ti.test.extra_args[:]
783 args.extend(["ARCH=%s" % ti.platform.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -0500784 "BOARD=%s" % ti.platform.name])
Andrew Boieba612002016-09-01 10:41:03 -0700785 args.extend(extra_args)
Andrew Boie6bb087c2016-02-10 13:39:00 -0800786 if (ti.platform.qemu_support and (not ti.build_only) and
787 (not build_only) and (enable_slow or not ti.test.slow)):
Andrew Boie6acbe632015-07-17 12:03:52 -0700788 self.add_qemu_goal(ti.name, ti.test.code_location, ti.outdir,
789 args, ti.test.timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300790 elif ti.test.type == "unit":
791 self.add_unit_goal(ti.name, ti.test.code_location, ti.outdir,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300792 args, ti.test.timeout, coverage)
Andrew Boie6acbe632015-07-17 12:03:52 -0700793 else:
794 self.add_build_goal(ti.name, ti.test.code_location, ti.outdir, args)
795
796 def execute(self, callback_fn=None, context=None):
797 """Execute all the registered build goals
798
799 @param callback_fn If not None, a callback function will be called
800 as individual goals transition between states. This function
801 should accept two parameters: a string state and an arbitrary
802 context object, supplied here
803 @param context Context object to pass to the callback function.
804 Type and semantics are specific to that callback function.
805 @return A dictionary mapping goal names to final status.
806 """
807
Andrew Boie08ce5a52016-02-22 13:28:10 -0800808 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -0700809 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -0800810 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -0700811 # Create our dynamic Makefile and execute it.
812 # Watch stderr output which is where we will keep
813 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -0800814 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -0700815 tf.write(goal.text)
816 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
817 tf.flush()
818
Daniel Leung6b170072016-04-07 12:10:25 -0700819 cmd = ["make", "-k", "-j", str(CPU_COUNTS * 2), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700820 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
821 stdout=devnull)
822
823 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -0800824 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -0700825 make_log.write(line)
826 verbose("MAKE: " + repr(line.strip()))
827 m = MakeGenerator.re_make.match(line)
828 if not m:
829 continue
830
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300831 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -0700832 if error:
833 goal = self.goals[error]
834 else:
835 goal = self.goals[name]
836 goal.make_state = state
837
838
839 if error:
840 goal.fail("build_error")
841 else:
842 if state == "finished":
843 if goal.qemu:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300844 if goal.qemu.unit:
845 # We can't run unit tests with Make
846 goal.qemu.handle()
847 if goal.qemu.returncode == 2:
848 goal.qemu_log = goal.qemu.valgrind_log
849 elif goal.qemu.returncode:
850 goal.qemu_log = goal.qemu.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700851 thread_status, metrics = goal.qemu.get_state()
852 goal.metrics.update(metrics)
853 if thread_status == "passed":
854 goal.success()
855 else:
856 goal.fail(thread_status)
857 else:
858 goal.success()
859
860 if callback_fn:
861 callback_fn(context, self.goals, goal)
862
863 p.wait()
864 return self.goals
865
866
867# "list" - List of strings
868# "list:<type>" - List of <type>
869# "set" - Set of unordered, unique strings
870# "set:<type>" - Set of <type>
871# "float" - Floating point
872# "int" - Integer
873# "bool" - Boolean
874# "str" - String
875
876# XXX Be sure to update __doc__ if you change any of this!!
877
878arch_valid_keys = {"name" : {"type" : "str", "required" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500879 "platforms" : {"type" : "list", "required" : True},
880 "supported_toolchains" : {"type" : "list", "required" : True}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700881
882platform_valid_keys = {"qemu_support" : {"type" : "bool", "default" : False},
Andrew Boie41878222016-11-03 11:58:53 -0700883 # FIXME remove
884 "microkernel_support" : {"type" : "bool",
885 "default" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500886 "supported_toolchains" : {"type" : "list", "default" : []}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700887
888testcase_valid_keys = {"tags" : {"type" : "set", "required" : True},
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300889 "type" : {"type" : "str", "default": "integration"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700890 "extra_args" : {"type" : "list"},
891 "build_only" : {"type" : "bool", "default" : False},
Anas Nashif2bd99bc2015-10-12 13:10:57 -0400892 "skip" : {"type" : "bool", "default" : False},
Andrew Boie6bb087c2016-02-10 13:39:00 -0800893 "slow" : {"type" : "bool", "default" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700894 "timeout" : {"type" : "int", "default" : 60},
Andrew Boie41878222016-11-03 11:58:53 -0700895 # FIXME remove once all testcase.ini are updated
Anas Nashifa7c4aa22016-02-09 20:31:26 -0500896 "kernel" : {"type": "str", "required" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700897 "arch_whitelist" : {"type" : "set"},
Anas Nashif30d13872015-10-05 10:02:45 -0400898 "arch_exclude" : {"type" : "set"},
Andrew Boie52fef672016-11-29 12:21:59 -0800899 "extra_sections" : {"type" : "list", "default" : []},
Anas Nashif30d13872015-10-05 10:02:45 -0400900 "platform_exclude" : {"type" : "set"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700901 "platform_whitelist" : {"type" : "set"},
Andrew Boie3ea78922016-03-24 14:46:00 -0700902 "filter" : {"type" : "str"}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700903
904
905class SanityConfigParser:
906 """Class to read architecture and test case .ini files with semantic checking
907 """
908 def __init__(self, filename):
909 """Instantiate a new SanityConfigParser object
910
911 @param filename Source .ini file to read
912 """
Andrew Boie08ce5a52016-02-22 13:28:10 -0800913 cp = configparser.SafeConfigParser()
Andrew Boie6acbe632015-07-17 12:03:52 -0700914 cp.readfp(open(filename))
915 self.filename = filename
916 self.cp = cp
917
918 def _cast_value(self, value, typestr):
919 v = value.strip()
920 if typestr == "str":
921 return v
922
923 elif typestr == "float":
924 return float(v)
925
926 elif typestr == "int":
927 return int(v)
928
929 elif typestr == "bool":
930 v = v.lower()
931 if v == "true" or v == "1":
932 return True
933 elif v == "" or v == "false" or v == "0":
934 return False
935 raise ConfigurationError(self.filename,
936 "bad value for boolean: '%s'" % value)
937
938 elif typestr.startswith("list"):
939 vs = v.split()
940 if len(typestr) > 4 and typestr[4] == ":":
941 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
942 else:
943 return vs
944
945 elif typestr.startswith("set"):
946 vs = v.split()
947 if len(typestr) > 3 and typestr[3] == ":":
948 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
949 else:
950 return set(vs)
951
952 else:
953 raise ConfigurationError(self.filename, "unknown type '%s'" % value)
954
955
956 def sections(self):
957 """Get the set of sections within the .ini file
958
959 @return a list of string section names"""
960 return self.cp.sections()
961
962 def get_section(self, section, valid_keys):
963 """Get a dictionary representing the keys/values within a section
964
965 @param section The section in the .ini file to retrieve data from
966 @param valid_keys A dictionary representing the intended semantics
967 for this section. Each key in this dictionary is a key that could
968 be specified, if a key is given in the .ini file which isn't in
969 here, it will generate an error. Each value in this dictionary
970 is another dictionary containing metadata:
971
972 "default" - Default value if not given
973 "type" - Data type to convert the text value to. Simple types
974 supported are "str", "float", "int", "bool" which will get
975 converted to respective Python data types. "set" and "list"
976 may also be specified which will split the value by
977 whitespace (but keep the elements as strings). finally,
978 "list:<type>" and "set:<type>" may be given which will
979 perform a type conversion after splitting the value up.
980 "required" - If true, raise an error if not defined. If false
981 and "default" isn't specified, a type conversion will be
982 done on an empty string
983 @return A dictionary containing the section key-value pairs with
984 type conversion and default values filled in per valid_keys
985 """
986
987 d = {}
988 cp = self.cp
989
990 if not cp.has_section(section):
Andrew Boiee57a1e52016-03-22 10:29:14 -0700991 # Just fill it with defaults
992 cp.add_section(section)
Andrew Boie6acbe632015-07-17 12:03:52 -0700993
994 for k, v in cp.items(section):
995 if k not in valid_keys:
996 raise ConfigurationError(self.filename,
997 "Unknown config key '%s' in defintiion for '%s'"
998 % (k, section))
999 d[k] = v
1000
Andrew Boie08ce5a52016-02-22 13:28:10 -08001001 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001002 if k not in d:
1003 if "required" in kinfo:
1004 required = kinfo["required"]
1005 else:
1006 required = False
1007
1008 if required:
1009 raise ConfigurationError(self.filename,
1010 "missing required value for '%s' in section '%s'"
1011 % (k, section))
1012 else:
1013 if "default" in kinfo:
1014 default = kinfo["default"]
1015 else:
1016 default = self._cast_value("", kinfo["type"])
1017 d[k] = default
1018 else:
1019 try:
1020 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001021 except ValueError as ve:
Andrew Boie6acbe632015-07-17 12:03:52 -07001022 raise ConfigurationError(self.filename,
1023 "bad %s value '%s' for key '%s' in section '%s'"
1024 % (kinfo["type"], d[k], k, section))
1025
1026 return d
1027
1028
1029class Platform:
1030 """Class representing metadata for a particular platform
1031
Anas Nashifc7406082015-12-13 15:00:31 -05001032 Maps directly to BOARD when building"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001033 def __init__(self, arch, name, plat_dict):
1034 """Constructor.
1035
1036 @param arch Architecture object for this platform
Anas Nashifc7406082015-12-13 15:00:31 -05001037 @param name String name for this platform, same as BOARD
Andrew Boie6acbe632015-07-17 12:03:52 -07001038 @param plat_dict SanityConfigParser output on the relevant section
1039 in the architecture configuration file which has lots of metadata.
1040 See the Architecture class.
1041 """
1042 self.name = name
1043 self.qemu_support = plat_dict["qemu_support"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001044 self.arch = arch
Javier B Perez4b554ba2016-08-15 13:25:33 -05001045 self.supported_toolchains = arch.supported_toolchains
1046 if plat_dict["supported_toolchains"]:
1047 self.supported_toolchains = plat_dict["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001048 # Gets populated in a separate step
Andrew Boie41878222016-11-03 11:58:53 -07001049 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001050 pass
1051
Andrew Boie6acbe632015-07-17 12:03:52 -07001052 def __repr__(self):
1053 return "<%s on %s>" % (self.name, self.arch.name)
1054
1055
1056class Architecture:
1057 """Class representing metadata for a particular architecture
1058 """
1059 def __init__(self, cfile):
1060 """Architecture constructor
1061
1062 @param cfile Path to Architecture configuration file, which gives
1063 info about the arch and all the platforms for it
1064 """
1065 cp = SanityConfigParser(cfile)
1066 self.platforms = []
1067
1068 arch = cp.get_section("arch", arch_valid_keys)
1069
1070 self.name = arch["name"]
Javier B Perez4b554ba2016-08-15 13:25:33 -05001071 self.supported_toolchains = arch["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001072
1073 for plat_name in arch["platforms"]:
1074 verbose("Platform: %s" % plat_name)
1075 plat_dict = cp.get_section(plat_name, platform_valid_keys)
1076 self.platforms.append(Platform(self, plat_name, plat_dict))
1077
1078 def __repr__(self):
1079 return "<arch %s>" % self.name
1080
1081
1082class TestCase:
1083 """Class representing a test application
1084 """
Andrew Boie3ea78922016-03-24 14:46:00 -07001085 def __init__(self, testcase_root, workdir, name, tc_dict, inifile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001086 """TestCase constructor.
1087
1088 This gets called by TestSuite as it finds and reads testcase.ini files.
1089 Multiple TestCase instances may be generated from a single testcase.ini,
1090 each one corresponds to a section within that file.
1091
Andrew Boie6acbe632015-07-17 12:03:52 -07001092 We need to have a unique name for every single test case. Since
1093 a testcase.ini can define multiple tests, the canonical name for
1094 the test case is <workdir>/<name>.
1095
1096 @param testcase_root Absolute path to the root directory where
1097 all the test cases live
1098 @param workdir Relative path to the project directory for this
1099 test application from the test_case root.
1100 @param name Name of this test case, corresponding to the section name
1101 in the test case configuration file. For many test cases that just
1102 define one test, can be anything and is usually "test". This is
1103 really only used to distinguish between different cases when
1104 the testcase.ini defines multiple tests
1105 @param tc_dict Dictionary with section values for this test case
1106 from the testcase.ini file
1107 """
1108 self.code_location = os.path.join(testcase_root, workdir)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001109 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001110 self.tags = tc_dict["tags"]
1111 self.extra_args = tc_dict["extra_args"]
1112 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001113 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001114 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001115 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001116 self.platform_whitelist = tc_dict["platform_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001117 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001118 self.timeout = tc_dict["timeout"]
1119 self.build_only = tc_dict["build_only"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001120 self.slow = tc_dict["slow"]
Andrew Boie52fef672016-11-29 12:21:59 -08001121 self.extra_sections = tc_dict["extra_sections"]
Andrew Boie14ac9022016-04-08 13:31:53 -07001122 self.path = os.path.join(os.path.basename(os.path.abspath(testcase_root)),
1123 workdir, name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001124 self.name = self.path # for now
Anas Nashif2cf0df02015-10-10 09:29:43 -04001125 self.defconfig = {}
Andrew Boie3ea78922016-03-24 14:46:00 -07001126 self.inifile = inifile
Andrew Boie6acbe632015-07-17 12:03:52 -07001127
Andrew Boie6acbe632015-07-17 12:03:52 -07001128 def __repr__(self):
1129 return self.name
1130
1131
1132
1133class TestInstance:
1134 """Class representing the execution of a particular TestCase on a platform
1135
1136 @param test The TestCase object we want to build/execute
1137 @param platform Platform object that we want to build and run against
1138 @param base_outdir Base directory for all test results. The actual
1139 out directory used is <outdir>/<platform>/<test case name>
1140 """
Andrew Boie6bb087c2016-02-10 13:39:00 -08001141 def __init__(self, test, platform, base_outdir, build_only=False,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001142 slow=False, coverage=False):
Andrew Boie6acbe632015-07-17 12:03:52 -07001143 self.test = test
1144 self.platform = platform
1145 self.name = os.path.join(platform.name, test.path)
1146 self.outdir = os.path.join(base_outdir, platform.name, test.path)
1147 self.build_only = build_only or test.build_only
1148
1149 def calculate_sizes(self):
1150 """Get the RAM/ROM sizes of a test case.
1151
1152 This can only be run after the instance has been executed by
1153 MakeGenerator, otherwise there won't be any binaries to measure.
1154
1155 @return A SizeCalculator object
1156 """
Andrew Boie5d4eb782015-10-02 10:04:56 -07001157 fns = glob.glob(os.path.join(self.outdir, "*.elf"))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001158 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001159 if (len(fns) != 1):
1160 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001161 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001162
1163 def __repr__(self):
1164 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1165
1166
Andrew Boie4ef16c52015-08-28 12:36:03 -07001167def defconfig_cb(context, goals, goal):
1168 if not goal.failed:
1169 return
1170
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001171
Andrew Boie4ef16c52015-08-28 12:36:03 -07001172 info("%sCould not build defconfig for %s%s" %
1173 (COLOR_RED, goal.name, COLOR_NORMAL));
1174 if INLINE_LOGS:
1175 with open(goal.get_error_log()) as fp:
1176 sys.stdout.write(fp.read())
1177 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001178 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001179
Andrew Boie6acbe632015-07-17 12:03:52 -07001180
1181class TestSuite:
Andrew Boie3ea78922016-03-24 14:46:00 -07001182 config_re = re.compile('(CONFIG_[A-Z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001183
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001184 def __init__(self, arch_root, testcase_roots, outdir, coverage):
Andrew Boie6acbe632015-07-17 12:03:52 -07001185 # Keep track of which test cases we've filtered out and why
1186 discards = {}
1187 self.arches = {}
1188 self.testcases = {}
1189 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001190 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001191 self.instances = {}
1192 self.goals = None
1193 self.discards = None
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001194 self.coverage = coverage
Andrew Boie6acbe632015-07-17 12:03:52 -07001195
1196 arch_root = os.path.abspath(arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001197
Andrew Boie3d348712016-04-08 11:52:13 -07001198 for testcase_root in testcase_roots:
1199 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001200
Andrew Boie3d348712016-04-08 11:52:13 -07001201 debug("Reading test case configuration files under %s..." %
1202 testcase_root)
1203 for dirpath, dirnames, filenames in os.walk(testcase_root,
1204 topdown=True):
1205 verbose("scanning %s" % dirpath)
1206 if "testcase.ini" in filenames:
1207 verbose("Found test case in " + dirpath)
1208 dirnames[:] = []
Andrew Boie3ea78922016-03-24 14:46:00 -07001209 ini_path = os.path.join(dirpath, "testcase.ini")
1210 cp = SanityConfigParser(ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001211 workdir = os.path.relpath(dirpath, testcase_root)
1212
1213 for section in cp.sections():
1214 tc_dict = cp.get_section(section, testcase_valid_keys)
Andrew Boie3ea78922016-03-24 14:46:00 -07001215 tc = TestCase(testcase_root, workdir, section, tc_dict,
1216 ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001217 self.testcases[tc.name] = tc
Andrew Boie6acbe632015-07-17 12:03:52 -07001218
1219 debug("Reading architecture configuration files under %s..." % arch_root)
1220 for dirpath, dirnames, filenames in os.walk(arch_root):
1221 for filename in filenames:
1222 if filename.endswith(".ini"):
1223 fn = os.path.join(dirpath, filename)
1224 verbose("Found arch configuration " + fn)
1225 arch = Architecture(fn)
1226 self.arches[arch.name] = arch
1227 self.platforms.extend(arch.platforms)
1228
Andrew Boie05e3d7f2016-08-09 11:29:34 -07001229 # Build up a list of boards based on the presence of
1230 # boards/*/*_defconfig files. We want to make sure that the arch.ini
1231 # files are not missing any boards
1232 all_plats = [plat.name for plat in self.platforms]
1233 for dirpath, dirnames, filenames in os.walk(os.path.join(ZEPHYR_BASE,
1234 "boards")):
1235 for filename in filenames:
1236 if filename.endswith("_defconfig"):
1237 board_name = filename.replace("_defconfig", "")
1238 if board_name not in all_plats:
1239 error("Platform '%s' not specified in any arch .ini file and will not be tested"
1240 % board_name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001241 self.instances = {}
1242
1243 def get_last_failed(self):
1244 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001245 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001246 result = []
1247 with open(LAST_SANITY, "r") as fp:
1248 cr = csv.DictReader(fp)
1249 for row in cr:
1250 if row["passed"] == "True":
1251 continue
1252 test = row["test"]
1253 platform = row["platform"]
1254 result.append((test, platform))
1255 return result
1256
Anas Nashifdfa86e22016-10-24 17:08:56 -04001257 def apply_filters(self, platform_filter, arch_filter, tag_filter, exclude_tag,
Andrew Boie821d8322016-03-22 10:08:35 -07001258 config_filter, testcase_filter, last_failed, all_plats,
Kumar Galad5719a62016-10-26 12:30:26 -05001259 platform_limit, toolchain, extra_args, enable_ccache):
Andrew Boie6acbe632015-07-17 12:03:52 -07001260 instances = []
1261 discards = {}
1262 verbose("platform filter: " + str(platform_filter))
1263 verbose(" arch_filter: " + str(arch_filter))
1264 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001265 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001266 verbose(" config_filter: " + str(config_filter))
Kumar Galad5719a62016-10-26 12:30:26 -05001267 verbose(" enable_ccache: " + str(enable_ccache))
Andrew Boie6acbe632015-07-17 12:03:52 -07001268
1269 if last_failed:
1270 failed_tests = self.get_last_failed()
1271
Andrew Boie821d8322016-03-22 10:08:35 -07001272 default_platforms = False
1273
1274 if all_plats:
1275 info("Selecting all possible platforms per test case")
1276 # When --all used, any --platform arguments ignored
1277 platform_filter = []
1278 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001279 info("Selecting default platforms per test case")
1280 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001281
Kumar Galad5719a62016-10-26 12:30:26 -05001282 mg = MakeGenerator(self.outdir, ccache=enable_ccache)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001283 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001284 for tc_name, tc in self.testcases.items():
1285 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001286 instance_list = []
1287 for plat in arch.platforms:
1288 instance = TestInstance(tc, plat, self.outdir)
1289
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001290 if (arch_name == "unit") != (tc.type == "unit"):
1291 continue
1292
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001293 if tc.skip:
1294 continue
1295
Anas Nashif2cf0df02015-10-10 09:29:43 -04001296 if tag_filter and not tc.tags.intersection(tag_filter):
1297 continue
1298
Anas Nashifdfa86e22016-10-24 17:08:56 -04001299 if exclude_tag and tc.tags.intersection(exclude_tag):
1300 continue
1301
Anas Nashif2cf0df02015-10-10 09:29:43 -04001302 if testcase_filter and tc_name not in testcase_filter:
1303 continue
1304
1305 if last_failed and (tc.name, plat.name) not in failed_tests:
1306 continue
1307
1308 if arch_filter and arch_name not in arch_filter:
1309 continue
1310
1311 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1312 continue
1313
1314 if tc.arch_exclude and arch.name in tc.arch_exclude:
1315 continue
1316
1317 if tc.platform_exclude and plat.name in tc.platform_exclude:
1318 continue
1319
1320 if platform_filter and plat.name not in platform_filter:
1321 continue
1322
1323 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1324 continue
1325
Andrew Boie3ea78922016-03-24 14:46:00 -07001326 if tc.tc_filter:
Anas Nashif8ea9d022015-11-10 12:24:20 -05001327 args = tc.extra_args[:]
1328 args.extend(["ARCH=" + plat.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -05001329 "BOARD=" + plat.name, "initconfig"])
Andrew Boieba612002016-09-01 10:41:03 -07001330 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001331 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001332 # conf, gen_idt, etc aren't rebuilt for every combination,
Anas Nashif2cf0df02015-10-10 09:29:43 -04001333 # need a way to avoid different Make processe from clobbering
1334 # each other since they all try to build them simultaneously
1335
1336 o = os.path.join(self.outdir, plat.name, tc.path)
Andrew Boie41878222016-11-03 11:58:53 -07001337 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o,".config")
1338 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "initconfig"])
Anas Nashif2cf0df02015-10-10 09:29:43 -04001339 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location), o, args)
1340
1341 info("Building testcase defconfigs...")
1342 results = mg.execute(defconfig_cb)
1343
Andrew Boie08ce5a52016-02-22 13:28:10 -08001344 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001345 if goal.failed:
1346 raise SanityRuntimeError("Couldn't build some defconfigs")
1347
Andrew Boie08ce5a52016-02-22 13:28:10 -08001348 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001349 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001350 defconfig = {}
1351 with open(out_config, "r") as fp:
1352 for line in fp.readlines():
1353 m = TestSuite.config_re.match(line)
1354 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001355 if line.strip() and not line.startswith("#"):
1356 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001357 continue
1358 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001359 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001360
Andrew Boie08ce5a52016-02-22 13:28:10 -08001361 for tc_name, tc in self.testcases.items():
1362 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001363 instance_list = []
1364 for plat in arch.platforms:
1365 instance = TestInstance(tc, plat, self.outdir)
1366
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001367 if (arch_name == "unit") != (tc.type == "unit"):
1368 # Discard silently
1369 continue
1370
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001371 if tc.skip:
1372 discards[instance] = "Skip filter"
1373 continue
1374
Andrew Boie6acbe632015-07-17 12:03:52 -07001375 if tag_filter and not tc.tags.intersection(tag_filter):
1376 discards[instance] = "Command line testcase tag filter"
1377 continue
1378
Anas Nashifdfa86e22016-10-24 17:08:56 -04001379 if exclude_tag and tc.tags.intersection(exclude_tag):
1380 discards[instance] = "Command line testcase exclude filter"
1381 continue
1382
Andrew Boie6acbe632015-07-17 12:03:52 -07001383 if testcase_filter and tc_name not in testcase_filter:
1384 discards[instance] = "Testcase name filter"
1385 continue
1386
1387 if last_failed and (tc.name, plat.name) not in failed_tests:
1388 discards[instance] = "Passed or skipped during last run"
1389 continue
1390
1391 if arch_filter and arch_name not in arch_filter:
1392 discards[instance] = "Command line testcase arch filter"
1393 continue
1394
1395 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1396 discards[instance] = "Not in test case arch whitelist"
1397 continue
1398
Anas Nashif30d13872015-10-05 10:02:45 -04001399 if tc.arch_exclude and arch.name in tc.arch_exclude:
1400 discards[instance] = "In test case arch exclude"
1401 continue
1402
1403 if tc.platform_exclude and plat.name in tc.platform_exclude:
1404 discards[instance] = "In test case platform exclude"
1405 continue
1406
Andrew Boie6acbe632015-07-17 12:03:52 -07001407 if platform_filter and plat.name not in platform_filter:
1408 discards[instance] = "Command line platform filter"
1409 continue
1410
1411 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1412 discards[instance] = "Not in testcase platform whitelist"
1413 continue
1414
Javier B Perez4b554ba2016-08-15 13:25:33 -05001415 if toolchain and toolchain not in plat.supported_toolchains:
1416 discards[instance] = "Not supported by the toolchain"
1417 continue
1418
Andrew Boie3ea78922016-03-24 14:46:00 -07001419 defconfig = {"ARCH" : arch.name, "PLATFORM" : plat.name}
Javier B Perez79414542016-08-08 12:24:59 -05001420 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001421 for p, tdefconfig in tc.defconfig.items():
1422 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001423 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001424 break
1425
Andrew Boie3ea78922016-03-24 14:46:00 -07001426 if tc.tc_filter:
1427 try:
1428 res = expr_parser.parse(tc.tc_filter, defconfig)
1429 except SyntaxError as se:
1430 sys.stderr.write("Failed processing %s\n" % tc.inifile)
1431 raise se
1432 if not res:
1433 discards[instance] = ("defconfig doesn't satisfy expression '%s'" %
1434 tc.tc_filter)
1435 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001436
Andrew Boie6acbe632015-07-17 12:03:52 -07001437 instance_list.append(instance)
1438
1439 if not instance_list:
1440 # Every platform in this arch was rejected already
1441 continue
1442
1443 if default_platforms:
Andrew Boie821d8322016-03-22 10:08:35 -07001444 self.add_instances(instance_list[:platform_limit])
1445 for instance in instance_list[platform_limit:]:
1446 discards[instance] = "Not in first %d platform(s) for arch" % platform_limit
Andrew Boie6acbe632015-07-17 12:03:52 -07001447 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001448 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001449 self.discards = discards
1450 return discards
1451
Andrew Boie821d8322016-03-22 10:08:35 -07001452 def add_instances(self, ti_list):
1453 for ti in ti_list:
1454 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001455
Andrew Boieba612002016-09-01 10:41:03 -07001456 def execute(self, cb, cb_context, build_only, enable_slow, enable_asserts,
Kumar Galad5719a62016-10-26 12:30:26 -05001457 extra_args, enable_ccache):
Daniel Leung6b170072016-04-07 12:10:25 -07001458
1459 def calc_one_elf_size(name, goal):
1460 if not goal.failed:
1461 i = self.instances[name]
1462 sc = i.calculate_sizes()
1463 goal.metrics["ram_size"] = sc.get_ram_size()
1464 goal.metrics["rom_size"] = sc.get_rom_size()
1465 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07001466
Kumar Galad5719a62016-10-26 12:30:26 -05001467 mg = MakeGenerator(self.outdir, asserts=enable_asserts, ccache=enable_ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001468 for i in self.instances.values():
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001469 mg.add_test_instance(i, build_only, enable_slow, self.coverage, extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001470 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07001471
1472 # Parallelize size calculation
1473 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
1474 futures = [executor.submit(calc_one_elf_size, name, goal) \
1475 for name, goal in self.goals.items()]
1476 concurrent.futures.wait(futures)
1477
Andrew Boie6acbe632015-07-17 12:03:52 -07001478 return self.goals
1479
1480 def discard_report(self, filename):
1481 if self.discards == None:
1482 raise SanityRuntimeException("apply_filters() hasn't been run!")
1483
1484 with open(filename, "wb") as csvfile:
1485 fieldnames = ["test", "arch", "platform", "reason"]
1486 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1487 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001488 for instance, reason in self.discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001489 rowdict = {"test" : i.test.name,
1490 "arch" : i.platform.arch.name,
1491 "platform" : i.platform.name,
1492 "reason" : reason}
1493 cw.writerow(rowdict)
1494
1495 def compare_metrics(self, filename):
1496 # name, datatype, lower results better
1497 interesting_metrics = [("ram_size", int, True),
1498 ("rom_size", int, True)]
1499
1500 if self.goals == None:
1501 raise SanityRuntimeException("execute() hasn't been run!")
1502
1503 if not os.path.exists(filename):
1504 info("Cannot compare metrics, %s not found" % filename)
1505 return []
1506
1507 results = []
1508 saved_metrics = {}
1509 with open(filename) as fp:
1510 cr = csv.DictReader(fp)
1511 for row in cr:
1512 d = {}
1513 for m, _, _ in interesting_metrics:
1514 d[m] = row[m]
1515 saved_metrics[(row["test"], row["platform"])] = d
1516
Andrew Boie08ce5a52016-02-22 13:28:10 -08001517 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001518 i = self.instances[name]
1519 mkey = (i.test.name, i.platform.name)
1520 if mkey not in saved_metrics:
1521 continue
1522 sm = saved_metrics[mkey]
1523 for metric, mtype, lower_better in interesting_metrics:
1524 if metric not in goal.metrics:
1525 continue
1526 if sm[metric] == "":
1527 continue
1528 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07001529 if delta == 0:
1530 continue
1531 results.append((i, metric, goal.metrics[metric], delta,
1532 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07001533 return results
1534
1535 def testcase_report(self, filename):
1536 if self.goals == None:
1537 raise SanityRuntimeException("execute() hasn't been run!")
1538
Andrew Boie08ce5a52016-02-22 13:28:10 -08001539 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07001540 fieldnames = ["test", "arch", "platform", "passed", "status",
1541 "extra_args", "qemu", "qemu_time", "ram_size",
1542 "rom_size"]
1543 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1544 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001545 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001546 i = self.instances[name]
1547 rowdict = {"test" : i.test.name,
1548 "arch" : i.platform.arch.name,
1549 "platform" : i.platform.name,
1550 "extra_args" : " ".join(i.test.extra_args),
1551 "qemu" : i.platform.qemu_support}
1552 if goal.failed:
1553 rowdict["passed"] = False
1554 rowdict["status"] = goal.reason
1555 else:
1556 rowdict["passed"] = True
1557 if goal.qemu:
1558 rowdict["qemu_time"] = goal.metrics["qemu_time"]
1559 rowdict["ram_size"] = goal.metrics["ram_size"]
1560 rowdict["rom_size"] = goal.metrics["rom_size"]
1561 cw.writerow(rowdict)
1562
1563
1564def parse_arguments():
1565
1566 parser = argparse.ArgumentParser(description = __doc__,
1567 formatter_class = argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05001568 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07001569
1570 parser.add_argument("-p", "--platform", action="append",
Andrew Boie821d8322016-03-22 10:08:35 -07001571 help="Platform filter for testing. This option may be used multiple "
1572 "times. Testcases will only be built/run on the platforms "
1573 "specified. If this option is not used, then N platforms will "
1574 "automatically be chosen from each arch to build and test, "
1575 "where N is provided by the --platform-limit option.")
1576 parser.add_argument("-L", "--platform-limit", action="store", type=int,
1577 metavar="N", default=1,
1578 help="Controls what platforms are tested if --platform or "
1579 "--all are not used. For each architecture specified by "
1580 "--arch (defaults to all of them), choose the first "
1581 "N platforms to test in the arch-specific .ini file "
1582 "'platforms' list. Defaults to 1.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001583 parser.add_argument("-a", "--arch", action="append",
1584 help="Arch filter for testing. Takes precedence over --platform. "
1585 "If unspecified, test all arches. Multiple invocations "
1586 "are treated as a logical 'or' relationship")
1587 parser.add_argument("-t", "--tag", action="append",
1588 help="Specify tags to restrict which tests to run by tag value. "
1589 "Default is to not do any tag filtering. Multiple invocations "
1590 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04001591 parser.add_argument("-e", "--exclude-tag", action="append",
1592 help="Specify tags of tests that should not run."
1593 "Default is to run all tests with all tags.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001594 parser.add_argument("-f", "--only-failed", action="store_true",
1595 help="Run only those tests that failed the previous sanity check "
1596 "invocation.")
1597 parser.add_argument("-c", "--config", action="append",
1598 help="Specify platform configuration values filtering. This can be "
1599 "specified two ways: <config>=<value> or just <config>. The "
Andrew Boie41878222016-11-03 11:58:53 -07001600 "defconfig for all platforms will be "
Andrew Boie6acbe632015-07-17 12:03:52 -07001601 "checked. For the <config>=<value> case, only match defconfig "
1602 "that have that value defined. For the <config> case, match "
1603 "defconfig that have that value assigned to any value. "
1604 "Prepend a '!' to invert the match.")
1605 parser.add_argument("-s", "--test", action="append",
1606 help="Run only the specified test cases. These are named by "
1607 "<path to test project relative to "
1608 "--testcase-root>/<testcase.ini section name>")
1609 parser.add_argument("-l", "--all", action="store_true",
Andrew Boie821d8322016-03-22 10:08:35 -07001610 help="Build/test on all platforms. Any --platform arguments "
1611 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001612
1613 parser.add_argument("-o", "--testcase-report",
1614 help="Output a CSV spreadsheet containing results of the test run")
1615 parser.add_argument("-d", "--discard-report",
1616 help="Output a CSV spreadhseet showing tests that were skipped "
1617 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07001618 parser.add_argument("--compare-report",
1619 help="Use this report file for size comparision")
1620
Kumar Galad5719a62016-10-26 12:30:26 -05001621 parser.add_argument("--ccache", action="store_const", const=1, default=0,
1622 help="Enable the use of ccache when building")
1623
Andrew Boie6acbe632015-07-17 12:03:52 -07001624 parser.add_argument("-y", "--dry-run", action="store_true",
1625 help="Create the filtered list of test cases, but don't actually "
1626 "run them. Useful if you're just interested in "
1627 "--discard-report")
1628
1629 parser.add_argument("-r", "--release", action="store_true",
1630 help="Update the benchmark database with the results of this test "
1631 "run. Intended to be run by CI when tagging an official "
1632 "release. This database is used as a basis for comparison "
1633 "when looking for deltas in metrics such as footprint")
1634 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
1635 help="Treat warning conditions as errors")
1636 parser.add_argument("-v", "--verbose", action="count", default=0,
1637 help="Emit debugging information, call multiple times to increase "
1638 "verbosity")
1639 parser.add_argument("-i", "--inline-logs", action="store_true",
1640 help="Upon test failure, print relevant log data to stdout "
1641 "instead of just a path to it")
1642 parser.add_argument("-m", "--last-metrics", action="store_true",
1643 help="Instead of comparing metrics from the last --release, "
1644 "compare with the results of the previous sanity check "
1645 "invocation")
1646 parser.add_argument("-u", "--no-update", action="store_true",
1647 help="do not update the results of the last run of the sanity "
1648 "checks")
1649 parser.add_argument("-b", "--build-only", action="store_true",
1650 help="Only build the code, do not execute any of it in QEMU")
1651 parser.add_argument("-j", "--jobs", type=int,
1652 help="Number of cores to use when building, defaults to "
1653 "number of CPUs * 2")
1654 parser.add_argument("-H", "--footprint-threshold", type=float, default=5,
1655 help="When checking test case footprint sizes, warn the user if "
1656 "the new app size is greater then the specified percentage "
1657 "from the last release. Default is 5. 0 to warn on any "
1658 "increase on app size")
Andrew Boieea7928f2015-08-14 14:27:38 -07001659 parser.add_argument("-D", "--all-deltas", action="store_true",
1660 help="Show all footprint deltas, positive or negative. Implies "
1661 "--footprint-threshold=0")
Andrew Boie6acbe632015-07-17 12:03:52 -07001662 parser.add_argument("-O", "--outdir",
1663 default="%s/sanity-out" % ZEPHYR_BASE,
1664 help="Output directory for logs and binaries.")
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001665 parser.add_argument("-n", "--no-clean", action="store_true",
1666 help="Do not delete the outdir before building. Will result in "
1667 "faster compilation since builds will be incremental")
Andrew Boie3d348712016-04-08 11:52:13 -07001668 parser.add_argument("-T", "--testcase-root", action="append", default=[],
Andrew Boie6acbe632015-07-17 12:03:52 -07001669 help="Base directory to recursively search for test cases. All "
Andrew Boie3d348712016-04-08 11:52:13 -07001670 "testcase.ini files under here will be processed. May be "
1671 "called multiple times. Defaults to the 'samples' and "
1672 "'tests' directories in the Zephyr tree.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001673 parser.add_argument("-A", "--arch-root",
1674 default="%s/scripts/sanity_chk/arches" % ZEPHYR_BASE,
1675 help="Directory to search for arch configuration files. All .ini "
1676 "files in the directory will be processed.")
Andrew Boiebbd670c2015-08-17 13:16:11 -07001677 parser.add_argument("-z", "--size", action="append",
1678 help="Don't run sanity checks. Instead, produce a report to "
1679 "stdout detailing RAM/ROM sizes on the specified filenames. "
1680 "All other command line arguments ignored.")
Andrew Boie6bb087c2016-02-10 13:39:00 -08001681 parser.add_argument("-S", "--enable-slow", action="store_true",
1682 help="Execute time-consuming test cases that have been marked "
1683 "as 'slow' in testcase.ini. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07001684 parser.add_argument("-R", "--enable-asserts", action="store_true",
1685 help="Build all test cases with assertions enabled.")
Andrew Boieba612002016-09-01 10:41:03 -07001686 parser.add_argument("-x", "--extra-args", action="append", default=[],
1687 help="Extra arguments to pass to the build when compiling test "
1688 "cases. May be called multiple times. These will be passed "
1689 "in after any sanitycheck-supplied options.")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001690 parser.add_argument("-C", "--coverage", action="store_true",
1691 help="Scan for unit test coverage with gcov + lcov.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001692
1693 return parser.parse_args()
1694
1695def log_info(filename):
1696 filename = os.path.relpath(filename)
1697 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001698 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001699 with open(filename) as fp:
1700 sys.stdout.write(fp.read())
Andrew Boie08ce5a52016-02-22 13:28:10 -08001701 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001702 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001703 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07001704
1705def terse_test_cb(instances, goals, goal):
1706 total_tests = len(goals)
1707 total_done = 0
1708 total_failed = 0
1709
Andrew Boie08ce5a52016-02-22 13:28:10 -08001710 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001711 if g.finished:
1712 total_done += 1
1713 if g.failed:
1714 total_failed += 1
1715
1716 if goal.failed:
1717 i = instances[goal.name]
1718 info("\n\n{:<25} {:<50} {}FAILED{}: {}".format(i.platform.name,
1719 i.test.name, COLOR_RED, COLOR_NORMAL, goal.reason))
1720 log_info(goal.get_error_log())
1721 info("")
1722
1723 sys.stdout.write("\rtotal complete: %s%3d/%3d%s failed: %s%3d%s" % (
1724 COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
1725 COLOR_RED if total_failed > 0 else COLOR_NORMAL,
1726 total_failed, COLOR_NORMAL))
1727 sys.stdout.flush()
1728
1729def chatty_test_cb(instances, goals, goal):
1730 i = instances[goal.name]
1731
1732 if VERBOSE < 2 and not goal.finished:
1733 return
1734
1735 if goal.failed:
1736 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
1737 elif goal.finished:
1738 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
1739 else:
1740 status = goal.make_state
1741
1742 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
1743 if goal.failed:
1744 log_info(goal.get_error_log())
1745
Andrew Boiebbd670c2015-08-17 13:16:11 -07001746
1747def size_report(sc):
1748 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07001749 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07001750 for i in range(len(sc.sections)):
1751 v = sc.sections[i]
1752
Andrew Boie73b4ee62015-10-07 11:33:22 -07001753 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
1754 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
1755 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07001756
Andrew Boie73b4ee62015-10-07 11:33:22 -07001757 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
1758 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001759 info("")
1760
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001761def generate_coverage(outdir, ignores):
1762 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
1763 coveragefile = os.path.join(outdir, "coverage.info")
1764 ztestfile = os.path.join(outdir, "ztest.info")
1765 subprocess.call(["lcov", "--capture", "--directory", outdir,
1766 "--output-file", coveragefile], stdout=coveragelog)
1767 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
1768 subprocess.call(["lcov", "--extract", coveragefile,
1769 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
1770 "--output-file", ztestfile], stdout=coveragelog)
1771 subprocess.call(["lcov", "--remove", ztestfile,
1772 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
1773 "--output-file", ztestfile], stdout=coveragelog)
1774 for i in ignores:
1775 subprocess.call(["lcov", "--remove", coveragefile, i,
1776 "--output-file", coveragefile], stdout=coveragelog)
1777 subprocess.call(["genhtml", "-output-directory",
1778 os.path.join(outdir, "coverage"),
1779 coveragefile, ztestfile], stdout=coveragelog)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001780
Andrew Boie6acbe632015-07-17 12:03:52 -07001781def main():
Andrew Boie4b182472015-07-31 12:25:22 -07001782 start_time = time.time()
Daniel Leung6b170072016-04-07 12:10:25 -07001783 global VERBOSE, INLINE_LOGS, CPU_COUNTS
Andrew Boie6acbe632015-07-17 12:03:52 -07001784 args = parse_arguments()
Javier B Perez4b554ba2016-08-15 13:25:33 -05001785 toolchain = os.environ.get("ZEPHYR_GCC_VARIANT", None)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001786
1787 if args.size:
1788 for fn in args.size:
Andrew Boie52fef672016-11-29 12:21:59 -08001789 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001790 sys.exit(0)
1791
Andrew Boie6acbe632015-07-17 12:03:52 -07001792 VERBOSE += args.verbose
1793 INLINE_LOGS = args.inline_logs
1794 if args.jobs:
Daniel Leung6b170072016-04-07 12:10:25 -07001795 CPU_COUNTS = args.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07001796
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001797 if os.path.exists(args.outdir) and not args.no_clean:
Andrew Boie6acbe632015-07-17 12:03:52 -07001798 info("Cleaning output directory " + args.outdir)
1799 shutil.rmtree(args.outdir)
1800
Andrew Boie3d348712016-04-08 11:52:13 -07001801 if not args.testcase_root:
1802 args.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
1803 os.path.join(ZEPHYR_BASE, "samples")]
1804
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001805 ts = TestSuite(args.arch_root, args.testcase_root, args.outdir, args.coverage)
Anas Nashifdfa86e22016-10-24 17:08:56 -04001806 discards = ts.apply_filters(args.platform, args.arch, args.tag, args.exclude_tag, args.config,
Andrew Boie821d8322016-03-22 10:08:35 -07001807 args.test, args.only_failed, args.all,
Kumar Galad5719a62016-10-26 12:30:26 -05001808 args.platform_limit, toolchain, args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001809
1810 if args.discard_report:
1811 ts.discard_report(args.discard_report)
1812
1813 if VERBOSE:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001814 for i, reason in discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001815 debug("{:<25} {:<50} {}SKIPPED{}: {}".format(i.platform.name,
1816 i.test.name, COLOR_YELLOW, COLOR_NORMAL, reason))
1817
1818 info("%d tests selected, %d tests discarded due to filters" %
1819 (len(ts.instances), len(discards)))
1820
1821 if args.dry_run:
1822 return
1823
1824 if VERBOSE or not TERMINAL:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001825 goals = ts.execute(chatty_test_cb, ts.instances, args.build_only,
Andrew Boieba612002016-09-01 10:41:03 -07001826 args.enable_slow, args.enable_asserts,
Kumar Galad5719a62016-10-26 12:30:26 -05001827 args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001828 else:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001829 goals = ts.execute(terse_test_cb, ts.instances, args.build_only,
Andrew Boieba612002016-09-01 10:41:03 -07001830 args.enable_slow, args.enable_asserts,
Kumar Galad5719a62016-10-26 12:30:26 -05001831 args.extra_args, args.ccache)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001832 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07001833
Daniel Leung7f850102016-04-08 11:07:32 -07001834 # figure out which report to use for size comparison
1835 if args.compare_report:
1836 report_to_use = args.compare_report
1837 elif args.last_metrics:
1838 report_to_use = LAST_SANITY
1839 else:
1840 report_to_use = RELEASE_DATA
1841
1842 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07001843 warnings = 0
1844 if deltas:
Andrew Boieea7928f2015-08-14 14:27:38 -07001845 for i, metric, value, delta, lower_better in deltas:
1846 if not args.all_deltas and ((delta < 0 and lower_better) or
1847 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07001848 continue
1849
Andrew Boieea7928f2015-08-14 14:27:38 -07001850 percentage = (float(delta) / float(value - delta))
1851 if not args.all_deltas and (percentage <
1852 (args.footprint_threshold / 100.0)):
1853 continue
1854
Daniel Leung00525c22016-04-11 10:27:56 -07001855 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07001856 i.platform.name, i.test.name, COLOR_YELLOW,
1857 "INFO" if args.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07001858 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07001859 warnings += 1
1860
1861 if warnings:
1862 info("Deltas based on metrics from last %s" %
1863 ("release" if not args.last_metrics else "run"))
1864
1865 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08001866 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001867 if goal.failed:
1868 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001869 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07001870 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
1871 (COLOR_RED, COLOR_NORMAL, goal.name,
1872 str(goal.metrics["unrecognized"])))
1873 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001874
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001875 if args.coverage:
1876 info("Generating coverage files...")
1877 generate_coverage(args.outdir, ["tests/*", "samples/*"])
1878
Andrew Boie4b182472015-07-31 12:25:22 -07001879 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Andrew Boie6acbe632015-07-17 12:03:52 -07001880 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
1881 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
Andrew Boie4b182472015-07-31 12:25:22 -07001882 warnings, COLOR_NORMAL, time.time() - start_time))
Andrew Boie6acbe632015-07-17 12:03:52 -07001883
1884 if args.testcase_report:
1885 ts.testcase_report(args.testcase_report)
1886 if not args.no_update:
1887 ts.testcase_report(LAST_SANITY)
1888 if args.release:
1889 ts.testcase_report(RELEASE_DATA)
1890
1891 if failed or (warnings and args.warnings_as_errors):
1892 sys.exit(1)
1893
1894if __name__ == "__main__":
1895 main()