blob: 0f089c8553e085f2d45cd89d1fe3d7f7754030d3 [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
Paul Sokolovskycb2ae522017-03-13 18:05:11 +0300143testcase and architecture .ini file and options passed in on the command
144line. If there is any confusion, running with -v or --discard-report
Andrew Boie6acbe632015-07-17 12:03:52 -0700145can 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
Anas Nashifb3311ed2017-04-13 14:44:48 -0400175import xml.etree.ElementTree as ET
Andrew Boie6acbe632015-07-17 12:03:52 -0700176
177if "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")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400189LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
190 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700191RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
192 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700193CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700194
195if os.isatty(sys.stdout.fileno()):
196 TERMINAL = True
197 COLOR_NORMAL = '\033[0m'
198 COLOR_RED = '\033[91m'
199 COLOR_GREEN = '\033[92m'
200 COLOR_YELLOW = '\033[93m'
201else:
202 TERMINAL = False
203 COLOR_NORMAL = ""
204 COLOR_RED = ""
205 COLOR_GREEN = ""
206 COLOR_YELLOW = ""
207
208class SanityCheckException(Exception):
209 pass
210
211class SanityRuntimeError(SanityCheckException):
212 pass
213
214class ConfigurationError(SanityCheckException):
215 def __init__(self, cfile, message):
216 self.cfile = cfile
217 self.message = message
218
219 def __str__(self):
220 return repr(self.cfile + ": " + self.message)
221
222class MakeError(SanityCheckException):
223 pass
224
225class BuildError(MakeError):
226 pass
227
228class ExecutionError(MakeError):
229 pass
230
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800231log_file = None
232
Andrew Boie6acbe632015-07-17 12:03:52 -0700233# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800234def info(what):
235 sys.stdout.write(what + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800236 if log_file:
237 log_file.write(what + "\n")
238 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700239
240def error(what):
241 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800242 if log_file:
243 log_file(what + "\n")
244 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700245
Andrew Boie08ce5a52016-02-22 13:28:10 -0800246def debug(what):
247 if VERBOSE >= 1:
248 info(what)
249
Andrew Boie6acbe632015-07-17 12:03:52 -0700250def verbose(what):
251 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800252 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700253
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300254class Handler:
255 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
256 RUN_FAILED = "PROJECT EXECUTION FAILED"
257 def __init__(self, name, outdir, log_fn, timeout, unit=False):
258 """Constructor
259
260 @param name Arbitrary name of the created thread
261 @param outdir Working directory, should be where qemu.pid gets created
262 by kbuild
263 @param log_fn Absolute path to write out QEMU's log data
264 @param timeout Kill the QEMU process if it doesn't finish up within
265 the given number of seconds
266 """
267 self.lock = threading.Lock()
268 self.state = "waiting"
269 self.metrics = {}
270 self.metrics["qemu_time"] = 0
271 self.metrics["ram_size"] = 0
272 self.metrics["rom_size"] = 0
273 self.unit = unit
274
275 def set_state(self, state, metrics):
276 self.lock.acquire()
277 self.state = state
278 self.metrics.update(metrics)
279 self.lock.release()
280
281 def get_state(self):
282 self.lock.acquire()
283 ret = (self.state, self.metrics)
284 self.lock.release()
285 return ret
286
287class UnitHandler(Handler):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300288 def __init__(self, name, sourcedir, outdir, run_log, valgrind_log, timeout):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300289 """Constructor
290
291 @param name Arbitrary name of the created thread
292 @param outdir Working directory containing the test binary
293 @param run_log Absolute path to runtime logs
294 @param valgrind Absolute path to valgrind's log
295 @param timeout Kill the QEMU process if it doesn't finish up within
296 the given number of seconds
297 """
298 super().__init__(name, outdir, run_log, timeout, True)
299
300 self.timeout = timeout
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300301 self.sourcedir = sourcedir
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300302 self.outdir = outdir
303 self.run_log = run_log
304 self.valgrind_log = valgrind_log
305 self.returncode = 0
306 self.set_state("running", {})
307
308 def handle(self):
309 out_state = "failed"
310
311 with open(self.run_log, "wt") as rl, open(self.valgrind_log, "wt") as vl:
312 try:
313 binary = os.path.join(self.outdir, "testbinary")
314 command = [binary]
315 if shutil.which("valgrind"):
316 command = ["valgrind", "--error-exitcode=2",
317 "--leak-check=full"] + command
318 returncode = subprocess.call(command, timeout=self.timeout,
319 stdout=rl, stderr=vl)
320 self.returncode = returncode
321 if returncode != 0:
322 if self.returncode == 1:
323 out_state = "failed"
324 else:
325 out_state = "failed valgrind"
326 else:
327 out_state = "passed"
328 except subprocess.TimeoutExpired:
329 out_state = "timeout"
330 self.returncode = 1
331
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300332 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
333
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300334 self.set_state(out_state, {})
335
336class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700337 """Spawns a thread to monitor QEMU output from pipes
338
339 We pass QEMU_PIPE to 'make qemu' and monitor the pipes for output.
340 We need to do this as once qemu starts, it runs forever until killed.
341 Test cases emit special messages to the console as they run, we check
342 for these to collect whether the test passed or failed.
343 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700344
345 @staticmethod
346 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results):
347 fifo_in = fifo_fn + ".in"
348 fifo_out = fifo_fn + ".out"
349
350 # These in/out nodes are named from QEMU's perspective, not ours
351 if os.path.exists(fifo_in):
352 os.unlink(fifo_in)
353 os.mkfifo(fifo_in)
354 if os.path.exists(fifo_out):
355 os.unlink(fifo_out)
356 os.mkfifo(fifo_out)
357
358 # We don't do anything with out_fp but we need to open it for
359 # writing so that QEMU doesn't block, due to the way pipes work
360 out_fp = open(fifo_in, "wb")
361 # Disable internal buffering, we don't
362 # want read() or poll() to ever block if there is data in there
363 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800364 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700365
366 start_time = time.time()
367 timeout_time = start_time + timeout
368 p = select.poll()
369 p.register(in_fp, select.POLLIN)
370
371 metrics = {}
372 line = ""
373 while True:
374 this_timeout = int((timeout_time - time.time()) * 1000)
375 if this_timeout < 0 or not p.poll(this_timeout):
376 out_state = "timeout"
377 break
378
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500379 try:
380 c = in_fp.read(1).decode("utf-8")
381 except UnicodeDecodeError:
382 # Test is writing something weird, fail
383 out_state = "unexpected byte"
384 break
385
Andrew Boie6acbe632015-07-17 12:03:52 -0700386 if c == "":
387 # EOF, this shouldn't happen unless QEMU crashes
388 out_state = "unexpected eof"
389 break
390 line = line + c
391 if c != "\n":
392 continue
393
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300394 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700395 log_out_fp.write(line)
396 log_out_fp.flush()
397 line = line.strip()
398 verbose("QEMU: %s" % line)
399
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300400 if line == handler.RUN_PASSED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700401 out_state = "passed"
402 break
403
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300404 if line == handler.RUN_FAILED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700405 out_state = "failed"
406 break
407
408 # TODO: Add support for getting numerical performance data
409 # from test cases. Will involve extending test case reporting
410 # APIs. Add whatever gets reported to the metrics dictionary
411 line = ""
412
413 metrics["qemu_time"] = time.time() - start_time
414 verbose("QEMU complete (%s) after %f seconds" %
415 (out_state, metrics["qemu_time"]))
416 handler.set_state(out_state, metrics)
417
418 log_out_fp.close()
419 out_fp.close()
420 in_fp.close()
421
422 pid = int(open(pid_fn).read())
423 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800424 try:
425 os.kill(pid, signal.SIGTERM)
426 except ProcessLookupError:
427 # Oh well, as long as it's dead! User probably sent Ctrl-C
428 pass
429
Andrew Boie6acbe632015-07-17 12:03:52 -0700430 os.unlink(fifo_in)
431 os.unlink(fifo_out)
432
Andrew Boie6acbe632015-07-17 12:03:52 -0700433 def __init__(self, name, outdir, log_fn, timeout):
434 """Constructor
435
436 @param name Arbitrary name of the created thread
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300437 @param outdir Working directory, should be where qemu.pid gets created
Andrew Boie6acbe632015-07-17 12:03:52 -0700438 by kbuild
439 @param log_fn Absolute path to write out QEMU's log data
440 @param timeout Kill the QEMU process if it doesn't finish up within
441 the given number of seconds
442 """
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300443 super().__init__(name, outdir, log_fn, timeout)
Andrew Boie6acbe632015-07-17 12:03:52 -0700444 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700445
446 # We pass this to QEMU which looks for fifos with .in and .out
447 # suffixes.
448 self.fifo_fn = os.path.join(outdir, "qemu-fifo")
449
450 self.pid_fn = os.path.join(outdir, "qemu.pid")
451 if os.path.exists(self.pid_fn):
452 os.unlink(self.pid_fn)
453
454 self.log_fn = log_fn
455 self.thread = threading.Thread(name=name, target=QEMUHandler._thread,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300456 args=(self, timeout, outdir,
457 self.log_fn, self.fifo_fn,
458 self.pid_fn, self.results))
Andrew Boie6acbe632015-07-17 12:03:52 -0700459 self.thread.daemon = True
460 verbose("Spawning QEMU process for %s" % name)
461 self.thread.start()
462
Andrew Boie6acbe632015-07-17 12:03:52 -0700463 def get_fifo(self):
464 return self.fifo_fn
465
Andrew Boie6acbe632015-07-17 12:03:52 -0700466class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700467
468 alloc_sections = ["bss", "noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800469 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500470 "_k_memory_pool", "exceptions", "initshell",
471 "_static_thread_area", "_k_timer_area",
472 "_k_mem_slab_area", "_k_mem_pool_area",
473 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
474 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
Tomasz Bursztykab56c4df2016-08-18 14:07:22 +0200475 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Tomasz Bursztykae38a9e82017-03-08 09:30:03 +0100476 "net_if", "net_if_event", "net_stack", "net_l2_data",
Anas Nashif6d72e632017-02-27 19:49:03 -0500477 "_k_queue_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700478 # These get copied into RAM only on non-XIP
Andrew Boie3b930212016-06-07 13:11:45 -0700479 ro_sections = ["text", "ctors", "init_array", "reset",
Anas Nashifbfcdfaf2016-12-15 10:02:14 -0500480 "rodata", "devconfig", "net_l2", "vector"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700481
Andrew Boie52fef672016-11-29 12:21:59 -0800482 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700483 """Constructor
484
Andrew Boiebbd670c2015-08-17 13:16:11 -0700485 @param filename Path to the output binary
486 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700487 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700488 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700489 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700490 magic = f.read(4)
491
Andrew Boie08ce5a52016-02-22 13:28:10 -0800492 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700493 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700494
495 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
496 # GREP can not be used as it returns an error if the symbol is not found.
Andrew Boiebbd670c2015-08-17 13:16:11 -0700497 is_xip_command = "nm " + filename + " | awk '/CONFIG_XIP/ { print $3 }'"
Andrew Boie8f0211d2016-03-02 20:40:29 -0800498 is_xip_output = subprocess.check_output(is_xip_command, shell=True,
499 stderr=subprocess.STDOUT).decode("utf-8").strip()
500 if is_xip_output.endswith("no symbols"):
501 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700502 self.is_xip = (len(is_xip_output) != 0)
503
Andrew Boiebbd670c2015-08-17 13:16:11 -0700504 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700505 self.sections = []
506 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700507 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800508 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700509
510 self._calculate_sizes()
511
512 def get_ram_size(self):
513 """Get the amount of RAM the application will use up on the device
514
515 @return amount of RAM, in bytes
516 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700517 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700518
519 def get_rom_size(self):
520 """Get the size of the data that this application uses on device's flash
521
522 @return amount of ROM, in bytes
523 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700524 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700525
526 def unrecognized_sections(self):
527 """Get a list of sections inside the binary that weren't recognized
528
529 @return list of unrecogized section names
530 """
531 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700532 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700533 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700534 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700535 return slist
536
537 def _calculate_sizes(self):
538 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700539 objdump_command = "objdump -h " + self.filename
Andrew Boie6acbe632015-07-17 12:03:52 -0700540 objdump_output = subprocess.check_output(objdump_command,
Andrew Boie08ce5a52016-02-22 13:28:10 -0800541 shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700542
543 for line in objdump_output:
544 words = line.split()
545
546 if (len(words) == 0): # Skip lines that are too short
547 continue
548
549 index = words[0]
550 if (not index[0].isdigit()): # Skip lines that do not start
551 continue # with a digit
552
553 name = words[1] # Skip lines with section names
554 if (name[0] == '.'): # starting with '.'
555 continue
556
Andrew Boie73b4ee62015-10-07 11:33:22 -0700557 # TODO this doesn't actually reflect the size in flash or RAM as
558 # it doesn't include linker-imposed padding between sections.
559 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700560 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700561 if size == 0:
562 continue
563
Andrew Boie73b4ee62015-10-07 11:33:22 -0700564 load_addr = int(words[4], 16)
565 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700566
567 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700568 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700569 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700570 if name in SizeCalculator.alloc_sections:
571 self.ram_size += size
572 stype = "alloc"
573 elif name in SizeCalculator.rw_sections:
574 self.ram_size += size
575 self.rom_size += size
576 stype = "rw"
577 elif name in SizeCalculator.ro_sections:
578 self.rom_size += size
579 if not self.is_xip:
580 self.ram_size += size
581 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700582 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700583 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800584 if name not in self.extra_sections:
585 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700586
Andrew Boie73b4ee62015-10-07 11:33:22 -0700587 self.sections.append({"name" : name, "load_addr" : load_addr,
588 "size" : size, "virt_addr" : virt_addr,
Andy Ross8d801a52016-10-04 11:48:21 -0700589 "type" : stype, "recognized" : recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700590
591
592class MakeGoal:
593 """Metadata class representing one of the sub-makes called by MakeGenerator
594
595 MakeGenerator returns a dictionary of these which can then be associdated
596 with TestInstances to get a complete picture of what happened during a test.
597 MakeGenerator is used for tasks outside of building tests (such as
598 defconfigs) which is why MakeGoal is a separate class from TestInstance.
599 """
600 def __init__(self, name, text, qemu, make_log, build_log, run_log,
601 qemu_log):
602 self.name = name
603 self.text = text
604 self.qemu = qemu
605 self.make_log = make_log
606 self.build_log = build_log
607 self.run_log = run_log
608 self.qemu_log = qemu_log
609 self.make_state = "waiting"
610 self.failed = False
611 self.finished = False
612 self.reason = None
613 self.metrics = {}
614
615 def get_error_log(self):
616 if self.make_state == "waiting":
617 # Shouldn't ever see this; breakage in the main Makefile itself.
618 return self.make_log
619 elif self.make_state == "building":
620 # Failure when calling the sub-make to build the code
621 return self.build_log
622 elif self.make_state == "running":
623 # Failure in sub-make for "make qemu", qemu probably failed to start
624 return self.run_log
625 elif self.make_state == "finished":
626 # QEMU finished, but timed out or otherwise wasn't successful
627 return self.qemu_log
628
629 def fail(self, reason):
630 self.failed = True
631 self.finished = True
632 self.reason = reason
633
634 def success(self):
635 self.finished = True
636
637 def __str__(self):
638 if self.finished:
639 if self.failed:
640 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
641 self.get_error_log())
642 else:
643 return "[%s] passed" % self.name
644 else:
645 return "[%s] in progress (%s)" % (self.name, self.make_state)
646
647
648class MakeGenerator:
649 """Generates a Makefile which just calls a bunch of sub-make sessions
650
651 In any given test suite we may need to build dozens if not hundreds of
652 test cases. The cleanest way to parallelize this is to just let Make
653 do the parallelization, sharing the jobserver among all the different
654 sub-make targets.
655 """
656
657 GOAL_HEADER_TMPL = """.PHONY: {goal}
658{goal}:
659"""
660
661 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Andrew Boief7a6e282017-02-02 13:04:57 -0800662\t$(MAKE) -C {directory} O={outdir} V={verb} EXTRA_CFLAGS="-Werror {cflags}" EXTRA_ASMFLAGS=-Wa,--fatal-warnings EXTRA_LDFLAGS=--fatal-warnings {args} >{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -0700663"""
664
665 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
666
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300667 re_make = re.compile("sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700668
Anas Nashife3febe92016-11-30 14:25:44 -0500669 def __init__(self, base_outdir, asserts=False, deprecations=False, ccache=0):
Andrew Boie6acbe632015-07-17 12:03:52 -0700670 """MakeGenerator constructor
671
672 @param base_outdir Intended to be the base out directory. A make.log
673 file will be created here which contains the output of the
674 top-level Make session, as well as the dynamic control Makefile
675 @param verbose If true, pass V=1 to all the sub-makes which greatly
676 increases their verbosity
677 """
678 self.goals = {}
679 if not os.path.exists(base_outdir):
680 os.makedirs(base_outdir)
681 self.logfile = os.path.join(base_outdir, "make.log")
682 self.makefile = os.path.join(base_outdir, "Makefile")
Andrew Boie55121052016-07-20 11:52:04 -0700683 self.asserts = asserts
Anas Nashife3febe92016-11-30 14:25:44 -0500684 self.deprecations = deprecations
Kumar Galad5719a62016-10-26 12:30:26 -0500685 self.ccache = ccache
Andrew Boie6acbe632015-07-17 12:03:52 -0700686
687 def _get_rule_header(self, name):
688 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
689
690 def _get_sub_make(self, name, phase, workdir, outdir, logfile, args):
691 verb = "1" if VERBOSE else "0"
692 args = " ".join(args)
Anas Nashife3febe92016-11-30 14:25:44 -0500693
Andrew Boie55121052016-07-20 11:52:04 -0700694 if self.asserts:
695 cflags="-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
696 else:
697 cflags=""
Anas Nashife3febe92016-11-30 14:25:44 -0500698
699 if self.deprecations:
700 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800701
702 if self.ccache:
703 args = args + " USE_CCACHE=1"
704
705 return MakeGenerator.MAKE_RULE_TMPL.format(phase=phase, goal=name,
Andrew Boie55121052016-07-20 11:52:04 -0700706 outdir=outdir, cflags=cflags,
Andrew Boie6acbe632015-07-17 12:03:52 -0700707 directory=workdir, verb=verb,
708 args=args, logfile=logfile)
709
710 def _get_rule_footer(self, name):
711 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
712
713 def _add_goal(self, outdir):
714 if not os.path.exists(outdir):
715 os.makedirs(outdir)
716
717 def add_build_goal(self, name, directory, outdir, args):
718 """Add a goal to invoke a Kbuild session
719
720 @param name A unique string name for this build goal. The results
721 dictionary returned by execute() will be keyed by this name.
722 @param directory Absolute path to working directory, will be passed
723 to make -C
724 @param outdir Absolute path to output directory, will be passed to
725 Kbuild via -O=<path>
726 @param args Extra command line arguments to pass to 'make', typically
727 environment variables or specific Make goals
728 """
729 self._add_goal(outdir)
730 build_logfile = os.path.join(outdir, "build.log")
731 text = (self._get_rule_header(name) +
732 self._get_sub_make(name, "building", directory,
733 outdir, build_logfile, args) +
734 self._get_rule_footer(name))
735 self.goals[name] = MakeGoal(name, text, None, self.logfile, build_logfile,
736 None, None)
737
738 def add_qemu_goal(self, name, directory, outdir, args, timeout=30):
739 """Add a goal to build a Zephyr project and then run it under QEMU
740
741 The generated make goal invokes Make twice, the first time it will
742 build the default goal, and the second will invoke the 'qemu' goal.
743 The output of the QEMU session will be monitored, and terminated
744 either upon pass/fail result of the test program, or the timeout
745 is reached.
746
747 @param name A unique string name for this build goal. The results
748 dictionary returned by execute() will be keyed by this name.
749 @param directory Absolute path to working directory, will be passed
750 to make -C
751 @param outdir Absolute path to output directory, will be passed to
752 Kbuild via -O=<path>
753 @param args Extra command line arguments to pass to 'make', typically
754 environment variables. Do not pass specific Make goals here.
755 @param timeout Maximum length of time QEMU session should be allowed
756 to run before automatically killing it. Default is 30 seconds.
757 """
758
759 self._add_goal(outdir)
760 build_logfile = os.path.join(outdir, "build.log")
761 run_logfile = os.path.join(outdir, "run.log")
762 qemu_logfile = os.path.join(outdir, "qemu.log")
763
764 q = QEMUHandler(name, outdir, qemu_logfile, timeout)
765 args.append("QEMU_PIPE=%s" % q.get_fifo())
766 text = (self._get_rule_header(name) +
767 self._get_sub_make(name, "building", directory,
768 outdir, build_logfile, args) +
769 self._get_sub_make(name, "running", directory,
770 outdir, run_logfile,
Mazen NEIFER7f046372017-01-31 12:41:33 +0100771 args + ["run"]) +
Andrew Boie6acbe632015-07-17 12:03:52 -0700772 self._get_rule_footer(name))
773 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
774 run_logfile, qemu_logfile)
775
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300776 def add_unit_goal(self, name, directory, outdir, args, timeout=30, coverage=False):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300777 self._add_goal(outdir)
778 build_logfile = os.path.join(outdir, "build.log")
779 run_logfile = os.path.join(outdir, "run.log")
780 qemu_logfile = os.path.join(outdir, "qemu.log")
781 valgrind_logfile = os.path.join(outdir, "valgrind.log")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300782 if coverage:
783 args += ["COVERAGE=1"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300784
785 # we handle running in the UnitHandler class
786 text = (self._get_rule_header(name) +
787 self._get_sub_make(name, "building", directory,
788 outdir, build_logfile, args) +
789 self._get_rule_footer(name))
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300790 q = UnitHandler(name, directory, outdir, run_logfile, valgrind_logfile, timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300791 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
792 run_logfile, valgrind_logfile)
793
Andrew Boie6acbe632015-07-17 12:03:52 -0700794
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300795 def add_test_instance(self, ti, build_only=False, enable_slow=False, coverage=False,
Andrew Boieba612002016-09-01 10:41:03 -0700796 extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -0700797 """Add a goal to build/test a TestInstance object
798
799 @param ti TestInstance object to build. The status dictionary returned
800 by execute() will be keyed by its .name field.
801 """
802 args = ti.test.extra_args[:]
803 args.extend(["ARCH=%s" % ti.platform.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -0500804 "BOARD=%s" % ti.platform.name])
Andrew Boieba612002016-09-01 10:41:03 -0700805 args.extend(extra_args)
Andrew Boie6bb087c2016-02-10 13:39:00 -0800806 if (ti.platform.qemu_support and (not ti.build_only) and
807 (not build_only) and (enable_slow or not ti.test.slow)):
Andrew Boie6acbe632015-07-17 12:03:52 -0700808 self.add_qemu_goal(ti.name, ti.test.code_location, ti.outdir,
809 args, ti.test.timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300810 elif ti.test.type == "unit":
811 self.add_unit_goal(ti.name, ti.test.code_location, ti.outdir,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300812 args, ti.test.timeout, coverage)
Andrew Boie6acbe632015-07-17 12:03:52 -0700813 else:
814 self.add_build_goal(ti.name, ti.test.code_location, ti.outdir, args)
815
816 def execute(self, callback_fn=None, context=None):
817 """Execute all the registered build goals
818
819 @param callback_fn If not None, a callback function will be called
820 as individual goals transition between states. This function
821 should accept two parameters: a string state and an arbitrary
822 context object, supplied here
823 @param context Context object to pass to the callback function.
824 Type and semantics are specific to that callback function.
825 @return A dictionary mapping goal names to final status.
826 """
827
Andrew Boie08ce5a52016-02-22 13:28:10 -0800828 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -0700829 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -0800830 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -0700831 # Create our dynamic Makefile and execute it.
832 # Watch stderr output which is where we will keep
833 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -0800834 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -0700835 tf.write(goal.text)
836 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
837 tf.flush()
838
Daniel Leung6b170072016-04-07 12:10:25 -0700839 cmd = ["make", "-k", "-j", str(CPU_COUNTS * 2), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700840 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
841 stdout=devnull)
842
843 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -0800844 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -0700845 make_log.write(line)
846 verbose("MAKE: " + repr(line.strip()))
847 m = MakeGenerator.re_make.match(line)
848 if not m:
849 continue
850
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300851 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -0700852 if error:
853 goal = self.goals[error]
854 else:
855 goal = self.goals[name]
856 goal.make_state = state
857
858
859 if error:
Andrew Boie822b0872017-01-10 13:32:40 -0800860 # Sometimes QEMU will run an image and then crash out, which
861 # will cause the 'make qemu' invocation to exit with
862 # nonzero status.
863 # Need to distinguish this case from a compilation failure.
864 if goal.qemu:
865 goal.fail("qemu_crash")
866 else:
867 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -0700868 else:
869 if state == "finished":
870 if goal.qemu:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300871 if goal.qemu.unit:
872 # We can't run unit tests with Make
873 goal.qemu.handle()
874 if goal.qemu.returncode == 2:
875 goal.qemu_log = goal.qemu.valgrind_log
876 elif goal.qemu.returncode:
877 goal.qemu_log = goal.qemu.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700878 thread_status, metrics = goal.qemu.get_state()
879 goal.metrics.update(metrics)
880 if thread_status == "passed":
881 goal.success()
882 else:
883 goal.fail(thread_status)
884 else:
885 goal.success()
886
887 if callback_fn:
888 callback_fn(context, self.goals, goal)
889
890 p.wait()
891 return self.goals
892
893
894# "list" - List of strings
895# "list:<type>" - List of <type>
896# "set" - Set of unordered, unique strings
897# "set:<type>" - Set of <type>
898# "float" - Floating point
899# "int" - Integer
900# "bool" - Boolean
901# "str" - String
902
903# XXX Be sure to update __doc__ if you change any of this!!
904
905arch_valid_keys = {"name" : {"type" : "str", "required" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500906 "platforms" : {"type" : "list", "required" : True},
907 "supported_toolchains" : {"type" : "list", "required" : True}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700908
909platform_valid_keys = {"qemu_support" : {"type" : "bool", "default" : False},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500910 "supported_toolchains" : {"type" : "list", "default" : []}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700911
912testcase_valid_keys = {"tags" : {"type" : "set", "required" : True},
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300913 "type" : {"type" : "str", "default": "integration"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700914 "extra_args" : {"type" : "list"},
915 "build_only" : {"type" : "bool", "default" : False},
Anas Nashif2bd99bc2015-10-12 13:10:57 -0400916 "skip" : {"type" : "bool", "default" : False},
Andrew Boie6bb087c2016-02-10 13:39:00 -0800917 "slow" : {"type" : "bool", "default" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700918 "timeout" : {"type" : "int", "default" : 60},
919 "arch_whitelist" : {"type" : "set"},
Anas Nashif30d13872015-10-05 10:02:45 -0400920 "arch_exclude" : {"type" : "set"},
Andrew Boie52fef672016-11-29 12:21:59 -0800921 "extra_sections" : {"type" : "list", "default" : []},
Anas Nashif30d13872015-10-05 10:02:45 -0400922 "platform_exclude" : {"type" : "set"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700923 "platform_whitelist" : {"type" : "set"},
Andrew Boie3ea78922016-03-24 14:46:00 -0700924 "filter" : {"type" : "str"}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700925
926
927class SanityConfigParser:
928 """Class to read architecture and test case .ini files with semantic checking
929 """
930 def __init__(self, filename):
931 """Instantiate a new SanityConfigParser object
932
933 @param filename Source .ini file to read
934 """
Andrew Boie08ce5a52016-02-22 13:28:10 -0800935 cp = configparser.SafeConfigParser()
Andrew Boie6acbe632015-07-17 12:03:52 -0700936 cp.readfp(open(filename))
937 self.filename = filename
938 self.cp = cp
939
940 def _cast_value(self, value, typestr):
941 v = value.strip()
942 if typestr == "str":
943 return v
944
945 elif typestr == "float":
946 return float(v)
947
948 elif typestr == "int":
949 return int(v)
950
951 elif typestr == "bool":
952 v = v.lower()
953 if v == "true" or v == "1":
954 return True
955 elif v == "" or v == "false" or v == "0":
956 return False
957 raise ConfigurationError(self.filename,
958 "bad value for boolean: '%s'" % value)
959
960 elif typestr.startswith("list"):
961 vs = v.split()
962 if len(typestr) > 4 and typestr[4] == ":":
963 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
964 else:
965 return vs
966
967 elif typestr.startswith("set"):
968 vs = v.split()
969 if len(typestr) > 3 and typestr[3] == ":":
970 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
971 else:
972 return set(vs)
973
974 else:
975 raise ConfigurationError(self.filename, "unknown type '%s'" % value)
976
977
978 def sections(self):
979 """Get the set of sections within the .ini file
980
981 @return a list of string section names"""
982 return self.cp.sections()
983
984 def get_section(self, section, valid_keys):
985 """Get a dictionary representing the keys/values within a section
986
987 @param section The section in the .ini file to retrieve data from
988 @param valid_keys A dictionary representing the intended semantics
989 for this section. Each key in this dictionary is a key that could
990 be specified, if a key is given in the .ini file which isn't in
991 here, it will generate an error. Each value in this dictionary
992 is another dictionary containing metadata:
993
994 "default" - Default value if not given
995 "type" - Data type to convert the text value to. Simple types
996 supported are "str", "float", "int", "bool" which will get
997 converted to respective Python data types. "set" and "list"
998 may also be specified which will split the value by
999 whitespace (but keep the elements as strings). finally,
1000 "list:<type>" and "set:<type>" may be given which will
1001 perform a type conversion after splitting the value up.
1002 "required" - If true, raise an error if not defined. If false
1003 and "default" isn't specified, a type conversion will be
1004 done on an empty string
1005 @return A dictionary containing the section key-value pairs with
1006 type conversion and default values filled in per valid_keys
1007 """
1008
1009 d = {}
1010 cp = self.cp
1011
1012 if not cp.has_section(section):
Andrew Boiee57a1e52016-03-22 10:29:14 -07001013 # Just fill it with defaults
1014 cp.add_section(section)
Andrew Boie6acbe632015-07-17 12:03:52 -07001015
1016 for k, v in cp.items(section):
1017 if k not in valid_keys:
1018 raise ConfigurationError(self.filename,
1019 "Unknown config key '%s' in defintiion for '%s'"
1020 % (k, section))
1021 d[k] = v
1022
Andrew Boie08ce5a52016-02-22 13:28:10 -08001023 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001024 if k not in d:
1025 if "required" in kinfo:
1026 required = kinfo["required"]
1027 else:
1028 required = False
1029
1030 if required:
1031 raise ConfigurationError(self.filename,
1032 "missing required value for '%s' in section '%s'"
1033 % (k, section))
1034 else:
1035 if "default" in kinfo:
1036 default = kinfo["default"]
1037 else:
1038 default = self._cast_value("", kinfo["type"])
1039 d[k] = default
1040 else:
1041 try:
1042 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001043 except ValueError as ve:
Andrew Boie6acbe632015-07-17 12:03:52 -07001044 raise ConfigurationError(self.filename,
1045 "bad %s value '%s' for key '%s' in section '%s'"
1046 % (kinfo["type"], d[k], k, section))
1047
1048 return d
1049
1050
1051class Platform:
1052 """Class representing metadata for a particular platform
1053
Anas Nashifc7406082015-12-13 15:00:31 -05001054 Maps directly to BOARD when building"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001055 def __init__(self, arch, name, plat_dict):
1056 """Constructor.
1057
1058 @param arch Architecture object for this platform
Anas Nashifc7406082015-12-13 15:00:31 -05001059 @param name String name for this platform, same as BOARD
Andrew Boie6acbe632015-07-17 12:03:52 -07001060 @param plat_dict SanityConfigParser output on the relevant section
1061 in the architecture configuration file which has lots of metadata.
1062 See the Architecture class.
1063 """
1064 self.name = name
1065 self.qemu_support = plat_dict["qemu_support"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001066 self.arch = arch
Javier B Perez4b554ba2016-08-15 13:25:33 -05001067 self.supported_toolchains = arch.supported_toolchains
1068 if plat_dict["supported_toolchains"]:
1069 self.supported_toolchains = plat_dict["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001070 # Gets populated in a separate step
Andrew Boie41878222016-11-03 11:58:53 -07001071 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001072 pass
1073
Andrew Boie6acbe632015-07-17 12:03:52 -07001074 def __repr__(self):
1075 return "<%s on %s>" % (self.name, self.arch.name)
1076
1077
1078class Architecture:
1079 """Class representing metadata for a particular architecture
1080 """
1081 def __init__(self, cfile):
1082 """Architecture constructor
1083
1084 @param cfile Path to Architecture configuration file, which gives
1085 info about the arch and all the platforms for it
1086 """
1087 cp = SanityConfigParser(cfile)
1088 self.platforms = []
1089
1090 arch = cp.get_section("arch", arch_valid_keys)
1091
1092 self.name = arch["name"]
Javier B Perez4b554ba2016-08-15 13:25:33 -05001093 self.supported_toolchains = arch["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001094
1095 for plat_name in arch["platforms"]:
1096 verbose("Platform: %s" % plat_name)
1097 plat_dict = cp.get_section(plat_name, platform_valid_keys)
1098 self.platforms.append(Platform(self, plat_name, plat_dict))
1099
1100 def __repr__(self):
1101 return "<arch %s>" % self.name
1102
1103
1104class TestCase:
1105 """Class representing a test application
1106 """
Andrew Boie3ea78922016-03-24 14:46:00 -07001107 def __init__(self, testcase_root, workdir, name, tc_dict, inifile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001108 """TestCase constructor.
1109
1110 This gets called by TestSuite as it finds and reads testcase.ini files.
1111 Multiple TestCase instances may be generated from a single testcase.ini,
1112 each one corresponds to a section within that file.
1113
Andrew Boie6acbe632015-07-17 12:03:52 -07001114 We need to have a unique name for every single test case. Since
1115 a testcase.ini can define multiple tests, the canonical name for
1116 the test case is <workdir>/<name>.
1117
1118 @param testcase_root Absolute path to the root directory where
1119 all the test cases live
1120 @param workdir Relative path to the project directory for this
1121 test application from the test_case root.
1122 @param name Name of this test case, corresponding to the section name
1123 in the test case configuration file. For many test cases that just
1124 define one test, can be anything and is usually "test". This is
1125 really only used to distinguish between different cases when
1126 the testcase.ini defines multiple tests
1127 @param tc_dict Dictionary with section values for this test case
1128 from the testcase.ini file
1129 """
1130 self.code_location = os.path.join(testcase_root, workdir)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001131 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001132 self.tags = tc_dict["tags"]
1133 self.extra_args = tc_dict["extra_args"]
1134 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001135 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001136 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001137 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001138 self.platform_whitelist = tc_dict["platform_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001139 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001140 self.timeout = tc_dict["timeout"]
1141 self.build_only = tc_dict["build_only"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001142 self.slow = tc_dict["slow"]
Andrew Boie52fef672016-11-29 12:21:59 -08001143 self.extra_sections = tc_dict["extra_sections"]
Andrew Boie14ac9022016-04-08 13:31:53 -07001144 self.path = os.path.join(os.path.basename(os.path.abspath(testcase_root)),
1145 workdir, name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001146 self.name = self.path # for now
Anas Nashif2cf0df02015-10-10 09:29:43 -04001147 self.defconfig = {}
Andrew Boie3ea78922016-03-24 14:46:00 -07001148 self.inifile = inifile
Andrew Boie6acbe632015-07-17 12:03:52 -07001149
Andrew Boie6acbe632015-07-17 12:03:52 -07001150 def __repr__(self):
1151 return self.name
1152
1153
1154
1155class TestInstance:
1156 """Class representing the execution of a particular TestCase on a platform
1157
1158 @param test The TestCase object we want to build/execute
1159 @param platform Platform object that we want to build and run against
1160 @param base_outdir Base directory for all test results. The actual
1161 out directory used is <outdir>/<platform>/<test case name>
1162 """
Andrew Boie6bb087c2016-02-10 13:39:00 -08001163 def __init__(self, test, platform, base_outdir, build_only=False,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001164 slow=False, coverage=False):
Andrew Boie6acbe632015-07-17 12:03:52 -07001165 self.test = test
1166 self.platform = platform
1167 self.name = os.path.join(platform.name, test.path)
1168 self.outdir = os.path.join(base_outdir, platform.name, test.path)
1169 self.build_only = build_only or test.build_only
1170
1171 def calculate_sizes(self):
1172 """Get the RAM/ROM sizes of a test case.
1173
1174 This can only be run after the instance has been executed by
1175 MakeGenerator, otherwise there won't be any binaries to measure.
1176
1177 @return A SizeCalculator object
1178 """
Andrew Boie5d4eb782015-10-02 10:04:56 -07001179 fns = glob.glob(os.path.join(self.outdir, "*.elf"))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001180 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001181 if (len(fns) != 1):
1182 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001183 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001184
1185 def __repr__(self):
1186 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1187
1188
Andrew Boie4ef16c52015-08-28 12:36:03 -07001189def defconfig_cb(context, goals, goal):
1190 if not goal.failed:
1191 return
1192
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001193
Andrew Boie4ef16c52015-08-28 12:36:03 -07001194 info("%sCould not build defconfig for %s%s" %
1195 (COLOR_RED, goal.name, COLOR_NORMAL));
1196 if INLINE_LOGS:
1197 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001198 data = fp.read()
1199 sys.stdout.write(data)
1200 if log_file:
1201 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001202 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001203 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001204
Andrew Boie6acbe632015-07-17 12:03:52 -07001205
1206class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001207 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001208
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001209 def __init__(self, arch_root, testcase_roots, outdir, coverage):
Andrew Boie6acbe632015-07-17 12:03:52 -07001210 # Keep track of which test cases we've filtered out and why
1211 discards = {}
1212 self.arches = {}
1213 self.testcases = {}
1214 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001215 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001216 self.instances = {}
1217 self.goals = None
1218 self.discards = None
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001219 self.coverage = coverage
Andrew Boie6acbe632015-07-17 12:03:52 -07001220
1221 arch_root = os.path.abspath(arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001222
Andrew Boie3d348712016-04-08 11:52:13 -07001223 for testcase_root in testcase_roots:
1224 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001225
Andrew Boie3d348712016-04-08 11:52:13 -07001226 debug("Reading test case configuration files under %s..." %
1227 testcase_root)
1228 for dirpath, dirnames, filenames in os.walk(testcase_root,
1229 topdown=True):
1230 verbose("scanning %s" % dirpath)
1231 if "testcase.ini" in filenames:
1232 verbose("Found test case in " + dirpath)
1233 dirnames[:] = []
Andrew Boie3ea78922016-03-24 14:46:00 -07001234 ini_path = os.path.join(dirpath, "testcase.ini")
1235 cp = SanityConfigParser(ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001236 workdir = os.path.relpath(dirpath, testcase_root)
1237
1238 for section in cp.sections():
1239 tc_dict = cp.get_section(section, testcase_valid_keys)
Andrew Boie3ea78922016-03-24 14:46:00 -07001240 tc = TestCase(testcase_root, workdir, section, tc_dict,
1241 ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001242 self.testcases[tc.name] = tc
Andrew Boie6acbe632015-07-17 12:03:52 -07001243
1244 debug("Reading architecture configuration files under %s..." % arch_root)
1245 for dirpath, dirnames, filenames in os.walk(arch_root):
1246 for filename in filenames:
1247 if filename.endswith(".ini"):
1248 fn = os.path.join(dirpath, filename)
1249 verbose("Found arch configuration " + fn)
1250 arch = Architecture(fn)
1251 self.arches[arch.name] = arch
1252 self.platforms.extend(arch.platforms)
1253
Andrew Boie05e3d7f2016-08-09 11:29:34 -07001254 # Build up a list of boards based on the presence of
1255 # boards/*/*_defconfig files. We want to make sure that the arch.ini
1256 # files are not missing any boards
1257 all_plats = [plat.name for plat in self.platforms]
1258 for dirpath, dirnames, filenames in os.walk(os.path.join(ZEPHYR_BASE,
1259 "boards")):
1260 for filename in filenames:
1261 if filename.endswith("_defconfig"):
1262 board_name = filename.replace("_defconfig", "")
1263 if board_name not in all_plats:
1264 error("Platform '%s' not specified in any arch .ini file and will not be tested"
1265 % board_name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001266 self.instances = {}
1267
1268 def get_last_failed(self):
1269 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001270 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001271 result = []
1272 with open(LAST_SANITY, "r") as fp:
1273 cr = csv.DictReader(fp)
1274 for row in cr:
1275 if row["passed"] == "True":
1276 continue
1277 test = row["test"]
1278 platform = row["platform"]
1279 result.append((test, platform))
1280 return result
1281
Anas Nashifdfa86e22016-10-24 17:08:56 -04001282 def apply_filters(self, platform_filter, arch_filter, tag_filter, exclude_tag,
Andrew Boie821d8322016-03-22 10:08:35 -07001283 config_filter, testcase_filter, last_failed, all_plats,
Kumar Galad5719a62016-10-26 12:30:26 -05001284 platform_limit, toolchain, extra_args, enable_ccache):
Andrew Boie6acbe632015-07-17 12:03:52 -07001285 instances = []
1286 discards = {}
1287 verbose("platform filter: " + str(platform_filter))
1288 verbose(" arch_filter: " + str(arch_filter))
1289 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001290 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001291 verbose(" config_filter: " + str(config_filter))
Kumar Galad5719a62016-10-26 12:30:26 -05001292 verbose(" enable_ccache: " + str(enable_ccache))
Andrew Boie6acbe632015-07-17 12:03:52 -07001293
1294 if last_failed:
1295 failed_tests = self.get_last_failed()
1296
Andrew Boie821d8322016-03-22 10:08:35 -07001297 default_platforms = False
1298
1299 if all_plats:
1300 info("Selecting all possible platforms per test case")
1301 # When --all used, any --platform arguments ignored
1302 platform_filter = []
1303 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001304 info("Selecting default platforms per test case")
1305 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001306
Kumar Galad5719a62016-10-26 12:30:26 -05001307 mg = MakeGenerator(self.outdir, ccache=enable_ccache)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001308 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001309 for tc_name, tc in self.testcases.items():
1310 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001311 instance_list = []
1312 for plat in arch.platforms:
1313 instance = TestInstance(tc, plat, self.outdir)
1314
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001315 if (arch_name == "unit") != (tc.type == "unit"):
1316 continue
1317
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001318 if tc.skip:
1319 continue
1320
Anas Nashif2cf0df02015-10-10 09:29:43 -04001321 if tag_filter and not tc.tags.intersection(tag_filter):
1322 continue
1323
Anas Nashifdfa86e22016-10-24 17:08:56 -04001324 if exclude_tag and tc.tags.intersection(exclude_tag):
1325 continue
1326
Anas Nashif2cf0df02015-10-10 09:29:43 -04001327 if testcase_filter and tc_name not in testcase_filter:
1328 continue
1329
1330 if last_failed and (tc.name, plat.name) not in failed_tests:
1331 continue
1332
1333 if arch_filter and arch_name not in arch_filter:
1334 continue
1335
1336 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1337 continue
1338
1339 if tc.arch_exclude and arch.name in tc.arch_exclude:
1340 continue
1341
1342 if tc.platform_exclude and plat.name in tc.platform_exclude:
1343 continue
1344
1345 if platform_filter and plat.name not in platform_filter:
1346 continue
1347
1348 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1349 continue
1350
Anas Nashif6d4ff232016-12-21 13:05:28 -05001351 if tc.tc_filter and (plat in arch.platforms[:platform_limit] or all_plats or platform_filter):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001352 args = tc.extra_args[:]
1353 args.extend(["ARCH=" + plat.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -05001354 "BOARD=" + plat.name, "initconfig"])
Andrew Boieba612002016-09-01 10:41:03 -07001355 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001356 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001357 # conf, gen_idt, etc aren't rebuilt for every combination,
Anas Nashif2cf0df02015-10-10 09:29:43 -04001358 # need a way to avoid different Make processe from clobbering
1359 # each other since they all try to build them simultaneously
1360
1361 o = os.path.join(self.outdir, plat.name, tc.path)
Andrew Boie41878222016-11-03 11:58:53 -07001362 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o,".config")
1363 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "initconfig"])
Anas Nashif2cf0df02015-10-10 09:29:43 -04001364 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location), o, args)
1365
1366 info("Building testcase defconfigs...")
1367 results = mg.execute(defconfig_cb)
1368
Andrew Boie08ce5a52016-02-22 13:28:10 -08001369 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001370 if goal.failed:
1371 raise SanityRuntimeError("Couldn't build some defconfigs")
1372
Andrew Boie08ce5a52016-02-22 13:28:10 -08001373 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001374 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001375 defconfig = {}
1376 with open(out_config, "r") as fp:
1377 for line in fp.readlines():
1378 m = TestSuite.config_re.match(line)
1379 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001380 if line.strip() and not line.startswith("#"):
1381 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001382 continue
1383 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001384 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001385
Andrew Boie08ce5a52016-02-22 13:28:10 -08001386 for tc_name, tc in self.testcases.items():
1387 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001388 instance_list = []
1389 for plat in arch.platforms:
1390 instance = TestInstance(tc, plat, self.outdir)
1391
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001392 if (arch_name == "unit") != (tc.type == "unit"):
1393 # Discard silently
1394 continue
1395
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001396 if tc.skip:
1397 discards[instance] = "Skip filter"
1398 continue
1399
Andrew Boie6acbe632015-07-17 12:03:52 -07001400 if tag_filter and not tc.tags.intersection(tag_filter):
1401 discards[instance] = "Command line testcase tag filter"
1402 continue
1403
Anas Nashifdfa86e22016-10-24 17:08:56 -04001404 if exclude_tag and tc.tags.intersection(exclude_tag):
1405 discards[instance] = "Command line testcase exclude filter"
1406 continue
1407
Andrew Boie6acbe632015-07-17 12:03:52 -07001408 if testcase_filter and tc_name not in testcase_filter:
1409 discards[instance] = "Testcase name filter"
1410 continue
1411
1412 if last_failed and (tc.name, plat.name) not in failed_tests:
1413 discards[instance] = "Passed or skipped during last run"
1414 continue
1415
1416 if arch_filter and arch_name not in arch_filter:
1417 discards[instance] = "Command line testcase arch filter"
1418 continue
1419
1420 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1421 discards[instance] = "Not in test case arch whitelist"
1422 continue
1423
Anas Nashif30d13872015-10-05 10:02:45 -04001424 if tc.arch_exclude and arch.name in tc.arch_exclude:
1425 discards[instance] = "In test case arch exclude"
1426 continue
1427
1428 if tc.platform_exclude and plat.name in tc.platform_exclude:
1429 discards[instance] = "In test case platform exclude"
1430 continue
1431
Andrew Boie6acbe632015-07-17 12:03:52 -07001432 if platform_filter and plat.name not in platform_filter:
1433 discards[instance] = "Command line platform filter"
1434 continue
1435
1436 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1437 discards[instance] = "Not in testcase platform whitelist"
1438 continue
1439
Javier B Perez4b554ba2016-08-15 13:25:33 -05001440 if toolchain and toolchain not in plat.supported_toolchains:
1441 discards[instance] = "Not supported by the toolchain"
1442 continue
1443
Andrew Boie3ea78922016-03-24 14:46:00 -07001444 defconfig = {"ARCH" : arch.name, "PLATFORM" : plat.name}
Javier B Perez79414542016-08-08 12:24:59 -05001445 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001446 for p, tdefconfig in tc.defconfig.items():
1447 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001448 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001449 break
1450
Andrew Boie3ea78922016-03-24 14:46:00 -07001451 if tc.tc_filter:
1452 try:
1453 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07001454 except (ValueError, SyntaxError) as se:
Andrew Boie3ea78922016-03-24 14:46:00 -07001455 sys.stderr.write("Failed processing %s\n" % tc.inifile)
1456 raise se
1457 if not res:
1458 discards[instance] = ("defconfig doesn't satisfy expression '%s'" %
1459 tc.tc_filter)
1460 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001461
Andrew Boie6acbe632015-07-17 12:03:52 -07001462 instance_list.append(instance)
1463
1464 if not instance_list:
1465 # Every platform in this arch was rejected already
1466 continue
1467
1468 if default_platforms:
Andrew Boie821d8322016-03-22 10:08:35 -07001469 self.add_instances(instance_list[:platform_limit])
1470 for instance in instance_list[platform_limit:]:
1471 discards[instance] = "Not in first %d platform(s) for arch" % platform_limit
Andrew Boie6acbe632015-07-17 12:03:52 -07001472 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001473 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001474 self.discards = discards
1475 return discards
1476
Andrew Boie821d8322016-03-22 10:08:35 -07001477 def add_instances(self, ti_list):
1478 for ti in ti_list:
1479 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001480
Anas Nashife3febe92016-11-30 14:25:44 -05001481 def execute(self, cb, cb_context, build_only, enable_slow, enable_asserts, enable_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001482 extra_args, enable_ccache):
Daniel Leung6b170072016-04-07 12:10:25 -07001483
1484 def calc_one_elf_size(name, goal):
1485 if not goal.failed:
1486 i = self.instances[name]
1487 sc = i.calculate_sizes()
1488 goal.metrics["ram_size"] = sc.get_ram_size()
1489 goal.metrics["rom_size"] = sc.get_rom_size()
1490 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07001491
Anas Nashife3febe92016-11-30 14:25:44 -05001492 mg = MakeGenerator(self.outdir, asserts=enable_asserts, deprecations=enable_deprecations,
1493 ccache=enable_ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001494 for i in self.instances.values():
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001495 mg.add_test_instance(i, build_only, enable_slow, self.coverage, extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001496 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07001497
1498 # Parallelize size calculation
1499 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
1500 futures = [executor.submit(calc_one_elf_size, name, goal) \
1501 for name, goal in self.goals.items()]
1502 concurrent.futures.wait(futures)
1503
Andrew Boie6acbe632015-07-17 12:03:52 -07001504 return self.goals
1505
1506 def discard_report(self, filename):
1507 if self.discards == None:
1508 raise SanityRuntimeException("apply_filters() hasn't been run!")
1509
1510 with open(filename, "wb") as csvfile:
1511 fieldnames = ["test", "arch", "platform", "reason"]
1512 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1513 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001514 for instance, reason in self.discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001515 rowdict = {"test" : i.test.name,
1516 "arch" : i.platform.arch.name,
1517 "platform" : i.platform.name,
1518 "reason" : reason}
1519 cw.writerow(rowdict)
1520
1521 def compare_metrics(self, filename):
1522 # name, datatype, lower results better
1523 interesting_metrics = [("ram_size", int, True),
1524 ("rom_size", int, True)]
1525
1526 if self.goals == None:
1527 raise SanityRuntimeException("execute() hasn't been run!")
1528
1529 if not os.path.exists(filename):
1530 info("Cannot compare metrics, %s not found" % filename)
1531 return []
1532
1533 results = []
1534 saved_metrics = {}
1535 with open(filename) as fp:
1536 cr = csv.DictReader(fp)
1537 for row in cr:
1538 d = {}
1539 for m, _, _ in interesting_metrics:
1540 d[m] = row[m]
1541 saved_metrics[(row["test"], row["platform"])] = d
1542
Andrew Boie08ce5a52016-02-22 13:28:10 -08001543 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001544 i = self.instances[name]
1545 mkey = (i.test.name, i.platform.name)
1546 if mkey not in saved_metrics:
1547 continue
1548 sm = saved_metrics[mkey]
1549 for metric, mtype, lower_better in interesting_metrics:
1550 if metric not in goal.metrics:
1551 continue
1552 if sm[metric] == "":
1553 continue
1554 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07001555 if delta == 0:
1556 continue
1557 results.append((i, metric, goal.metrics[metric], delta,
1558 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07001559 return results
1560
Anas Nashifb3311ed2017-04-13 14:44:48 -04001561 def testcase_xunit_report(self, filename, args):
1562 if self.goals == None:
1563 raise SanityRuntimeException("execute() hasn't been run!")
1564
1565 fails = 0
1566 passes = 0
1567 errors = 0
1568
1569 for name, goal in self.goals.items():
1570 if goal.failed:
1571 if goal.reason in ['build_error', 'qemu_crash']:
1572 errors += 1
1573 else:
1574 fails += 1
1575 else:
1576 passes += 1
1577
1578 run = "Sanitycheck"
1579 eleTestsuite = None
1580 append = args.only_failed
1581
1582 if os.path.exists(filename) or append:
1583 tree = ET.parse(filename)
1584 eleTestsuites = tree.getroot()
1585 eleTestsuite = tree.findall('testsuite')[0];
1586 else:
1587 eleTestsuites = ET.Element('testsuites')
1588 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite', name=run,
1589 tests="%d" %(errors + passes + fails), failures="%d" %fails, errors="%d" %errors, skip="0")
1590
1591 qemu_time = "0"
1592 for name, goal in self.goals.items():
1593
1594 i = self.instances[name]
1595 if append:
1596 for tc in eleTestsuite.findall('testcase'):
1597 if tc.get('name') == "%s:%s" %(i.platform.name, i.test.name):
1598 eleTestsuite.remove(tc)
1599
1600 if not goal.failed and goal.qemu:
1601 qemu_time = "%s" %(goal.metrics["qemu_time"])
1602
1603 eleTestcase = ET.SubElement(eleTestsuite, 'testcase', name="%s:%s" %(i.platform.name, i.test.name), time=qemu_time)
1604 if goal.failed:
1605 failure = ET.SubElement(eleTestcase, 'failure', type="failure", message=goal.reason)
1606 p = ("%s/%s/%s" %(args.outdir, i.platform.name, i.test.name))
1607 bl = os.path.join(p, "build.log")
1608 if os.path.exists(bl):
1609 with open(bl, "r") as f:
1610 log = f.read()
1611 failure.text = (str(log))
1612
1613 result = ET.tostring(eleTestsuites)
1614 f = open(filename, 'wb')
1615 f.write(result)
1616 f.close()
1617
Andrew Boie6acbe632015-07-17 12:03:52 -07001618 def testcase_report(self, filename):
1619 if self.goals == None:
1620 raise SanityRuntimeException("execute() hasn't been run!")
1621
Andrew Boie08ce5a52016-02-22 13:28:10 -08001622 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07001623 fieldnames = ["test", "arch", "platform", "passed", "status",
1624 "extra_args", "qemu", "qemu_time", "ram_size",
1625 "rom_size"]
1626 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1627 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001628 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001629 i = self.instances[name]
1630 rowdict = {"test" : i.test.name,
1631 "arch" : i.platform.arch.name,
1632 "platform" : i.platform.name,
1633 "extra_args" : " ".join(i.test.extra_args),
1634 "qemu" : i.platform.qemu_support}
1635 if goal.failed:
1636 rowdict["passed"] = False
1637 rowdict["status"] = goal.reason
1638 else:
1639 rowdict["passed"] = True
1640 if goal.qemu:
1641 rowdict["qemu_time"] = goal.metrics["qemu_time"]
1642 rowdict["ram_size"] = goal.metrics["ram_size"]
1643 rowdict["rom_size"] = goal.metrics["rom_size"]
1644 cw.writerow(rowdict)
1645
1646
1647def parse_arguments():
1648
1649 parser = argparse.ArgumentParser(description = __doc__,
1650 formatter_class = argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05001651 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07001652
1653 parser.add_argument("-p", "--platform", action="append",
Andrew Boie821d8322016-03-22 10:08:35 -07001654 help="Platform filter for testing. This option may be used multiple "
1655 "times. Testcases will only be built/run on the platforms "
1656 "specified. If this option is not used, then N platforms will "
1657 "automatically be chosen from each arch to build and test, "
1658 "where N is provided by the --platform-limit option.")
1659 parser.add_argument("-L", "--platform-limit", action="store", type=int,
1660 metavar="N", default=1,
1661 help="Controls what platforms are tested if --platform or "
1662 "--all are not used. For each architecture specified by "
1663 "--arch (defaults to all of them), choose the first "
1664 "N platforms to test in the arch-specific .ini file "
1665 "'platforms' list. Defaults to 1.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001666 parser.add_argument("-a", "--arch", action="append",
1667 help="Arch filter for testing. Takes precedence over --platform. "
1668 "If unspecified, test all arches. Multiple invocations "
1669 "are treated as a logical 'or' relationship")
1670 parser.add_argument("-t", "--tag", action="append",
1671 help="Specify tags to restrict which tests to run by tag value. "
1672 "Default is to not do any tag filtering. Multiple invocations "
1673 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04001674 parser.add_argument("-e", "--exclude-tag", action="append",
1675 help="Specify tags of tests that should not run."
1676 "Default is to run all tests with all tags.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001677 parser.add_argument("-f", "--only-failed", action="store_true",
1678 help="Run only those tests that failed the previous sanity check "
1679 "invocation.")
1680 parser.add_argument("-c", "--config", action="append",
1681 help="Specify platform configuration values filtering. This can be "
1682 "specified two ways: <config>=<value> or just <config>. The "
Andrew Boie41878222016-11-03 11:58:53 -07001683 "defconfig for all platforms will be "
Andrew Boie6acbe632015-07-17 12:03:52 -07001684 "checked. For the <config>=<value> case, only match defconfig "
1685 "that have that value defined. For the <config> case, match "
1686 "defconfig that have that value assigned to any value. "
1687 "Prepend a '!' to invert the match.")
1688 parser.add_argument("-s", "--test", action="append",
1689 help="Run only the specified test cases. These are named by "
1690 "<path to test project relative to "
1691 "--testcase-root>/<testcase.ini section name>")
1692 parser.add_argument("-l", "--all", action="store_true",
Andrew Boie821d8322016-03-22 10:08:35 -07001693 help="Build/test on all platforms. Any --platform arguments "
1694 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001695
1696 parser.add_argument("-o", "--testcase-report",
1697 help="Output a CSV spreadsheet containing results of the test run")
1698 parser.add_argument("-d", "--discard-report",
1699 help="Output a CSV spreadhseet showing tests that were skipped "
1700 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07001701 parser.add_argument("--compare-report",
1702 help="Use this report file for size comparision")
1703
Kumar Galad5719a62016-10-26 12:30:26 -05001704 parser.add_argument("--ccache", action="store_const", const=1, default=0,
1705 help="Enable the use of ccache when building")
1706
Andrew Boie6acbe632015-07-17 12:03:52 -07001707 parser.add_argument("-y", "--dry-run", action="store_true",
1708 help="Create the filtered list of test cases, but don't actually "
1709 "run them. Useful if you're just interested in "
1710 "--discard-report")
1711
1712 parser.add_argument("-r", "--release", action="store_true",
1713 help="Update the benchmark database with the results of this test "
1714 "run. Intended to be run by CI when tagging an official "
1715 "release. This database is used as a basis for comparison "
1716 "when looking for deltas in metrics such as footprint")
1717 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
1718 help="Treat warning conditions as errors")
1719 parser.add_argument("-v", "--verbose", action="count", default=0,
1720 help="Emit debugging information, call multiple times to increase "
1721 "verbosity")
1722 parser.add_argument("-i", "--inline-logs", action="store_true",
1723 help="Upon test failure, print relevant log data to stdout "
1724 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001725 parser.add_argument("--log-file", metavar="FILENAME", action="store",
1726 help="log also to file")
Andrew Boie6acbe632015-07-17 12:03:52 -07001727 parser.add_argument("-m", "--last-metrics", action="store_true",
1728 help="Instead of comparing metrics from the last --release, "
1729 "compare with the results of the previous sanity check "
1730 "invocation")
1731 parser.add_argument("-u", "--no-update", action="store_true",
1732 help="do not update the results of the last run of the sanity "
1733 "checks")
1734 parser.add_argument("-b", "--build-only", action="store_true",
1735 help="Only build the code, do not execute any of it in QEMU")
1736 parser.add_argument("-j", "--jobs", type=int,
1737 help="Number of cores to use when building, defaults to "
1738 "number of CPUs * 2")
1739 parser.add_argument("-H", "--footprint-threshold", type=float, default=5,
1740 help="When checking test case footprint sizes, warn the user if "
1741 "the new app size is greater then the specified percentage "
1742 "from the last release. Default is 5. 0 to warn on any "
1743 "increase on app size")
Andrew Boieea7928f2015-08-14 14:27:38 -07001744 parser.add_argument("-D", "--all-deltas", action="store_true",
1745 help="Show all footprint deltas, positive or negative. Implies "
1746 "--footprint-threshold=0")
Andrew Boie6acbe632015-07-17 12:03:52 -07001747 parser.add_argument("-O", "--outdir",
1748 default="%s/sanity-out" % ZEPHYR_BASE,
1749 help="Output directory for logs and binaries.")
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001750 parser.add_argument("-n", "--no-clean", action="store_true",
1751 help="Do not delete the outdir before building. Will result in "
1752 "faster compilation since builds will be incremental")
Andrew Boie3d348712016-04-08 11:52:13 -07001753 parser.add_argument("-T", "--testcase-root", action="append", default=[],
Andrew Boie6acbe632015-07-17 12:03:52 -07001754 help="Base directory to recursively search for test cases. All "
Andrew Boie3d348712016-04-08 11:52:13 -07001755 "testcase.ini files under here will be processed. May be "
1756 "called multiple times. Defaults to the 'samples' and "
1757 "'tests' directories in the Zephyr tree.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001758 parser.add_argument("-A", "--arch-root",
1759 default="%s/scripts/sanity_chk/arches" % ZEPHYR_BASE,
1760 help="Directory to search for arch configuration files. All .ini "
1761 "files in the directory will be processed.")
Andrew Boiebbd670c2015-08-17 13:16:11 -07001762 parser.add_argument("-z", "--size", action="append",
1763 help="Don't run sanity checks. Instead, produce a report to "
1764 "stdout detailing RAM/ROM sizes on the specified filenames. "
1765 "All other command line arguments ignored.")
Andrew Boie6bb087c2016-02-10 13:39:00 -08001766 parser.add_argument("-S", "--enable-slow", action="store_true",
1767 help="Execute time-consuming test cases that have been marked "
1768 "as 'slow' in testcase.ini. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07001769 parser.add_argument("-R", "--enable-asserts", action="store_true",
1770 help="Build all test cases with assertions enabled.")
Anas Nashife3febe92016-11-30 14:25:44 -05001771 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
1772 help="Error on deprecation warnings.")
Andrew Boieba612002016-09-01 10:41:03 -07001773 parser.add_argument("-x", "--extra-args", action="append", default=[],
1774 help="Extra arguments to pass to the build when compiling test "
1775 "cases. May be called multiple times. These will be passed "
1776 "in after any sanitycheck-supplied options.")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001777 parser.add_argument("-C", "--coverage", action="store_true",
1778 help="Scan for unit test coverage with gcov + lcov.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001779
1780 return parser.parse_args()
1781
1782def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01001783 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001784 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001785 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08001786
1787 try:
1788 with open(filename) as fp:
1789 data = fp.read()
1790 except Exception as e:
1791 data = "Unable to read log data (%s)\n" % (str(e))
1792
1793 sys.stdout.write(data)
1794 if log_file:
1795 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001796 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001797 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001798 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07001799
1800def terse_test_cb(instances, goals, goal):
1801 total_tests = len(goals)
1802 total_done = 0
1803 total_failed = 0
1804
Andrew Boie08ce5a52016-02-22 13:28:10 -08001805 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001806 if g.finished:
1807 total_done += 1
1808 if g.failed:
1809 total_failed += 1
1810
1811 if goal.failed:
1812 i = instances[goal.name]
1813 info("\n\n{:<25} {:<50} {}FAILED{}: {}".format(i.platform.name,
1814 i.test.name, COLOR_RED, COLOR_NORMAL, goal.reason))
1815 log_info(goal.get_error_log())
1816 info("")
1817
Andrew Boie7a992ae2017-01-17 10:40:02 -08001818 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" % (
Andrew Boie6acbe632015-07-17 12:03:52 -07001819 COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
Andrew Boie7a992ae2017-01-17 10:40:02 -08001820 int((float(total_done) / total_tests) * 100),
Andrew Boie6acbe632015-07-17 12:03:52 -07001821 COLOR_RED if total_failed > 0 else COLOR_NORMAL,
1822 total_failed, COLOR_NORMAL))
1823 sys.stdout.flush()
1824
1825def chatty_test_cb(instances, goals, goal):
1826 i = instances[goal.name]
1827
1828 if VERBOSE < 2 and not goal.finished:
1829 return
1830
1831 if goal.failed:
1832 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
1833 elif goal.finished:
1834 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
1835 else:
1836 status = goal.make_state
1837
1838 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
1839 if goal.failed:
1840 log_info(goal.get_error_log())
1841
Andrew Boiebbd670c2015-08-17 13:16:11 -07001842
1843def size_report(sc):
1844 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07001845 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07001846 for i in range(len(sc.sections)):
1847 v = sc.sections[i]
1848
Andrew Boie73b4ee62015-10-07 11:33:22 -07001849 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
1850 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
1851 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07001852
Andrew Boie73b4ee62015-10-07 11:33:22 -07001853 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
1854 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001855 info("")
1856
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001857def generate_coverage(outdir, ignores):
1858 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
1859 coveragefile = os.path.join(outdir, "coverage.info")
1860 ztestfile = os.path.join(outdir, "ztest.info")
1861 subprocess.call(["lcov", "--capture", "--directory", outdir,
1862 "--output-file", coveragefile], stdout=coveragelog)
1863 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
1864 subprocess.call(["lcov", "--extract", coveragefile,
1865 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
1866 "--output-file", ztestfile], stdout=coveragelog)
1867 subprocess.call(["lcov", "--remove", ztestfile,
1868 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
1869 "--output-file", ztestfile], stdout=coveragelog)
1870 for i in ignores:
1871 subprocess.call(["lcov", "--remove", coveragefile, i,
1872 "--output-file", coveragefile], stdout=coveragelog)
1873 subprocess.call(["genhtml", "-output-directory",
1874 os.path.join(outdir, "coverage"),
1875 coveragefile, ztestfile], stdout=coveragelog)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001876
Andrew Boie6acbe632015-07-17 12:03:52 -07001877def main():
Andrew Boie4b182472015-07-31 12:25:22 -07001878 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001879 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Andrew Boie6acbe632015-07-17 12:03:52 -07001880 args = parse_arguments()
Javier B Perez4b554ba2016-08-15 13:25:33 -05001881 toolchain = os.environ.get("ZEPHYR_GCC_VARIANT", None)
Andrew Boie1e4e68b2017-02-10 11:40:54 -08001882 if toolchain == "zephyr":
1883 os.environ["DISABLE_TRYRUN"] = "1"
Andrew Boiebbd670c2015-08-17 13:16:11 -07001884
1885 if args.size:
1886 for fn in args.size:
Andrew Boie52fef672016-11-29 12:21:59 -08001887 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001888 sys.exit(0)
1889
Andrew Boie6acbe632015-07-17 12:03:52 -07001890 VERBOSE += args.verbose
1891 INLINE_LOGS = args.inline_logs
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001892 if args.log_file:
1893 log_file = open(args.log_file, "w")
Andrew Boie6acbe632015-07-17 12:03:52 -07001894 if args.jobs:
Daniel Leung6b170072016-04-07 12:10:25 -07001895 CPU_COUNTS = args.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07001896
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001897 if os.path.exists(args.outdir) and not args.no_clean:
Andrew Boie6acbe632015-07-17 12:03:52 -07001898 info("Cleaning output directory " + args.outdir)
1899 shutil.rmtree(args.outdir)
1900
Andrew Boie3d348712016-04-08 11:52:13 -07001901 if not args.testcase_root:
1902 args.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
1903 os.path.join(ZEPHYR_BASE, "samples")]
1904
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001905 ts = TestSuite(args.arch_root, args.testcase_root, args.outdir, args.coverage)
Anas Nashifdfa86e22016-10-24 17:08:56 -04001906 discards = ts.apply_filters(args.platform, args.arch, args.tag, args.exclude_tag, args.config,
Andrew Boie821d8322016-03-22 10:08:35 -07001907 args.test, args.only_failed, args.all,
Kumar Galad5719a62016-10-26 12:30:26 -05001908 args.platform_limit, toolchain, args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001909
1910 if args.discard_report:
1911 ts.discard_report(args.discard_report)
1912
1913 if VERBOSE:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001914 for i, reason in discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001915 debug("{:<25} {:<50} {}SKIPPED{}: {}".format(i.platform.name,
1916 i.test.name, COLOR_YELLOW, COLOR_NORMAL, reason))
1917
1918 info("%d tests selected, %d tests discarded due to filters" %
1919 (len(ts.instances), len(discards)))
1920
1921 if args.dry_run:
1922 return
1923
1924 if VERBOSE or not TERMINAL:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001925 goals = ts.execute(chatty_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05001926 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001927 args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001928 else:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001929 goals = ts.execute(terse_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05001930 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001931 args.extra_args, args.ccache)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001932 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07001933
Daniel Leung7f850102016-04-08 11:07:32 -07001934 # figure out which report to use for size comparison
1935 if args.compare_report:
1936 report_to_use = args.compare_report
1937 elif args.last_metrics:
1938 report_to_use = LAST_SANITY
1939 else:
1940 report_to_use = RELEASE_DATA
1941
1942 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07001943 warnings = 0
1944 if deltas:
Andrew Boieea7928f2015-08-14 14:27:38 -07001945 for i, metric, value, delta, lower_better in deltas:
1946 if not args.all_deltas and ((delta < 0 and lower_better) or
1947 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07001948 continue
1949
Andrew Boieea7928f2015-08-14 14:27:38 -07001950 percentage = (float(delta) / float(value - delta))
1951 if not args.all_deltas and (percentage <
1952 (args.footprint_threshold / 100.0)):
1953 continue
1954
Daniel Leung00525c22016-04-11 10:27:56 -07001955 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07001956 i.platform.name, i.test.name, COLOR_YELLOW,
1957 "INFO" if args.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07001958 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07001959 warnings += 1
1960
1961 if warnings:
1962 info("Deltas based on metrics from last %s" %
1963 ("release" if not args.last_metrics else "run"))
1964
1965 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08001966 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001967 if goal.failed:
1968 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001969 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07001970 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
1971 (COLOR_RED, COLOR_NORMAL, goal.name,
1972 str(goal.metrics["unrecognized"])))
1973 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001974
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001975 if args.coverage:
1976 info("Generating coverage files...")
1977 generate_coverage(args.outdir, ["tests/*", "samples/*"])
1978
Andrew Boie4b182472015-07-31 12:25:22 -07001979 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Andrew Boie6acbe632015-07-17 12:03:52 -07001980 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
1981 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
Andrew Boie4b182472015-07-31 12:25:22 -07001982 warnings, COLOR_NORMAL, time.time() - start_time))
Andrew Boie6acbe632015-07-17 12:03:52 -07001983
1984 if args.testcase_report:
1985 ts.testcase_report(args.testcase_report)
1986 if not args.no_update:
Anas Nashifb3311ed2017-04-13 14:44:48 -04001987 ts.testcase_xunit_report(LAST_SANITY_XUNIT, args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001988 ts.testcase_report(LAST_SANITY)
1989 if args.release:
1990 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001991 if log_file:
1992 log_file.close()
Andrew Boie6acbe632015-07-17 12:03:52 -07001993 if failed or (warnings and args.warnings_as_errors):
1994 sys.exit(1)
1995
1996if __name__ == "__main__":
1997 main()