blob: 486db9d1e7c0a3f84989029fc7abff96c76bcd83 [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
Andrew Boie6acbe632015-07-17 12:03:52 -0700175
176if "ZEPHYR_BASE" not in os.environ:
Anas Nashif427cdd32015-08-06 07:25:42 -0400177 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700178 exit(1)
179ZEPHYR_BASE = os.environ["ZEPHYR_BASE"]
Andrew Boie3ea78922016-03-24 14:46:00 -0700180
181sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
182
183import expr_parser
184
Andrew Boie6acbe632015-07-17 12:03:52 -0700185VERBOSE = 0
186LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
187 "last_sanity.csv")
188RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
189 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700190CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700191
192if os.isatty(sys.stdout.fileno()):
193 TERMINAL = True
194 COLOR_NORMAL = '\033[0m'
195 COLOR_RED = '\033[91m'
196 COLOR_GREEN = '\033[92m'
197 COLOR_YELLOW = '\033[93m'
198else:
199 TERMINAL = False
200 COLOR_NORMAL = ""
201 COLOR_RED = ""
202 COLOR_GREEN = ""
203 COLOR_YELLOW = ""
204
205class SanityCheckException(Exception):
206 pass
207
208class SanityRuntimeError(SanityCheckException):
209 pass
210
211class ConfigurationError(SanityCheckException):
212 def __init__(self, cfile, message):
213 self.cfile = cfile
214 self.message = message
215
216 def __str__(self):
217 return repr(self.cfile + ": " + self.message)
218
219class MakeError(SanityCheckException):
220 pass
221
222class BuildError(MakeError):
223 pass
224
225class ExecutionError(MakeError):
226 pass
227
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800228log_file = None
229
Andrew Boie6acbe632015-07-17 12:03:52 -0700230# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800231def info(what):
232 sys.stdout.write(what + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800233 if log_file:
234 log_file.write(what + "\n")
235 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700236
237def error(what):
238 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800239 if log_file:
240 log_file(what + "\n")
241 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700242
Andrew Boie08ce5a52016-02-22 13:28:10 -0800243def debug(what):
244 if VERBOSE >= 1:
245 info(what)
246
Andrew Boie6acbe632015-07-17 12:03:52 -0700247def verbose(what):
248 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800249 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700250
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300251class Handler:
252 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
253 RUN_FAILED = "PROJECT EXECUTION FAILED"
254 def __init__(self, name, outdir, log_fn, timeout, unit=False):
255 """Constructor
256
257 @param name Arbitrary name of the created thread
258 @param outdir Working directory, should be where qemu.pid gets created
259 by kbuild
260 @param log_fn Absolute path to write out QEMU's log data
261 @param timeout Kill the QEMU process if it doesn't finish up within
262 the given number of seconds
263 """
264 self.lock = threading.Lock()
265 self.state = "waiting"
266 self.metrics = {}
267 self.metrics["qemu_time"] = 0
268 self.metrics["ram_size"] = 0
269 self.metrics["rom_size"] = 0
270 self.unit = unit
271
272 def set_state(self, state, metrics):
273 self.lock.acquire()
274 self.state = state
275 self.metrics.update(metrics)
276 self.lock.release()
277
278 def get_state(self):
279 self.lock.acquire()
280 ret = (self.state, self.metrics)
281 self.lock.release()
282 return ret
283
284class UnitHandler(Handler):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300285 def __init__(self, name, sourcedir, outdir, run_log, valgrind_log, timeout):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300286 """Constructor
287
288 @param name Arbitrary name of the created thread
289 @param outdir Working directory containing the test binary
290 @param run_log Absolute path to runtime logs
291 @param valgrind Absolute path to valgrind's log
292 @param timeout Kill the QEMU process if it doesn't finish up within
293 the given number of seconds
294 """
295 super().__init__(name, outdir, run_log, timeout, True)
296
297 self.timeout = timeout
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300298 self.sourcedir = sourcedir
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300299 self.outdir = outdir
300 self.run_log = run_log
301 self.valgrind_log = valgrind_log
302 self.returncode = 0
303 self.set_state("running", {})
304
305 def handle(self):
306 out_state = "failed"
307
308 with open(self.run_log, "wt") as rl, open(self.valgrind_log, "wt") as vl:
309 try:
310 binary = os.path.join(self.outdir, "testbinary")
311 command = [binary]
312 if shutil.which("valgrind"):
313 command = ["valgrind", "--error-exitcode=2",
314 "--leak-check=full"] + command
315 returncode = subprocess.call(command, timeout=self.timeout,
316 stdout=rl, stderr=vl)
317 self.returncode = returncode
318 if returncode != 0:
319 if self.returncode == 1:
320 out_state = "failed"
321 else:
322 out_state = "failed valgrind"
323 else:
324 out_state = "passed"
325 except subprocess.TimeoutExpired:
326 out_state = "timeout"
327 self.returncode = 1
328
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300329 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
330
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300331 self.set_state(out_state, {})
332
333class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700334 """Spawns a thread to monitor QEMU output from pipes
335
336 We pass QEMU_PIPE to 'make qemu' and monitor the pipes for output.
337 We need to do this as once qemu starts, it runs forever until killed.
338 Test cases emit special messages to the console as they run, we check
339 for these to collect whether the test passed or failed.
340 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700341
342 @staticmethod
343 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results):
344 fifo_in = fifo_fn + ".in"
345 fifo_out = fifo_fn + ".out"
346
347 # These in/out nodes are named from QEMU's perspective, not ours
348 if os.path.exists(fifo_in):
349 os.unlink(fifo_in)
350 os.mkfifo(fifo_in)
351 if os.path.exists(fifo_out):
352 os.unlink(fifo_out)
353 os.mkfifo(fifo_out)
354
355 # We don't do anything with out_fp but we need to open it for
356 # writing so that QEMU doesn't block, due to the way pipes work
357 out_fp = open(fifo_in, "wb")
358 # Disable internal buffering, we don't
359 # want read() or poll() to ever block if there is data in there
360 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800361 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700362
363 start_time = time.time()
364 timeout_time = start_time + timeout
365 p = select.poll()
366 p.register(in_fp, select.POLLIN)
367
368 metrics = {}
369 line = ""
370 while True:
371 this_timeout = int((timeout_time - time.time()) * 1000)
372 if this_timeout < 0 or not p.poll(this_timeout):
373 out_state = "timeout"
374 break
375
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500376 try:
377 c = in_fp.read(1).decode("utf-8")
378 except UnicodeDecodeError:
379 # Test is writing something weird, fail
380 out_state = "unexpected byte"
381 break
382
Andrew Boie6acbe632015-07-17 12:03:52 -0700383 if c == "":
384 # EOF, this shouldn't happen unless QEMU crashes
385 out_state = "unexpected eof"
386 break
387 line = line + c
388 if c != "\n":
389 continue
390
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300391 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700392 log_out_fp.write(line)
393 log_out_fp.flush()
394 line = line.strip()
395 verbose("QEMU: %s" % line)
396
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300397 if line == handler.RUN_PASSED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700398 out_state = "passed"
399 break
400
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300401 if line == handler.RUN_FAILED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700402 out_state = "failed"
403 break
404
405 # TODO: Add support for getting numerical performance data
406 # from test cases. Will involve extending test case reporting
407 # APIs. Add whatever gets reported to the metrics dictionary
408 line = ""
409
410 metrics["qemu_time"] = time.time() - start_time
411 verbose("QEMU complete (%s) after %f seconds" %
412 (out_state, metrics["qemu_time"]))
413 handler.set_state(out_state, metrics)
414
415 log_out_fp.close()
416 out_fp.close()
417 in_fp.close()
418
419 pid = int(open(pid_fn).read())
420 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800421 try:
422 os.kill(pid, signal.SIGTERM)
423 except ProcessLookupError:
424 # Oh well, as long as it's dead! User probably sent Ctrl-C
425 pass
426
Andrew Boie6acbe632015-07-17 12:03:52 -0700427 os.unlink(fifo_in)
428 os.unlink(fifo_out)
429
Andrew Boie6acbe632015-07-17 12:03:52 -0700430 def __init__(self, name, outdir, log_fn, timeout):
431 """Constructor
432
433 @param name Arbitrary name of the created thread
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300434 @param outdir Working directory, should be where qemu.pid gets created
Andrew Boie6acbe632015-07-17 12:03:52 -0700435 by kbuild
436 @param log_fn Absolute path to write out QEMU's log data
437 @param timeout Kill the QEMU process if it doesn't finish up within
438 the given number of seconds
439 """
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300440 super().__init__(name, outdir, log_fn, timeout)
Andrew Boie6acbe632015-07-17 12:03:52 -0700441 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700442
443 # We pass this to QEMU which looks for fifos with .in and .out
444 # suffixes.
445 self.fifo_fn = os.path.join(outdir, "qemu-fifo")
446
447 self.pid_fn = os.path.join(outdir, "qemu.pid")
448 if os.path.exists(self.pid_fn):
449 os.unlink(self.pid_fn)
450
451 self.log_fn = log_fn
452 self.thread = threading.Thread(name=name, target=QEMUHandler._thread,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300453 args=(self, timeout, outdir,
454 self.log_fn, self.fifo_fn,
455 self.pid_fn, self.results))
Andrew Boie6acbe632015-07-17 12:03:52 -0700456 self.thread.daemon = True
457 verbose("Spawning QEMU process for %s" % name)
458 self.thread.start()
459
Andrew Boie6acbe632015-07-17 12:03:52 -0700460 def get_fifo(self):
461 return self.fifo_fn
462
Andrew Boie6acbe632015-07-17 12:03:52 -0700463class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700464
465 alloc_sections = ["bss", "noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800466 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500467 "_k_memory_pool", "exceptions", "initshell",
468 "_static_thread_area", "_k_timer_area",
469 "_k_mem_slab_area", "_k_mem_pool_area",
470 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
471 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
Tomasz Bursztykab56c4df2016-08-18 14:07:22 +0200472 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Tomasz Bursztykae38a9e82017-03-08 09:30:03 +0100473 "net_if", "net_if_event", "net_stack", "net_l2_data",
Anas Nashif6d72e632017-02-27 19:49:03 -0500474 "_k_queue_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700475 # These get copied into RAM only on non-XIP
Andrew Boie3b930212016-06-07 13:11:45 -0700476 ro_sections = ["text", "ctors", "init_array", "reset",
Anas Nashifbfcdfaf2016-12-15 10:02:14 -0500477 "rodata", "devconfig", "net_l2", "vector"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700478
Andrew Boie52fef672016-11-29 12:21:59 -0800479 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700480 """Constructor
481
Andrew Boiebbd670c2015-08-17 13:16:11 -0700482 @param filename Path to the output binary
483 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700484 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700485 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700486 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700487 magic = f.read(4)
488
Andrew Boie08ce5a52016-02-22 13:28:10 -0800489 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700490 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700491
492 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
493 # GREP can not be used as it returns an error if the symbol is not found.
Andrew Boiebbd670c2015-08-17 13:16:11 -0700494 is_xip_command = "nm " + filename + " | awk '/CONFIG_XIP/ { print $3 }'"
Andrew Boie8f0211d2016-03-02 20:40:29 -0800495 is_xip_output = subprocess.check_output(is_xip_command, shell=True,
496 stderr=subprocess.STDOUT).decode("utf-8").strip()
497 if is_xip_output.endswith("no symbols"):
498 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700499 self.is_xip = (len(is_xip_output) != 0)
500
Andrew Boiebbd670c2015-08-17 13:16:11 -0700501 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700502 self.sections = []
503 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700504 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800505 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700506
507 self._calculate_sizes()
508
509 def get_ram_size(self):
510 """Get the amount of RAM the application will use up on the device
511
512 @return amount of RAM, in bytes
513 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700514 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700515
516 def get_rom_size(self):
517 """Get the size of the data that this application uses on device's flash
518
519 @return amount of ROM, in bytes
520 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700521 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700522
523 def unrecognized_sections(self):
524 """Get a list of sections inside the binary that weren't recognized
525
526 @return list of unrecogized section names
527 """
528 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700529 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700530 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700531 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700532 return slist
533
534 def _calculate_sizes(self):
535 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700536 objdump_command = "objdump -h " + self.filename
Andrew Boie6acbe632015-07-17 12:03:52 -0700537 objdump_output = subprocess.check_output(objdump_command,
Andrew Boie08ce5a52016-02-22 13:28:10 -0800538 shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700539
540 for line in objdump_output:
541 words = line.split()
542
543 if (len(words) == 0): # Skip lines that are too short
544 continue
545
546 index = words[0]
547 if (not index[0].isdigit()): # Skip lines that do not start
548 continue # with a digit
549
550 name = words[1] # Skip lines with section names
551 if (name[0] == '.'): # starting with '.'
552 continue
553
Andrew Boie73b4ee62015-10-07 11:33:22 -0700554 # TODO this doesn't actually reflect the size in flash or RAM as
555 # it doesn't include linker-imposed padding between sections.
556 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700557 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700558 if size == 0:
559 continue
560
Andrew Boie73b4ee62015-10-07 11:33:22 -0700561 load_addr = int(words[4], 16)
562 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700563
564 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700565 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700566 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700567 if name in SizeCalculator.alloc_sections:
568 self.ram_size += size
569 stype = "alloc"
570 elif name in SizeCalculator.rw_sections:
571 self.ram_size += size
572 self.rom_size += size
573 stype = "rw"
574 elif name in SizeCalculator.ro_sections:
575 self.rom_size += size
576 if not self.is_xip:
577 self.ram_size += size
578 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700579 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700580 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800581 if name not in self.extra_sections:
582 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700583
Andrew Boie73b4ee62015-10-07 11:33:22 -0700584 self.sections.append({"name" : name, "load_addr" : load_addr,
585 "size" : size, "virt_addr" : virt_addr,
Andy Ross8d801a52016-10-04 11:48:21 -0700586 "type" : stype, "recognized" : recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700587
588
589class MakeGoal:
590 """Metadata class representing one of the sub-makes called by MakeGenerator
591
592 MakeGenerator returns a dictionary of these which can then be associdated
593 with TestInstances to get a complete picture of what happened during a test.
594 MakeGenerator is used for tasks outside of building tests (such as
595 defconfigs) which is why MakeGoal is a separate class from TestInstance.
596 """
597 def __init__(self, name, text, qemu, make_log, build_log, run_log,
598 qemu_log):
599 self.name = name
600 self.text = text
601 self.qemu = qemu
602 self.make_log = make_log
603 self.build_log = build_log
604 self.run_log = run_log
605 self.qemu_log = qemu_log
606 self.make_state = "waiting"
607 self.failed = False
608 self.finished = False
609 self.reason = None
610 self.metrics = {}
611
612 def get_error_log(self):
613 if self.make_state == "waiting":
614 # Shouldn't ever see this; breakage in the main Makefile itself.
615 return self.make_log
616 elif self.make_state == "building":
617 # Failure when calling the sub-make to build the code
618 return self.build_log
619 elif self.make_state == "running":
620 # Failure in sub-make for "make qemu", qemu probably failed to start
621 return self.run_log
622 elif self.make_state == "finished":
623 # QEMU finished, but timed out or otherwise wasn't successful
624 return self.qemu_log
625
626 def fail(self, reason):
627 self.failed = True
628 self.finished = True
629 self.reason = reason
630
631 def success(self):
632 self.finished = True
633
634 def __str__(self):
635 if self.finished:
636 if self.failed:
637 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
638 self.get_error_log())
639 else:
640 return "[%s] passed" % self.name
641 else:
642 return "[%s] in progress (%s)" % (self.name, self.make_state)
643
644
645class MakeGenerator:
646 """Generates a Makefile which just calls a bunch of sub-make sessions
647
648 In any given test suite we may need to build dozens if not hundreds of
649 test cases. The cleanest way to parallelize this is to just let Make
650 do the parallelization, sharing the jobserver among all the different
651 sub-make targets.
652 """
653
654 GOAL_HEADER_TMPL = """.PHONY: {goal}
655{goal}:
656"""
657
658 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Andrew Boief7a6e282017-02-02 13:04:57 -0800659\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 -0700660"""
661
662 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
663
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300664 re_make = re.compile("sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700665
Anas Nashife3febe92016-11-30 14:25:44 -0500666 def __init__(self, base_outdir, asserts=False, deprecations=False, ccache=0):
Andrew Boie6acbe632015-07-17 12:03:52 -0700667 """MakeGenerator constructor
668
669 @param base_outdir Intended to be the base out directory. A make.log
670 file will be created here which contains the output of the
671 top-level Make session, as well as the dynamic control Makefile
672 @param verbose If true, pass V=1 to all the sub-makes which greatly
673 increases their verbosity
674 """
675 self.goals = {}
676 if not os.path.exists(base_outdir):
677 os.makedirs(base_outdir)
678 self.logfile = os.path.join(base_outdir, "make.log")
679 self.makefile = os.path.join(base_outdir, "Makefile")
Andrew Boie55121052016-07-20 11:52:04 -0700680 self.asserts = asserts
Anas Nashife3febe92016-11-30 14:25:44 -0500681 self.deprecations = deprecations
Kumar Galad5719a62016-10-26 12:30:26 -0500682 self.ccache = ccache
Andrew Boie6acbe632015-07-17 12:03:52 -0700683
684 def _get_rule_header(self, name):
685 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
686
687 def _get_sub_make(self, name, phase, workdir, outdir, logfile, args):
688 verb = "1" if VERBOSE else "0"
689 args = " ".join(args)
Anas Nashife3febe92016-11-30 14:25:44 -0500690
Andrew Boie55121052016-07-20 11:52:04 -0700691 if self.asserts:
692 cflags="-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
693 else:
694 cflags=""
Anas Nashife3febe92016-11-30 14:25:44 -0500695
696 if self.deprecations:
697 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800698
699 if self.ccache:
700 args = args + " USE_CCACHE=1"
701
702 return MakeGenerator.MAKE_RULE_TMPL.format(phase=phase, goal=name,
Andrew Boie55121052016-07-20 11:52:04 -0700703 outdir=outdir, cflags=cflags,
Andrew Boie6acbe632015-07-17 12:03:52 -0700704 directory=workdir, verb=verb,
705 args=args, logfile=logfile)
706
707 def _get_rule_footer(self, name):
708 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
709
710 def _add_goal(self, outdir):
711 if not os.path.exists(outdir):
712 os.makedirs(outdir)
713
714 def add_build_goal(self, name, directory, outdir, args):
715 """Add a goal to invoke a Kbuild session
716
717 @param name A unique string name for this build goal. The results
718 dictionary returned by execute() will be keyed by this name.
719 @param directory Absolute path to working directory, will be passed
720 to make -C
721 @param outdir Absolute path to output directory, will be passed to
722 Kbuild via -O=<path>
723 @param args Extra command line arguments to pass to 'make', typically
724 environment variables or specific Make goals
725 """
726 self._add_goal(outdir)
727 build_logfile = os.path.join(outdir, "build.log")
728 text = (self._get_rule_header(name) +
729 self._get_sub_make(name, "building", directory,
730 outdir, build_logfile, args) +
731 self._get_rule_footer(name))
732 self.goals[name] = MakeGoal(name, text, None, self.logfile, build_logfile,
733 None, None)
734
735 def add_qemu_goal(self, name, directory, outdir, args, timeout=30):
736 """Add a goal to build a Zephyr project and then run it under QEMU
737
738 The generated make goal invokes Make twice, the first time it will
739 build the default goal, and the second will invoke the 'qemu' goal.
740 The output of the QEMU session will be monitored, and terminated
741 either upon pass/fail result of the test program, or the timeout
742 is reached.
743
744 @param name A unique string name for this build goal. The results
745 dictionary returned by execute() will be keyed by this name.
746 @param directory Absolute path to working directory, will be passed
747 to make -C
748 @param outdir Absolute path to output directory, will be passed to
749 Kbuild via -O=<path>
750 @param args Extra command line arguments to pass to 'make', typically
751 environment variables. Do not pass specific Make goals here.
752 @param timeout Maximum length of time QEMU session should be allowed
753 to run before automatically killing it. Default is 30 seconds.
754 """
755
756 self._add_goal(outdir)
757 build_logfile = os.path.join(outdir, "build.log")
758 run_logfile = os.path.join(outdir, "run.log")
759 qemu_logfile = os.path.join(outdir, "qemu.log")
760
761 q = QEMUHandler(name, outdir, qemu_logfile, timeout)
762 args.append("QEMU_PIPE=%s" % q.get_fifo())
763 text = (self._get_rule_header(name) +
764 self._get_sub_make(name, "building", directory,
765 outdir, build_logfile, args) +
766 self._get_sub_make(name, "running", directory,
767 outdir, run_logfile,
Mazen NEIFER7f046372017-01-31 12:41:33 +0100768 args + ["run"]) +
Andrew Boie6acbe632015-07-17 12:03:52 -0700769 self._get_rule_footer(name))
770 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
771 run_logfile, qemu_logfile)
772
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300773 def add_unit_goal(self, name, directory, outdir, args, timeout=30, coverage=False):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300774 self._add_goal(outdir)
775 build_logfile = os.path.join(outdir, "build.log")
776 run_logfile = os.path.join(outdir, "run.log")
777 qemu_logfile = os.path.join(outdir, "qemu.log")
778 valgrind_logfile = os.path.join(outdir, "valgrind.log")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300779 if coverage:
780 args += ["COVERAGE=1"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300781
782 # we handle running in the UnitHandler class
783 text = (self._get_rule_header(name) +
784 self._get_sub_make(name, "building", directory,
785 outdir, build_logfile, args) +
786 self._get_rule_footer(name))
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300787 q = UnitHandler(name, directory, outdir, run_logfile, valgrind_logfile, timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300788 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
789 run_logfile, valgrind_logfile)
790
Andrew Boie6acbe632015-07-17 12:03:52 -0700791
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300792 def add_test_instance(self, ti, build_only=False, enable_slow=False, coverage=False,
Andrew Boieba612002016-09-01 10:41:03 -0700793 extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -0700794 """Add a goal to build/test a TestInstance object
795
796 @param ti TestInstance object to build. The status dictionary returned
797 by execute() will be keyed by its .name field.
798 """
799 args = ti.test.extra_args[:]
800 args.extend(["ARCH=%s" % ti.platform.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -0500801 "BOARD=%s" % ti.platform.name])
Andrew Boieba612002016-09-01 10:41:03 -0700802 args.extend(extra_args)
Andrew Boie6bb087c2016-02-10 13:39:00 -0800803 if (ti.platform.qemu_support and (not ti.build_only) and
804 (not build_only) and (enable_slow or not ti.test.slow)):
Andrew Boie6acbe632015-07-17 12:03:52 -0700805 self.add_qemu_goal(ti.name, ti.test.code_location, ti.outdir,
806 args, ti.test.timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300807 elif ti.test.type == "unit":
808 self.add_unit_goal(ti.name, ti.test.code_location, ti.outdir,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300809 args, ti.test.timeout, coverage)
Andrew Boie6acbe632015-07-17 12:03:52 -0700810 else:
811 self.add_build_goal(ti.name, ti.test.code_location, ti.outdir, args)
812
813 def execute(self, callback_fn=None, context=None):
814 """Execute all the registered build goals
815
816 @param callback_fn If not None, a callback function will be called
817 as individual goals transition between states. This function
818 should accept two parameters: a string state and an arbitrary
819 context object, supplied here
820 @param context Context object to pass to the callback function.
821 Type and semantics are specific to that callback function.
822 @return A dictionary mapping goal names to final status.
823 """
824
Andrew Boie08ce5a52016-02-22 13:28:10 -0800825 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -0700826 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -0800827 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -0700828 # Create our dynamic Makefile and execute it.
829 # Watch stderr output which is where we will keep
830 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -0800831 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -0700832 tf.write(goal.text)
833 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
834 tf.flush()
835
Daniel Leung6b170072016-04-07 12:10:25 -0700836 cmd = ["make", "-k", "-j", str(CPU_COUNTS * 2), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700837 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
838 stdout=devnull)
839
840 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -0800841 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -0700842 make_log.write(line)
843 verbose("MAKE: " + repr(line.strip()))
844 m = MakeGenerator.re_make.match(line)
845 if not m:
846 continue
847
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300848 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -0700849 if error:
850 goal = self.goals[error]
851 else:
852 goal = self.goals[name]
853 goal.make_state = state
854
855
856 if error:
Andrew Boie822b0872017-01-10 13:32:40 -0800857 # Sometimes QEMU will run an image and then crash out, which
858 # will cause the 'make qemu' invocation to exit with
859 # nonzero status.
860 # Need to distinguish this case from a compilation failure.
861 if goal.qemu:
862 goal.fail("qemu_crash")
863 else:
864 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -0700865 else:
866 if state == "finished":
867 if goal.qemu:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300868 if goal.qemu.unit:
869 # We can't run unit tests with Make
870 goal.qemu.handle()
871 if goal.qemu.returncode == 2:
872 goal.qemu_log = goal.qemu.valgrind_log
873 elif goal.qemu.returncode:
874 goal.qemu_log = goal.qemu.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700875 thread_status, metrics = goal.qemu.get_state()
876 goal.metrics.update(metrics)
877 if thread_status == "passed":
878 goal.success()
879 else:
880 goal.fail(thread_status)
881 else:
882 goal.success()
883
884 if callback_fn:
885 callback_fn(context, self.goals, goal)
886
887 p.wait()
888 return self.goals
889
890
891# "list" - List of strings
892# "list:<type>" - List of <type>
893# "set" - Set of unordered, unique strings
894# "set:<type>" - Set of <type>
895# "float" - Floating point
896# "int" - Integer
897# "bool" - Boolean
898# "str" - String
899
900# XXX Be sure to update __doc__ if you change any of this!!
901
902arch_valid_keys = {"name" : {"type" : "str", "required" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500903 "platforms" : {"type" : "list", "required" : True},
904 "supported_toolchains" : {"type" : "list", "required" : True}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700905
906platform_valid_keys = {"qemu_support" : {"type" : "bool", "default" : False},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500907 "supported_toolchains" : {"type" : "list", "default" : []}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700908
909testcase_valid_keys = {"tags" : {"type" : "set", "required" : True},
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300910 "type" : {"type" : "str", "default": "integration"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700911 "extra_args" : {"type" : "list"},
912 "build_only" : {"type" : "bool", "default" : False},
Anas Nashif2bd99bc2015-10-12 13:10:57 -0400913 "skip" : {"type" : "bool", "default" : False},
Andrew Boie6bb087c2016-02-10 13:39:00 -0800914 "slow" : {"type" : "bool", "default" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700915 "timeout" : {"type" : "int", "default" : 60},
916 "arch_whitelist" : {"type" : "set"},
Anas Nashif30d13872015-10-05 10:02:45 -0400917 "arch_exclude" : {"type" : "set"},
Andrew Boie52fef672016-11-29 12:21:59 -0800918 "extra_sections" : {"type" : "list", "default" : []},
Anas Nashif30d13872015-10-05 10:02:45 -0400919 "platform_exclude" : {"type" : "set"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700920 "platform_whitelist" : {"type" : "set"},
Andrew Boie3ea78922016-03-24 14:46:00 -0700921 "filter" : {"type" : "str"}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700922
923
924class SanityConfigParser:
925 """Class to read architecture and test case .ini files with semantic checking
926 """
927 def __init__(self, filename):
928 """Instantiate a new SanityConfigParser object
929
930 @param filename Source .ini file to read
931 """
Andrew Boie08ce5a52016-02-22 13:28:10 -0800932 cp = configparser.SafeConfigParser()
Andrew Boie6acbe632015-07-17 12:03:52 -0700933 cp.readfp(open(filename))
934 self.filename = filename
935 self.cp = cp
936
937 def _cast_value(self, value, typestr):
938 v = value.strip()
939 if typestr == "str":
940 return v
941
942 elif typestr == "float":
943 return float(v)
944
945 elif typestr == "int":
946 return int(v)
947
948 elif typestr == "bool":
949 v = v.lower()
950 if v == "true" or v == "1":
951 return True
952 elif v == "" or v == "false" or v == "0":
953 return False
954 raise ConfigurationError(self.filename,
955 "bad value for boolean: '%s'" % value)
956
957 elif typestr.startswith("list"):
958 vs = v.split()
959 if len(typestr) > 4 and typestr[4] == ":":
960 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
961 else:
962 return vs
963
964 elif typestr.startswith("set"):
965 vs = v.split()
966 if len(typestr) > 3 and typestr[3] == ":":
967 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
968 else:
969 return set(vs)
970
971 else:
972 raise ConfigurationError(self.filename, "unknown type '%s'" % value)
973
974
975 def sections(self):
976 """Get the set of sections within the .ini file
977
978 @return a list of string section names"""
979 return self.cp.sections()
980
981 def get_section(self, section, valid_keys):
982 """Get a dictionary representing the keys/values within a section
983
984 @param section The section in the .ini file to retrieve data from
985 @param valid_keys A dictionary representing the intended semantics
986 for this section. Each key in this dictionary is a key that could
987 be specified, if a key is given in the .ini file which isn't in
988 here, it will generate an error. Each value in this dictionary
989 is another dictionary containing metadata:
990
991 "default" - Default value if not given
992 "type" - Data type to convert the text value to. Simple types
993 supported are "str", "float", "int", "bool" which will get
994 converted to respective Python data types. "set" and "list"
995 may also be specified which will split the value by
996 whitespace (but keep the elements as strings). finally,
997 "list:<type>" and "set:<type>" may be given which will
998 perform a type conversion after splitting the value up.
999 "required" - If true, raise an error if not defined. If false
1000 and "default" isn't specified, a type conversion will be
1001 done on an empty string
1002 @return A dictionary containing the section key-value pairs with
1003 type conversion and default values filled in per valid_keys
1004 """
1005
1006 d = {}
1007 cp = self.cp
1008
1009 if not cp.has_section(section):
Andrew Boiee57a1e52016-03-22 10:29:14 -07001010 # Just fill it with defaults
1011 cp.add_section(section)
Andrew Boie6acbe632015-07-17 12:03:52 -07001012
1013 for k, v in cp.items(section):
1014 if k not in valid_keys:
1015 raise ConfigurationError(self.filename,
1016 "Unknown config key '%s' in defintiion for '%s'"
1017 % (k, section))
1018 d[k] = v
1019
Andrew Boie08ce5a52016-02-22 13:28:10 -08001020 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001021 if k not in d:
1022 if "required" in kinfo:
1023 required = kinfo["required"]
1024 else:
1025 required = False
1026
1027 if required:
1028 raise ConfigurationError(self.filename,
1029 "missing required value for '%s' in section '%s'"
1030 % (k, section))
1031 else:
1032 if "default" in kinfo:
1033 default = kinfo["default"]
1034 else:
1035 default = self._cast_value("", kinfo["type"])
1036 d[k] = default
1037 else:
1038 try:
1039 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001040 except ValueError as ve:
Andrew Boie6acbe632015-07-17 12:03:52 -07001041 raise ConfigurationError(self.filename,
1042 "bad %s value '%s' for key '%s' in section '%s'"
1043 % (kinfo["type"], d[k], k, section))
1044
1045 return d
1046
1047
1048class Platform:
1049 """Class representing metadata for a particular platform
1050
Anas Nashifc7406082015-12-13 15:00:31 -05001051 Maps directly to BOARD when building"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001052 def __init__(self, arch, name, plat_dict):
1053 """Constructor.
1054
1055 @param arch Architecture object for this platform
Anas Nashifc7406082015-12-13 15:00:31 -05001056 @param name String name for this platform, same as BOARD
Andrew Boie6acbe632015-07-17 12:03:52 -07001057 @param plat_dict SanityConfigParser output on the relevant section
1058 in the architecture configuration file which has lots of metadata.
1059 See the Architecture class.
1060 """
1061 self.name = name
1062 self.qemu_support = plat_dict["qemu_support"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001063 self.arch = arch
Javier B Perez4b554ba2016-08-15 13:25:33 -05001064 self.supported_toolchains = arch.supported_toolchains
1065 if plat_dict["supported_toolchains"]:
1066 self.supported_toolchains = plat_dict["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001067 # Gets populated in a separate step
Andrew Boie41878222016-11-03 11:58:53 -07001068 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001069 pass
1070
Andrew Boie6acbe632015-07-17 12:03:52 -07001071 def __repr__(self):
1072 return "<%s on %s>" % (self.name, self.arch.name)
1073
1074
1075class Architecture:
1076 """Class representing metadata for a particular architecture
1077 """
1078 def __init__(self, cfile):
1079 """Architecture constructor
1080
1081 @param cfile Path to Architecture configuration file, which gives
1082 info about the arch and all the platforms for it
1083 """
1084 cp = SanityConfigParser(cfile)
1085 self.platforms = []
1086
1087 arch = cp.get_section("arch", arch_valid_keys)
1088
1089 self.name = arch["name"]
Javier B Perez4b554ba2016-08-15 13:25:33 -05001090 self.supported_toolchains = arch["supported_toolchains"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001091
1092 for plat_name in arch["platforms"]:
1093 verbose("Platform: %s" % plat_name)
1094 plat_dict = cp.get_section(plat_name, platform_valid_keys)
1095 self.platforms.append(Platform(self, plat_name, plat_dict))
1096
1097 def __repr__(self):
1098 return "<arch %s>" % self.name
1099
1100
1101class TestCase:
1102 """Class representing a test application
1103 """
Andrew Boie3ea78922016-03-24 14:46:00 -07001104 def __init__(self, testcase_root, workdir, name, tc_dict, inifile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001105 """TestCase constructor.
1106
1107 This gets called by TestSuite as it finds and reads testcase.ini files.
1108 Multiple TestCase instances may be generated from a single testcase.ini,
1109 each one corresponds to a section within that file.
1110
Andrew Boie6acbe632015-07-17 12:03:52 -07001111 We need to have a unique name for every single test case. Since
1112 a testcase.ini can define multiple tests, the canonical name for
1113 the test case is <workdir>/<name>.
1114
1115 @param testcase_root Absolute path to the root directory where
1116 all the test cases live
1117 @param workdir Relative path to the project directory for this
1118 test application from the test_case root.
1119 @param name Name of this test case, corresponding to the section name
1120 in the test case configuration file. For many test cases that just
1121 define one test, can be anything and is usually "test". This is
1122 really only used to distinguish between different cases when
1123 the testcase.ini defines multiple tests
1124 @param tc_dict Dictionary with section values for this test case
1125 from the testcase.ini file
1126 """
1127 self.code_location = os.path.join(testcase_root, workdir)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001128 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001129 self.tags = tc_dict["tags"]
1130 self.extra_args = tc_dict["extra_args"]
1131 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001132 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001133 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001134 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001135 self.platform_whitelist = tc_dict["platform_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001136 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001137 self.timeout = tc_dict["timeout"]
1138 self.build_only = tc_dict["build_only"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001139 self.slow = tc_dict["slow"]
Andrew Boie52fef672016-11-29 12:21:59 -08001140 self.extra_sections = tc_dict["extra_sections"]
Andrew Boie14ac9022016-04-08 13:31:53 -07001141 self.path = os.path.join(os.path.basename(os.path.abspath(testcase_root)),
1142 workdir, name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001143 self.name = self.path # for now
Anas Nashif2cf0df02015-10-10 09:29:43 -04001144 self.defconfig = {}
Andrew Boie3ea78922016-03-24 14:46:00 -07001145 self.inifile = inifile
Andrew Boie6acbe632015-07-17 12:03:52 -07001146
Andrew Boie6acbe632015-07-17 12:03:52 -07001147 def __repr__(self):
1148 return self.name
1149
1150
1151
1152class TestInstance:
1153 """Class representing the execution of a particular TestCase on a platform
1154
1155 @param test The TestCase object we want to build/execute
1156 @param platform Platform object that we want to build and run against
1157 @param base_outdir Base directory for all test results. The actual
1158 out directory used is <outdir>/<platform>/<test case name>
1159 """
Andrew Boie6bb087c2016-02-10 13:39:00 -08001160 def __init__(self, test, platform, base_outdir, build_only=False,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001161 slow=False, coverage=False):
Andrew Boie6acbe632015-07-17 12:03:52 -07001162 self.test = test
1163 self.platform = platform
1164 self.name = os.path.join(platform.name, test.path)
1165 self.outdir = os.path.join(base_outdir, platform.name, test.path)
1166 self.build_only = build_only or test.build_only
1167
1168 def calculate_sizes(self):
1169 """Get the RAM/ROM sizes of a test case.
1170
1171 This can only be run after the instance has been executed by
1172 MakeGenerator, otherwise there won't be any binaries to measure.
1173
1174 @return A SizeCalculator object
1175 """
Andrew Boie5d4eb782015-10-02 10:04:56 -07001176 fns = glob.glob(os.path.join(self.outdir, "*.elf"))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001177 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001178 if (len(fns) != 1):
1179 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001180 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001181
1182 def __repr__(self):
1183 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1184
1185
Andrew Boie4ef16c52015-08-28 12:36:03 -07001186def defconfig_cb(context, goals, goal):
1187 if not goal.failed:
1188 return
1189
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001190
Andrew Boie4ef16c52015-08-28 12:36:03 -07001191 info("%sCould not build defconfig for %s%s" %
1192 (COLOR_RED, goal.name, COLOR_NORMAL));
1193 if INLINE_LOGS:
1194 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001195 data = fp.read()
1196 sys.stdout.write(data)
1197 if log_file:
1198 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001199 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001200 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001201
Andrew Boie6acbe632015-07-17 12:03:52 -07001202
1203class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001204 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001205
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001206 def __init__(self, arch_root, testcase_roots, outdir, coverage):
Andrew Boie6acbe632015-07-17 12:03:52 -07001207 # Keep track of which test cases we've filtered out and why
1208 discards = {}
1209 self.arches = {}
1210 self.testcases = {}
1211 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001212 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001213 self.instances = {}
1214 self.goals = None
1215 self.discards = None
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001216 self.coverage = coverage
Andrew Boie6acbe632015-07-17 12:03:52 -07001217
1218 arch_root = os.path.abspath(arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001219
Andrew Boie3d348712016-04-08 11:52:13 -07001220 for testcase_root in testcase_roots:
1221 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001222
Andrew Boie3d348712016-04-08 11:52:13 -07001223 debug("Reading test case configuration files under %s..." %
1224 testcase_root)
1225 for dirpath, dirnames, filenames in os.walk(testcase_root,
1226 topdown=True):
1227 verbose("scanning %s" % dirpath)
1228 if "testcase.ini" in filenames:
1229 verbose("Found test case in " + dirpath)
1230 dirnames[:] = []
Andrew Boie3ea78922016-03-24 14:46:00 -07001231 ini_path = os.path.join(dirpath, "testcase.ini")
1232 cp = SanityConfigParser(ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001233 workdir = os.path.relpath(dirpath, testcase_root)
1234
1235 for section in cp.sections():
1236 tc_dict = cp.get_section(section, testcase_valid_keys)
Andrew Boie3ea78922016-03-24 14:46:00 -07001237 tc = TestCase(testcase_root, workdir, section, tc_dict,
1238 ini_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001239 self.testcases[tc.name] = tc
Andrew Boie6acbe632015-07-17 12:03:52 -07001240
1241 debug("Reading architecture configuration files under %s..." % arch_root)
1242 for dirpath, dirnames, filenames in os.walk(arch_root):
1243 for filename in filenames:
1244 if filename.endswith(".ini"):
1245 fn = os.path.join(dirpath, filename)
1246 verbose("Found arch configuration " + fn)
1247 arch = Architecture(fn)
1248 self.arches[arch.name] = arch
1249 self.platforms.extend(arch.platforms)
1250
Andrew Boie05e3d7f2016-08-09 11:29:34 -07001251 # Build up a list of boards based on the presence of
1252 # boards/*/*_defconfig files. We want to make sure that the arch.ini
1253 # files are not missing any boards
1254 all_plats = [plat.name for plat in self.platforms]
1255 for dirpath, dirnames, filenames in os.walk(os.path.join(ZEPHYR_BASE,
1256 "boards")):
1257 for filename in filenames:
1258 if filename.endswith("_defconfig"):
1259 board_name = filename.replace("_defconfig", "")
1260 if board_name not in all_plats:
1261 error("Platform '%s' not specified in any arch .ini file and will not be tested"
1262 % board_name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 self.instances = {}
1264
1265 def get_last_failed(self):
1266 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001267 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001268 result = []
1269 with open(LAST_SANITY, "r") as fp:
1270 cr = csv.DictReader(fp)
1271 for row in cr:
1272 if row["passed"] == "True":
1273 continue
1274 test = row["test"]
1275 platform = row["platform"]
1276 result.append((test, platform))
1277 return result
1278
Anas Nashifdfa86e22016-10-24 17:08:56 -04001279 def apply_filters(self, platform_filter, arch_filter, tag_filter, exclude_tag,
Andrew Boie821d8322016-03-22 10:08:35 -07001280 config_filter, testcase_filter, last_failed, all_plats,
Kumar Galad5719a62016-10-26 12:30:26 -05001281 platform_limit, toolchain, extra_args, enable_ccache):
Andrew Boie6acbe632015-07-17 12:03:52 -07001282 instances = []
1283 discards = {}
1284 verbose("platform filter: " + str(platform_filter))
1285 verbose(" arch_filter: " + str(arch_filter))
1286 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001287 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001288 verbose(" config_filter: " + str(config_filter))
Kumar Galad5719a62016-10-26 12:30:26 -05001289 verbose(" enable_ccache: " + str(enable_ccache))
Andrew Boie6acbe632015-07-17 12:03:52 -07001290
1291 if last_failed:
1292 failed_tests = self.get_last_failed()
1293
Andrew Boie821d8322016-03-22 10:08:35 -07001294 default_platforms = False
1295
1296 if all_plats:
1297 info("Selecting all possible platforms per test case")
1298 # When --all used, any --platform arguments ignored
1299 platform_filter = []
1300 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001301 info("Selecting default platforms per test case")
1302 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001303
Kumar Galad5719a62016-10-26 12:30:26 -05001304 mg = MakeGenerator(self.outdir, ccache=enable_ccache)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001305 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001306 for tc_name, tc in self.testcases.items():
1307 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001308 instance_list = []
1309 for plat in arch.platforms:
1310 instance = TestInstance(tc, plat, self.outdir)
1311
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001312 if (arch_name == "unit") != (tc.type == "unit"):
1313 continue
1314
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001315 if tc.skip:
1316 continue
1317
Anas Nashif2cf0df02015-10-10 09:29:43 -04001318 if tag_filter and not tc.tags.intersection(tag_filter):
1319 continue
1320
Anas Nashifdfa86e22016-10-24 17:08:56 -04001321 if exclude_tag and tc.tags.intersection(exclude_tag):
1322 continue
1323
Anas Nashif2cf0df02015-10-10 09:29:43 -04001324 if testcase_filter and tc_name not in testcase_filter:
1325 continue
1326
1327 if last_failed and (tc.name, plat.name) not in failed_tests:
1328 continue
1329
1330 if arch_filter and arch_name not in arch_filter:
1331 continue
1332
1333 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1334 continue
1335
1336 if tc.arch_exclude and arch.name in tc.arch_exclude:
1337 continue
1338
1339 if tc.platform_exclude and plat.name in tc.platform_exclude:
1340 continue
1341
1342 if platform_filter and plat.name not in platform_filter:
1343 continue
1344
1345 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1346 continue
1347
Anas Nashif6d4ff232016-12-21 13:05:28 -05001348 if tc.tc_filter and (plat in arch.platforms[:platform_limit] or all_plats or platform_filter):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001349 args = tc.extra_args[:]
1350 args.extend(["ARCH=" + plat.arch.name,
Anas Nashifc7406082015-12-13 15:00:31 -05001351 "BOARD=" + plat.name, "initconfig"])
Andrew Boieba612002016-09-01 10:41:03 -07001352 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001353 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001354 # conf, gen_idt, etc aren't rebuilt for every combination,
Anas Nashif2cf0df02015-10-10 09:29:43 -04001355 # need a way to avoid different Make processe from clobbering
1356 # each other since they all try to build them simultaneously
1357
1358 o = os.path.join(self.outdir, plat.name, tc.path)
Andrew Boie41878222016-11-03 11:58:53 -07001359 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o,".config")
1360 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "initconfig"])
Anas Nashif2cf0df02015-10-10 09:29:43 -04001361 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location), o, args)
1362
1363 info("Building testcase defconfigs...")
1364 results = mg.execute(defconfig_cb)
1365
Andrew Boie08ce5a52016-02-22 13:28:10 -08001366 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001367 if goal.failed:
1368 raise SanityRuntimeError("Couldn't build some defconfigs")
1369
Andrew Boie08ce5a52016-02-22 13:28:10 -08001370 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001371 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001372 defconfig = {}
1373 with open(out_config, "r") as fp:
1374 for line in fp.readlines():
1375 m = TestSuite.config_re.match(line)
1376 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001377 if line.strip() and not line.startswith("#"):
1378 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001379 continue
1380 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001381 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001382
Andrew Boie08ce5a52016-02-22 13:28:10 -08001383 for tc_name, tc in self.testcases.items():
1384 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001385 instance_list = []
1386 for plat in arch.platforms:
1387 instance = TestInstance(tc, plat, self.outdir)
1388
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001389 if (arch_name == "unit") != (tc.type == "unit"):
1390 # Discard silently
1391 continue
1392
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001393 if tc.skip:
1394 discards[instance] = "Skip filter"
1395 continue
1396
Andrew Boie6acbe632015-07-17 12:03:52 -07001397 if tag_filter and not tc.tags.intersection(tag_filter):
1398 discards[instance] = "Command line testcase tag filter"
1399 continue
1400
Anas Nashifdfa86e22016-10-24 17:08:56 -04001401 if exclude_tag and tc.tags.intersection(exclude_tag):
1402 discards[instance] = "Command line testcase exclude filter"
1403 continue
1404
Andrew Boie6acbe632015-07-17 12:03:52 -07001405 if testcase_filter and tc_name not in testcase_filter:
1406 discards[instance] = "Testcase name filter"
1407 continue
1408
1409 if last_failed and (tc.name, plat.name) not in failed_tests:
1410 discards[instance] = "Passed or skipped during last run"
1411 continue
1412
1413 if arch_filter and arch_name not in arch_filter:
1414 discards[instance] = "Command line testcase arch filter"
1415 continue
1416
1417 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1418 discards[instance] = "Not in test case arch whitelist"
1419 continue
1420
Anas Nashif30d13872015-10-05 10:02:45 -04001421 if tc.arch_exclude and arch.name in tc.arch_exclude:
1422 discards[instance] = "In test case arch exclude"
1423 continue
1424
1425 if tc.platform_exclude and plat.name in tc.platform_exclude:
1426 discards[instance] = "In test case platform exclude"
1427 continue
1428
Andrew Boie6acbe632015-07-17 12:03:52 -07001429 if platform_filter and plat.name not in platform_filter:
1430 discards[instance] = "Command line platform filter"
1431 continue
1432
1433 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1434 discards[instance] = "Not in testcase platform whitelist"
1435 continue
1436
Javier B Perez4b554ba2016-08-15 13:25:33 -05001437 if toolchain and toolchain not in plat.supported_toolchains:
1438 discards[instance] = "Not supported by the toolchain"
1439 continue
1440
Andrew Boie3ea78922016-03-24 14:46:00 -07001441 defconfig = {"ARCH" : arch.name, "PLATFORM" : plat.name}
Javier B Perez79414542016-08-08 12:24:59 -05001442 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001443 for p, tdefconfig in tc.defconfig.items():
1444 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001445 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001446 break
1447
Andrew Boie3ea78922016-03-24 14:46:00 -07001448 if tc.tc_filter:
1449 try:
1450 res = expr_parser.parse(tc.tc_filter, defconfig)
1451 except SyntaxError as se:
1452 sys.stderr.write("Failed processing %s\n" % tc.inifile)
1453 raise se
1454 if not res:
1455 discards[instance] = ("defconfig doesn't satisfy expression '%s'" %
1456 tc.tc_filter)
1457 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001458
Andrew Boie6acbe632015-07-17 12:03:52 -07001459 instance_list.append(instance)
1460
1461 if not instance_list:
1462 # Every platform in this arch was rejected already
1463 continue
1464
1465 if default_platforms:
Andrew Boie821d8322016-03-22 10:08:35 -07001466 self.add_instances(instance_list[:platform_limit])
1467 for instance in instance_list[platform_limit:]:
1468 discards[instance] = "Not in first %d platform(s) for arch" % platform_limit
Andrew Boie6acbe632015-07-17 12:03:52 -07001469 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001470 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001471 self.discards = discards
1472 return discards
1473
Andrew Boie821d8322016-03-22 10:08:35 -07001474 def add_instances(self, ti_list):
1475 for ti in ti_list:
1476 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001477
Anas Nashife3febe92016-11-30 14:25:44 -05001478 def execute(self, cb, cb_context, build_only, enable_slow, enable_asserts, enable_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001479 extra_args, enable_ccache):
Daniel Leung6b170072016-04-07 12:10:25 -07001480
1481 def calc_one_elf_size(name, goal):
1482 if not goal.failed:
1483 i = self.instances[name]
1484 sc = i.calculate_sizes()
1485 goal.metrics["ram_size"] = sc.get_ram_size()
1486 goal.metrics["rom_size"] = sc.get_rom_size()
1487 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07001488
Anas Nashife3febe92016-11-30 14:25:44 -05001489 mg = MakeGenerator(self.outdir, asserts=enable_asserts, deprecations=enable_deprecations,
1490 ccache=enable_ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001491 for i in self.instances.values():
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001492 mg.add_test_instance(i, build_only, enable_slow, self.coverage, extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001493 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07001494
1495 # Parallelize size calculation
1496 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
1497 futures = [executor.submit(calc_one_elf_size, name, goal) \
1498 for name, goal in self.goals.items()]
1499 concurrent.futures.wait(futures)
1500
Andrew Boie6acbe632015-07-17 12:03:52 -07001501 return self.goals
1502
1503 def discard_report(self, filename):
1504 if self.discards == None:
1505 raise SanityRuntimeException("apply_filters() hasn't been run!")
1506
1507 with open(filename, "wb") as csvfile:
1508 fieldnames = ["test", "arch", "platform", "reason"]
1509 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1510 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001511 for instance, reason in self.discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001512 rowdict = {"test" : i.test.name,
1513 "arch" : i.platform.arch.name,
1514 "platform" : i.platform.name,
1515 "reason" : reason}
1516 cw.writerow(rowdict)
1517
1518 def compare_metrics(self, filename):
1519 # name, datatype, lower results better
1520 interesting_metrics = [("ram_size", int, True),
1521 ("rom_size", int, True)]
1522
1523 if self.goals == None:
1524 raise SanityRuntimeException("execute() hasn't been run!")
1525
1526 if not os.path.exists(filename):
1527 info("Cannot compare metrics, %s not found" % filename)
1528 return []
1529
1530 results = []
1531 saved_metrics = {}
1532 with open(filename) as fp:
1533 cr = csv.DictReader(fp)
1534 for row in cr:
1535 d = {}
1536 for m, _, _ in interesting_metrics:
1537 d[m] = row[m]
1538 saved_metrics[(row["test"], row["platform"])] = d
1539
Andrew Boie08ce5a52016-02-22 13:28:10 -08001540 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001541 i = self.instances[name]
1542 mkey = (i.test.name, i.platform.name)
1543 if mkey not in saved_metrics:
1544 continue
1545 sm = saved_metrics[mkey]
1546 for metric, mtype, lower_better in interesting_metrics:
1547 if metric not in goal.metrics:
1548 continue
1549 if sm[metric] == "":
1550 continue
1551 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07001552 if delta == 0:
1553 continue
1554 results.append((i, metric, goal.metrics[metric], delta,
1555 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07001556 return results
1557
1558 def testcase_report(self, filename):
1559 if self.goals == None:
1560 raise SanityRuntimeException("execute() hasn't been run!")
1561
Andrew Boie08ce5a52016-02-22 13:28:10 -08001562 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07001563 fieldnames = ["test", "arch", "platform", "passed", "status",
1564 "extra_args", "qemu", "qemu_time", "ram_size",
1565 "rom_size"]
1566 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1567 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001568 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001569 i = self.instances[name]
1570 rowdict = {"test" : i.test.name,
1571 "arch" : i.platform.arch.name,
1572 "platform" : i.platform.name,
1573 "extra_args" : " ".join(i.test.extra_args),
1574 "qemu" : i.platform.qemu_support}
1575 if goal.failed:
1576 rowdict["passed"] = False
1577 rowdict["status"] = goal.reason
1578 else:
1579 rowdict["passed"] = True
1580 if goal.qemu:
1581 rowdict["qemu_time"] = goal.metrics["qemu_time"]
1582 rowdict["ram_size"] = goal.metrics["ram_size"]
1583 rowdict["rom_size"] = goal.metrics["rom_size"]
1584 cw.writerow(rowdict)
1585
1586
1587def parse_arguments():
1588
1589 parser = argparse.ArgumentParser(description = __doc__,
1590 formatter_class = argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05001591 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07001592
1593 parser.add_argument("-p", "--platform", action="append",
Andrew Boie821d8322016-03-22 10:08:35 -07001594 help="Platform filter for testing. This option may be used multiple "
1595 "times. Testcases will only be built/run on the platforms "
1596 "specified. If this option is not used, then N platforms will "
1597 "automatically be chosen from each arch to build and test, "
1598 "where N is provided by the --platform-limit option.")
1599 parser.add_argument("-L", "--platform-limit", action="store", type=int,
1600 metavar="N", default=1,
1601 help="Controls what platforms are tested if --platform or "
1602 "--all are not used. For each architecture specified by "
1603 "--arch (defaults to all of them), choose the first "
1604 "N platforms to test in the arch-specific .ini file "
1605 "'platforms' list. Defaults to 1.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001606 parser.add_argument("-a", "--arch", action="append",
1607 help="Arch filter for testing. Takes precedence over --platform. "
1608 "If unspecified, test all arches. Multiple invocations "
1609 "are treated as a logical 'or' relationship")
1610 parser.add_argument("-t", "--tag", action="append",
1611 help="Specify tags to restrict which tests to run by tag value. "
1612 "Default is to not do any tag filtering. Multiple invocations "
1613 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04001614 parser.add_argument("-e", "--exclude-tag", action="append",
1615 help="Specify tags of tests that should not run."
1616 "Default is to run all tests with all tags.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001617 parser.add_argument("-f", "--only-failed", action="store_true",
1618 help="Run only those tests that failed the previous sanity check "
1619 "invocation.")
1620 parser.add_argument("-c", "--config", action="append",
1621 help="Specify platform configuration values filtering. This can be "
1622 "specified two ways: <config>=<value> or just <config>. The "
Andrew Boie41878222016-11-03 11:58:53 -07001623 "defconfig for all platforms will be "
Andrew Boie6acbe632015-07-17 12:03:52 -07001624 "checked. For the <config>=<value> case, only match defconfig "
1625 "that have that value defined. For the <config> case, match "
1626 "defconfig that have that value assigned to any value. "
1627 "Prepend a '!' to invert the match.")
1628 parser.add_argument("-s", "--test", action="append",
1629 help="Run only the specified test cases. These are named by "
1630 "<path to test project relative to "
1631 "--testcase-root>/<testcase.ini section name>")
1632 parser.add_argument("-l", "--all", action="store_true",
Andrew Boie821d8322016-03-22 10:08:35 -07001633 help="Build/test on all platforms. Any --platform arguments "
1634 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001635
1636 parser.add_argument("-o", "--testcase-report",
1637 help="Output a CSV spreadsheet containing results of the test run")
1638 parser.add_argument("-d", "--discard-report",
1639 help="Output a CSV spreadhseet showing tests that were skipped "
1640 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07001641 parser.add_argument("--compare-report",
1642 help="Use this report file for size comparision")
1643
Kumar Galad5719a62016-10-26 12:30:26 -05001644 parser.add_argument("--ccache", action="store_const", const=1, default=0,
1645 help="Enable the use of ccache when building")
1646
Andrew Boie6acbe632015-07-17 12:03:52 -07001647 parser.add_argument("-y", "--dry-run", action="store_true",
1648 help="Create the filtered list of test cases, but don't actually "
1649 "run them. Useful if you're just interested in "
1650 "--discard-report")
1651
1652 parser.add_argument("-r", "--release", action="store_true",
1653 help="Update the benchmark database with the results of this test "
1654 "run. Intended to be run by CI when tagging an official "
1655 "release. This database is used as a basis for comparison "
1656 "when looking for deltas in metrics such as footprint")
1657 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
1658 help="Treat warning conditions as errors")
1659 parser.add_argument("-v", "--verbose", action="count", default=0,
1660 help="Emit debugging information, call multiple times to increase "
1661 "verbosity")
1662 parser.add_argument("-i", "--inline-logs", action="store_true",
1663 help="Upon test failure, print relevant log data to stdout "
1664 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001665 parser.add_argument("--log-file", metavar="FILENAME", action="store",
1666 help="log also to file")
Andrew Boie6acbe632015-07-17 12:03:52 -07001667 parser.add_argument("-m", "--last-metrics", action="store_true",
1668 help="Instead of comparing metrics from the last --release, "
1669 "compare with the results of the previous sanity check "
1670 "invocation")
1671 parser.add_argument("-u", "--no-update", action="store_true",
1672 help="do not update the results of the last run of the sanity "
1673 "checks")
1674 parser.add_argument("-b", "--build-only", action="store_true",
1675 help="Only build the code, do not execute any of it in QEMU")
1676 parser.add_argument("-j", "--jobs", type=int,
1677 help="Number of cores to use when building, defaults to "
1678 "number of CPUs * 2")
1679 parser.add_argument("-H", "--footprint-threshold", type=float, default=5,
1680 help="When checking test case footprint sizes, warn the user if "
1681 "the new app size is greater then the specified percentage "
1682 "from the last release. Default is 5. 0 to warn on any "
1683 "increase on app size")
Andrew Boieea7928f2015-08-14 14:27:38 -07001684 parser.add_argument("-D", "--all-deltas", action="store_true",
1685 help="Show all footprint deltas, positive or negative. Implies "
1686 "--footprint-threshold=0")
Andrew Boie6acbe632015-07-17 12:03:52 -07001687 parser.add_argument("-O", "--outdir",
1688 default="%s/sanity-out" % ZEPHYR_BASE,
1689 help="Output directory for logs and binaries.")
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001690 parser.add_argument("-n", "--no-clean", action="store_true",
1691 help="Do not delete the outdir before building. Will result in "
1692 "faster compilation since builds will be incremental")
Andrew Boie3d348712016-04-08 11:52:13 -07001693 parser.add_argument("-T", "--testcase-root", action="append", default=[],
Andrew Boie6acbe632015-07-17 12:03:52 -07001694 help="Base directory to recursively search for test cases. All "
Andrew Boie3d348712016-04-08 11:52:13 -07001695 "testcase.ini files under here will be processed. May be "
1696 "called multiple times. Defaults to the 'samples' and "
1697 "'tests' directories in the Zephyr tree.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001698 parser.add_argument("-A", "--arch-root",
1699 default="%s/scripts/sanity_chk/arches" % ZEPHYR_BASE,
1700 help="Directory to search for arch configuration files. All .ini "
1701 "files in the directory will be processed.")
Andrew Boiebbd670c2015-08-17 13:16:11 -07001702 parser.add_argument("-z", "--size", action="append",
1703 help="Don't run sanity checks. Instead, produce a report to "
1704 "stdout detailing RAM/ROM sizes on the specified filenames. "
1705 "All other command line arguments ignored.")
Andrew Boie6bb087c2016-02-10 13:39:00 -08001706 parser.add_argument("-S", "--enable-slow", action="store_true",
1707 help="Execute time-consuming test cases that have been marked "
1708 "as 'slow' in testcase.ini. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07001709 parser.add_argument("-R", "--enable-asserts", action="store_true",
1710 help="Build all test cases with assertions enabled.")
Anas Nashife3febe92016-11-30 14:25:44 -05001711 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
1712 help="Error on deprecation warnings.")
Andrew Boieba612002016-09-01 10:41:03 -07001713 parser.add_argument("-x", "--extra-args", action="append", default=[],
1714 help="Extra arguments to pass to the build when compiling test "
1715 "cases. May be called multiple times. These will be passed "
1716 "in after any sanitycheck-supplied options.")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001717 parser.add_argument("-C", "--coverage", action="store_true",
1718 help="Scan for unit test coverage with gcov + lcov.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001719
1720 return parser.parse_args()
1721
1722def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01001723 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001724 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001725 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08001726
1727 try:
1728 with open(filename) as fp:
1729 data = fp.read()
1730 except Exception as e:
1731 data = "Unable to read log data (%s)\n" % (str(e))
1732
1733 sys.stdout.write(data)
1734 if log_file:
1735 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001736 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001737 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001738 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07001739
1740def terse_test_cb(instances, goals, goal):
1741 total_tests = len(goals)
1742 total_done = 0
1743 total_failed = 0
1744
Andrew Boie08ce5a52016-02-22 13:28:10 -08001745 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001746 if g.finished:
1747 total_done += 1
1748 if g.failed:
1749 total_failed += 1
1750
1751 if goal.failed:
1752 i = instances[goal.name]
1753 info("\n\n{:<25} {:<50} {}FAILED{}: {}".format(i.platform.name,
1754 i.test.name, COLOR_RED, COLOR_NORMAL, goal.reason))
1755 log_info(goal.get_error_log())
1756 info("")
1757
Andrew Boie7a992ae2017-01-17 10:40:02 -08001758 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" % (
Andrew Boie6acbe632015-07-17 12:03:52 -07001759 COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
Andrew Boie7a992ae2017-01-17 10:40:02 -08001760 int((float(total_done) / total_tests) * 100),
Andrew Boie6acbe632015-07-17 12:03:52 -07001761 COLOR_RED if total_failed > 0 else COLOR_NORMAL,
1762 total_failed, COLOR_NORMAL))
1763 sys.stdout.flush()
1764
1765def chatty_test_cb(instances, goals, goal):
1766 i = instances[goal.name]
1767
1768 if VERBOSE < 2 and not goal.finished:
1769 return
1770
1771 if goal.failed:
1772 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
1773 elif goal.finished:
1774 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
1775 else:
1776 status = goal.make_state
1777
1778 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
1779 if goal.failed:
1780 log_info(goal.get_error_log())
1781
Andrew Boiebbd670c2015-08-17 13:16:11 -07001782
1783def size_report(sc):
1784 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07001785 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07001786 for i in range(len(sc.sections)):
1787 v = sc.sections[i]
1788
Andrew Boie73b4ee62015-10-07 11:33:22 -07001789 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
1790 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
1791 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07001792
Andrew Boie73b4ee62015-10-07 11:33:22 -07001793 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
1794 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001795 info("")
1796
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001797def generate_coverage(outdir, ignores):
1798 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
1799 coveragefile = os.path.join(outdir, "coverage.info")
1800 ztestfile = os.path.join(outdir, "ztest.info")
1801 subprocess.call(["lcov", "--capture", "--directory", outdir,
1802 "--output-file", coveragefile], stdout=coveragelog)
1803 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
1804 subprocess.call(["lcov", "--extract", coveragefile,
1805 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
1806 "--output-file", ztestfile], stdout=coveragelog)
1807 subprocess.call(["lcov", "--remove", ztestfile,
1808 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
1809 "--output-file", ztestfile], stdout=coveragelog)
1810 for i in ignores:
1811 subprocess.call(["lcov", "--remove", coveragefile, i,
1812 "--output-file", coveragefile], stdout=coveragelog)
1813 subprocess.call(["genhtml", "-output-directory",
1814 os.path.join(outdir, "coverage"),
1815 coveragefile, ztestfile], stdout=coveragelog)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001816
Andrew Boie6acbe632015-07-17 12:03:52 -07001817def main():
Andrew Boie4b182472015-07-31 12:25:22 -07001818 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001819 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Andrew Boie6acbe632015-07-17 12:03:52 -07001820 args = parse_arguments()
Javier B Perez4b554ba2016-08-15 13:25:33 -05001821 toolchain = os.environ.get("ZEPHYR_GCC_VARIANT", None)
Andrew Boie1e4e68b2017-02-10 11:40:54 -08001822 if toolchain == "zephyr":
1823 os.environ["DISABLE_TRYRUN"] = "1"
Andrew Boiebbd670c2015-08-17 13:16:11 -07001824
1825 if args.size:
1826 for fn in args.size:
Andrew Boie52fef672016-11-29 12:21:59 -08001827 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001828 sys.exit(0)
1829
Andrew Boie6acbe632015-07-17 12:03:52 -07001830 VERBOSE += args.verbose
1831 INLINE_LOGS = args.inline_logs
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001832 if args.log_file:
1833 log_file = open(args.log_file, "w")
Andrew Boie6acbe632015-07-17 12:03:52 -07001834 if args.jobs:
Daniel Leung6b170072016-04-07 12:10:25 -07001835 CPU_COUNTS = args.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07001836
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001837 if os.path.exists(args.outdir) and not args.no_clean:
Andrew Boie6acbe632015-07-17 12:03:52 -07001838 info("Cleaning output directory " + args.outdir)
1839 shutil.rmtree(args.outdir)
1840
Andrew Boie3d348712016-04-08 11:52:13 -07001841 if not args.testcase_root:
1842 args.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
1843 os.path.join(ZEPHYR_BASE, "samples")]
1844
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001845 ts = TestSuite(args.arch_root, args.testcase_root, args.outdir, args.coverage)
Anas Nashifdfa86e22016-10-24 17:08:56 -04001846 discards = ts.apply_filters(args.platform, args.arch, args.tag, args.exclude_tag, args.config,
Andrew Boie821d8322016-03-22 10:08:35 -07001847 args.test, args.only_failed, args.all,
Kumar Galad5719a62016-10-26 12:30:26 -05001848 args.platform_limit, toolchain, args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001849
1850 if args.discard_report:
1851 ts.discard_report(args.discard_report)
1852
1853 if VERBOSE:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001854 for i, reason in discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001855 debug("{:<25} {:<50} {}SKIPPED{}: {}".format(i.platform.name,
1856 i.test.name, COLOR_YELLOW, COLOR_NORMAL, reason))
1857
1858 info("%d tests selected, %d tests discarded due to filters" %
1859 (len(ts.instances), len(discards)))
1860
1861 if args.dry_run:
1862 return
1863
1864 if VERBOSE or not TERMINAL:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001865 goals = ts.execute(chatty_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05001866 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001867 args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001868 else:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001869 goals = ts.execute(terse_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05001870 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001871 args.extra_args, args.ccache)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001872 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07001873
Daniel Leung7f850102016-04-08 11:07:32 -07001874 # figure out which report to use for size comparison
1875 if args.compare_report:
1876 report_to_use = args.compare_report
1877 elif args.last_metrics:
1878 report_to_use = LAST_SANITY
1879 else:
1880 report_to_use = RELEASE_DATA
1881
1882 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07001883 warnings = 0
1884 if deltas:
Andrew Boieea7928f2015-08-14 14:27:38 -07001885 for i, metric, value, delta, lower_better in deltas:
1886 if not args.all_deltas and ((delta < 0 and lower_better) or
1887 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07001888 continue
1889
Andrew Boieea7928f2015-08-14 14:27:38 -07001890 percentage = (float(delta) / float(value - delta))
1891 if not args.all_deltas and (percentage <
1892 (args.footprint_threshold / 100.0)):
1893 continue
1894
Daniel Leung00525c22016-04-11 10:27:56 -07001895 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07001896 i.platform.name, i.test.name, COLOR_YELLOW,
1897 "INFO" if args.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07001898 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07001899 warnings += 1
1900
1901 if warnings:
1902 info("Deltas based on metrics from last %s" %
1903 ("release" if not args.last_metrics else "run"))
1904
1905 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08001906 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001907 if goal.failed:
1908 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001909 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07001910 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
1911 (COLOR_RED, COLOR_NORMAL, goal.name,
1912 str(goal.metrics["unrecognized"])))
1913 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001914
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001915 if args.coverage:
1916 info("Generating coverage files...")
1917 generate_coverage(args.outdir, ["tests/*", "samples/*"])
1918
Andrew Boie4b182472015-07-31 12:25:22 -07001919 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Andrew Boie6acbe632015-07-17 12:03:52 -07001920 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
1921 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
Andrew Boie4b182472015-07-31 12:25:22 -07001922 warnings, COLOR_NORMAL, time.time() - start_time))
Andrew Boie6acbe632015-07-17 12:03:52 -07001923
1924 if args.testcase_report:
1925 ts.testcase_report(args.testcase_report)
1926 if not args.no_update:
1927 ts.testcase_report(LAST_SANITY)
1928 if args.release:
1929 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001930 if log_file:
1931 log_file.close()
Andrew Boie6acbe632015-07-17 12:03:52 -07001932 if failed or (warnings and args.warnings_as_errors):
1933 sys.exit(1)
1934
1935if __name__ == "__main__":
1936 main()