blob: 0c480bd4fdea96df64972926fd3f539f01137de3 [file] [log] [blame]
Andrew Boie08ce5a52016-02-22 13:28:10 -08001#!/usr/bin/env python3
Anas Nashifa792a3d2017-04-04 18:47:49 -04002# vim: set syntax=python ts=4 :
Andrew Boie6acbe632015-07-17 12:03:52 -07003"""Zephyr Sanity Tests
4
5This script scans for the set of unit test applications in the git
6repository and attempts to execute them. By default, it tries to
7build each test case on one platform per architecture, using a precedence
Paul Sokolovskyff70add2017-06-16 01:31:54 +03008list defined in an architecture configuration file, and if possible
Andrew Boie6acbe632015-07-17 12:03:52 -07009run the tests in the QEMU emulator.
10
Anas Nashifa792a3d2017-04-04 18:47:49 -040011Test cases are detected by the presence of a 'testcase.yaml' or a sample.yaml
12files in the application's project directory. This file may contain one or more
13blocks, each identifying a test scenario. The title of the block is a name for
14the test case, which only needs to be unique for the test cases specified in
15that testcase meta-data. The full canonical name for each test case is <path to
16test case>/<block>.
Andrew Boie6acbe632015-07-17 12:03:52 -070017
Anas Nashifa792a3d2017-04-04 18:47:49 -040018Each test block in the testcase meta data can define the following key/value pairs:
Andrew Boie6acbe632015-07-17 12:03:52 -070019
Anas Nashifa792a3d2017-04-04 18:47:49 -040020 tags: <list of tags> (required)
Andrew Boie6acbe632015-07-17 12:03:52 -070021 A set of string tags for the testcase. Usually pertains to
22 functional domains but can be anything. Command line invocations
23 of this script can filter the set of tests to run based on tag.
24
Anas Nashifa792a3d2017-04-04 18:47:49 -040025 skip: <True|False> (default False)
Anas Nashif2bd99bc2015-10-12 13:10:57 -040026 skip testcase unconditionally. This can be used for broken tests.
27
Anas Nashifa792a3d2017-04-04 18:47:49 -040028 slow: <True|False> (default False)
Andrew Boie6bb087c2016-02-10 13:39:00 -080029 Don't run this test case unless --enable-slow was passed in on the
30 command line. Intended for time-consuming test cases that are only
31 run under certain circumstances, like daily builds. These test cases
32 are still compiled.
33
Anas Nashifa792a3d2017-04-04 18:47:49 -040034 extra_args: <list of extra arguments>
Andrew Boie6acbe632015-07-17 12:03:52 -070035 Extra arguments to pass to Make when building or running the
36 test case.
37
Anas Nashifa792a3d2017-04-04 18:47:49 -040038 build_only: <True|False> (default False)
Andrew Boie6acbe632015-07-17 12:03:52 -070039 If true, don't try to run the test under QEMU even if the
40 selected platform supports it.
41
Anas Nashifa792a3d2017-04-04 18:47:49 -040042 build_on_all: <True|False> (default False)
43 If true, attempt to build test on all available platforms.
44
45 depends_on: <list of features>
46 A board or platform can announce what features it supports, this option
47 will enable the test only those platforms that provide this feature.
48
49 min_ram: <integer>
50 minimum amount of RAM needed for this test to build and run. This is
51 compared with information provided by the board metadata.
52
53 min_flash: <integer>
54 minimum amount of ROM needed for this test to build and run. This is
55 compared with information provided by the board metadata.
56
57 timeout: <number of seconds>
Andrew Boie6acbe632015-07-17 12:03:52 -070058 Length of time to run test in QEMU before automatically killing it.
59 Default to 60 seconds.
60
Anas Nashifa792a3d2017-04-04 18:47:49 -040061 arch_whitelist: <list of arches, such as x86, arm, arc>
Andrew Boie6acbe632015-07-17 12:03:52 -070062 Set of architectures that this test case should only be run for.
63
Anas Nashifa792a3d2017-04-04 18:47:49 -040064 arch_exclude: <list of arches, such as x86, arm, arc>
Anas Nashif30d13872015-10-05 10:02:45 -040065 Set of architectures that this test case should not run on.
66
Anas Nashifa792a3d2017-04-04 18:47:49 -040067 platform_whitelist: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040068 Set of platforms that this test case should only be run for.
69
Anas Nashifa792a3d2017-04-04 18:47:49 -040070 platform_exclude: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040071 Set of platforms that this test case should not run on.
Andrew Boie6acbe632015-07-17 12:03:52 -070072
Anas Nashifa792a3d2017-04-04 18:47:49 -040073 extra_sections: <list of extra binary sections>
Andrew Boie52fef672016-11-29 12:21:59 -080074 When computing sizes, sanitycheck will report errors if it finds
75 extra, unexpected sections in the Zephyr binary unless they are named
76 here. They will not be included in the size calculation.
77
Anas Nashifa792a3d2017-04-04 18:47:49 -040078 filter: <expression>
Andrew Boie3ea78922016-03-24 14:46:00 -070079 Filter whether the testcase should be run by evaluating an expression
80 against an environment containing the following values:
81
82 { ARCH : <architecture>,
83 PLATFORM : <platform>,
Javier B Perez79414542016-08-08 12:24:59 -050084 <all CONFIG_* key/value pairs in the test's generated defconfig>,
85 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070086 }
87
88 The grammar for the expression language is as follows:
89
90 expression ::= expression "and" expression
91 | expression "or" expression
92 | "not" expression
93 | "(" expression ")"
94 | symbol "==" constant
95 | symbol "!=" constant
96 | symbol "<" number
97 | symbol ">" number
98 | symbol ">=" number
99 | symbol "<=" number
100 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700101 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -0700102 | symbol
103
104 list ::= "[" list_contents "]"
105
106 list_contents ::= constant
107 | list_contents "," constant
108
109 constant ::= number
110 | string
111
112
113 For the case where expression ::= symbol, it evaluates to true
114 if the symbol is defined to a non-empty string.
115
116 Operator precedence, starting from lowest to highest:
117
118 or (left associative)
119 and (left associative)
120 not (right associative)
121 all comparison operators (non-associative)
122
123 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
124 are all syntactic sugar for these expressions. For instance
125
126 arch_exclude = x86 arc
127
128 Is the same as:
129
130 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700131
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700132 The ':' operator compiles the string argument as a regular expression,
133 and then returns a true value only if the symbol's value in the environment
134 matches. For example, if CONFIG_SOC="quark_se" then
135
136 filter = CONFIG_SOC : "quark.*"
137
138 Would match it.
139
Anas Nashifa792a3d2017-04-04 18:47:49 -0400140The set of test cases that actually run depends on directives in the testcase
141filed and options passed in on the command line. If there is any confusion,
142running with -v or --discard-report can help show why particular test cases
143were skipped.
Andrew Boie6acbe632015-07-17 12:03:52 -0700144
145Metrics (such as pass/fail state and binary size) for the last code
146release are stored in scripts/sanity_chk/sanity_last_release.csv.
147To update this, pass the --all --release options.
148
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500149To load arguments from a file, write '+' before the file name, e.g.,
150+file_name. File content must be one or more valid arguments separated by
151line break instead of white spaces.
152
Andrew Boie6acbe632015-07-17 12:03:52 -0700153Most everyday users will run with no arguments.
154"""
155
156import argparse
157import os
158import sys
Andrew Boie08ce5a52016-02-22 13:28:10 -0800159import configparser
Andrew Boie6acbe632015-07-17 12:03:52 -0700160import re
161import tempfile
162import subprocess
163import multiprocessing
164import select
165import shutil
166import signal
167import threading
168import time
169import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700170import glob
Daniel Leung6b170072016-04-07 12:10:25 -0700171import concurrent
172import concurrent.futures
Anas Nashifb3311ed2017-04-13 14:44:48 -0400173import xml.etree.ElementTree as ET
Anas Nashife6fcc012017-05-17 09:29:09 -0400174from xml.sax.saxutils import escape
Anas Nashif035799f2017-05-13 21:31:53 -0400175from collections import OrderedDict
176from itertools import islice
Anas Nashifa792a3d2017-04-04 18:47:49 -0400177import yaml
Andrew Boie6acbe632015-07-17 12:03:52 -0700178
179if "ZEPHYR_BASE" not in os.environ:
Anas Nashif427cdd32015-08-06 07:25:42 -0400180 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700181 exit(1)
182ZEPHYR_BASE = os.environ["ZEPHYR_BASE"]
Andrew Boie3ea78922016-03-24 14:46:00 -0700183
184sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
185
186import expr_parser
187
Andrew Boie6acbe632015-07-17 12:03:52 -0700188VERBOSE = 0
189LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
190 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400191LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
192 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700193RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
194 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700195CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700196
197if os.isatty(sys.stdout.fileno()):
198 TERMINAL = True
199 COLOR_NORMAL = '\033[0m'
200 COLOR_RED = '\033[91m'
201 COLOR_GREEN = '\033[92m'
202 COLOR_YELLOW = '\033[93m'
203else:
204 TERMINAL = False
205 COLOR_NORMAL = ""
206 COLOR_RED = ""
207 COLOR_GREEN = ""
208 COLOR_YELLOW = ""
209
210class SanityCheckException(Exception):
211 pass
212
213class SanityRuntimeError(SanityCheckException):
214 pass
215
216class ConfigurationError(SanityCheckException):
217 def __init__(self, cfile, message):
218 self.cfile = cfile
219 self.message = message
220
221 def __str__(self):
222 return repr(self.cfile + ": " + self.message)
223
224class MakeError(SanityCheckException):
225 pass
226
227class BuildError(MakeError):
228 pass
229
230class ExecutionError(MakeError):
231 pass
232
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800233log_file = None
234
Andrew Boie6acbe632015-07-17 12:03:52 -0700235# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800236def info(what):
237 sys.stdout.write(what + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800238 if log_file:
239 log_file.write(what + "\n")
240 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700241
242def error(what):
243 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800244 if log_file:
245 log_file(what + "\n")
246 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700247
Andrew Boie08ce5a52016-02-22 13:28:10 -0800248def debug(what):
249 if VERBOSE >= 1:
250 info(what)
251
Andrew Boie6acbe632015-07-17 12:03:52 -0700252def verbose(what):
253 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800254 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700255
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300256class Handler:
257 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
258 RUN_FAILED = "PROJECT EXECUTION FAILED"
259 def __init__(self, name, outdir, log_fn, timeout, unit=False):
260 """Constructor
261
262 @param name Arbitrary name of the created thread
263 @param outdir Working directory, should be where qemu.pid gets created
264 by kbuild
265 @param log_fn Absolute path to write out QEMU's log data
266 @param timeout Kill the QEMU process if it doesn't finish up within
267 the given number of seconds
268 """
269 self.lock = threading.Lock()
270 self.state = "waiting"
271 self.metrics = {}
272 self.metrics["qemu_time"] = 0
273 self.metrics["ram_size"] = 0
274 self.metrics["rom_size"] = 0
275 self.unit = unit
276
277 def set_state(self, state, metrics):
278 self.lock.acquire()
279 self.state = state
280 self.metrics.update(metrics)
281 self.lock.release()
282
283 def get_state(self):
284 self.lock.acquire()
285 ret = (self.state, self.metrics)
286 self.lock.release()
287 return ret
288
289class UnitHandler(Handler):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300290 def __init__(self, name, sourcedir, outdir, run_log, valgrind_log, timeout):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300291 """Constructor
292
293 @param name Arbitrary name of the created thread
294 @param outdir Working directory containing the test binary
295 @param run_log Absolute path to runtime logs
296 @param valgrind Absolute path to valgrind's log
297 @param timeout Kill the QEMU process if it doesn't finish up within
298 the given number of seconds
299 """
300 super().__init__(name, outdir, run_log, timeout, True)
301
302 self.timeout = timeout
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300303 self.sourcedir = sourcedir
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300304 self.outdir = outdir
305 self.run_log = run_log
306 self.valgrind_log = valgrind_log
307 self.returncode = 0
308 self.set_state("running", {})
309
310 def handle(self):
311 out_state = "failed"
312
313 with open(self.run_log, "wt") as rl, open(self.valgrind_log, "wt") as vl:
314 try:
315 binary = os.path.join(self.outdir, "testbinary")
316 command = [binary]
317 if shutil.which("valgrind"):
318 command = ["valgrind", "--error-exitcode=2",
319 "--leak-check=full"] + command
320 returncode = subprocess.call(command, timeout=self.timeout,
321 stdout=rl, stderr=vl)
322 self.returncode = returncode
323 if returncode != 0:
324 if self.returncode == 1:
325 out_state = "failed"
326 else:
327 out_state = "failed valgrind"
328 else:
329 out_state = "passed"
330 except subprocess.TimeoutExpired:
331 out_state = "timeout"
332 self.returncode = 1
333
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300334 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
335
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300336 self.set_state(out_state, {})
337
338class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700339 """Spawns a thread to monitor QEMU output from pipes
340
341 We pass QEMU_PIPE to 'make qemu' and monitor the pipes for output.
342 We need to do this as once qemu starts, it runs forever until killed.
343 Test cases emit special messages to the console as they run, we check
344 for these to collect whether the test passed or failed.
345 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700346
347 @staticmethod
348 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results):
349 fifo_in = fifo_fn + ".in"
350 fifo_out = fifo_fn + ".out"
351
352 # These in/out nodes are named from QEMU's perspective, not ours
353 if os.path.exists(fifo_in):
354 os.unlink(fifo_in)
355 os.mkfifo(fifo_in)
356 if os.path.exists(fifo_out):
357 os.unlink(fifo_out)
358 os.mkfifo(fifo_out)
359
360 # We don't do anything with out_fp but we need to open it for
361 # writing so that QEMU doesn't block, due to the way pipes work
362 out_fp = open(fifo_in, "wb")
363 # Disable internal buffering, we don't
364 # want read() or poll() to ever block if there is data in there
365 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800366 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700367
368 start_time = time.time()
369 timeout_time = start_time + timeout
370 p = select.poll()
371 p.register(in_fp, select.POLLIN)
372
373 metrics = {}
374 line = ""
375 while True:
376 this_timeout = int((timeout_time - time.time()) * 1000)
377 if this_timeout < 0 or not p.poll(this_timeout):
378 out_state = "timeout"
379 break
380
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500381 try:
382 c = in_fp.read(1).decode("utf-8")
383 except UnicodeDecodeError:
384 # Test is writing something weird, fail
385 out_state = "unexpected byte"
386 break
387
Andrew Boie6acbe632015-07-17 12:03:52 -0700388 if c == "":
389 # EOF, this shouldn't happen unless QEMU crashes
390 out_state = "unexpected eof"
391 break
392 line = line + c
393 if c != "\n":
394 continue
395
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300396 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700397 log_out_fp.write(line)
398 log_out_fp.flush()
399 line = line.strip()
400 verbose("QEMU: %s" % line)
401
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300402 if line == handler.RUN_PASSED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700403 out_state = "passed"
404 break
405
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300406 if line == handler.RUN_FAILED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700407 out_state = "failed"
408 break
409
410 # TODO: Add support for getting numerical performance data
411 # from test cases. Will involve extending test case reporting
412 # APIs. Add whatever gets reported to the metrics dictionary
413 line = ""
414
415 metrics["qemu_time"] = time.time() - start_time
416 verbose("QEMU complete (%s) after %f seconds" %
417 (out_state, metrics["qemu_time"]))
418 handler.set_state(out_state, metrics)
419
420 log_out_fp.close()
421 out_fp.close()
422 in_fp.close()
423
424 pid = int(open(pid_fn).read())
425 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800426 try:
427 os.kill(pid, signal.SIGTERM)
428 except ProcessLookupError:
429 # Oh well, as long as it's dead! User probably sent Ctrl-C
430 pass
431
Andrew Boie6acbe632015-07-17 12:03:52 -0700432 os.unlink(fifo_in)
433 os.unlink(fifo_out)
434
Andrew Boie6acbe632015-07-17 12:03:52 -0700435 def __init__(self, name, outdir, log_fn, timeout):
436 """Constructor
437
438 @param name Arbitrary name of the created thread
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300439 @param outdir Working directory, should be where qemu.pid gets created
Andrew Boie6acbe632015-07-17 12:03:52 -0700440 by kbuild
441 @param log_fn Absolute path to write out QEMU's log data
442 @param timeout Kill the QEMU process if it doesn't finish up within
443 the given number of seconds
444 """
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300445 super().__init__(name, outdir, log_fn, timeout)
Andrew Boie6acbe632015-07-17 12:03:52 -0700446 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700447
448 # We pass this to QEMU which looks for fifos with .in and .out
449 # suffixes.
450 self.fifo_fn = os.path.join(outdir, "qemu-fifo")
451
452 self.pid_fn = os.path.join(outdir, "qemu.pid")
453 if os.path.exists(self.pid_fn):
454 os.unlink(self.pid_fn)
455
456 self.log_fn = log_fn
457 self.thread = threading.Thread(name=name, target=QEMUHandler._thread,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300458 args=(self, timeout, outdir,
459 self.log_fn, self.fifo_fn,
460 self.pid_fn, self.results))
Andrew Boie6acbe632015-07-17 12:03:52 -0700461 self.thread.daemon = True
462 verbose("Spawning QEMU process for %s" % name)
463 self.thread.start()
464
Andrew Boie6acbe632015-07-17 12:03:52 -0700465 def get_fifo(self):
466 return self.fifo_fn
467
Andrew Boie6acbe632015-07-17 12:03:52 -0700468class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700469
470 alloc_sections = ["bss", "noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800471 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500472 "_k_memory_pool", "exceptions", "initshell",
473 "_static_thread_area", "_k_timer_area",
474 "_k_mem_slab_area", "_k_mem_pool_area",
475 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
476 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
Tomasz Bursztykab56c4df2016-08-18 14:07:22 +0200477 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Tomasz Bursztykae38a9e82017-03-08 09:30:03 +0100478 "net_if", "net_if_event", "net_stack", "net_l2_data",
Johan Hedberg97039272017-06-03 19:20:27 +0300479 "_k_queue_area", "_net_buf_pool_area" ]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700480 # These get copied into RAM only on non-XIP
Andrew Boie3b930212016-06-07 13:11:45 -0700481 ro_sections = ["text", "ctors", "init_array", "reset",
Anas Nashifbfcdfaf2016-12-15 10:02:14 -0500482 "rodata", "devconfig", "net_l2", "vector"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700483
Andrew Boie52fef672016-11-29 12:21:59 -0800484 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700485 """Constructor
486
Andrew Boiebbd670c2015-08-17 13:16:11 -0700487 @param filename Path to the output binary
488 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700489 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700490 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700491 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700492 magic = f.read(4)
493
Andrew Boie08ce5a52016-02-22 13:28:10 -0800494 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700495 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700496
497 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
498 # GREP can not be used as it returns an error if the symbol is not found.
Andrew Boiebbd670c2015-08-17 13:16:11 -0700499 is_xip_command = "nm " + filename + " | awk '/CONFIG_XIP/ { print $3 }'"
Andrew Boie8f0211d2016-03-02 20:40:29 -0800500 is_xip_output = subprocess.check_output(is_xip_command, shell=True,
501 stderr=subprocess.STDOUT).decode("utf-8").strip()
502 if is_xip_output.endswith("no symbols"):
503 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700504 self.is_xip = (len(is_xip_output) != 0)
505
Andrew Boiebbd670c2015-08-17 13:16:11 -0700506 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700507 self.sections = []
508 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700509 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800510 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700511
512 self._calculate_sizes()
513
514 def get_ram_size(self):
515 """Get the amount of RAM the application will use up on the device
516
517 @return amount of RAM, in bytes
518 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700519 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700520
521 def get_rom_size(self):
522 """Get the size of the data that this application uses on device's flash
523
524 @return amount of ROM, in bytes
525 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700526 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700527
528 def unrecognized_sections(self):
529 """Get a list of sections inside the binary that weren't recognized
530
David B. Kinder29963c32017-06-16 12:32:42 -0700531 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700532 """
533 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700534 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700535 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700536 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700537 return slist
538
539 def _calculate_sizes(self):
540 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700541 objdump_command = "objdump -h " + self.filename
Andrew Boie6acbe632015-07-17 12:03:52 -0700542 objdump_output = subprocess.check_output(objdump_command,
Andrew Boie08ce5a52016-02-22 13:28:10 -0800543 shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700544
545 for line in objdump_output:
546 words = line.split()
547
548 if (len(words) == 0): # Skip lines that are too short
549 continue
550
551 index = words[0]
552 if (not index[0].isdigit()): # Skip lines that do not start
553 continue # with a digit
554
555 name = words[1] # Skip lines with section names
556 if (name[0] == '.'): # starting with '.'
557 continue
558
Andrew Boie73b4ee62015-10-07 11:33:22 -0700559 # TODO this doesn't actually reflect the size in flash or RAM as
560 # it doesn't include linker-imposed padding between sections.
561 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700562 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700563 if size == 0:
564 continue
565
Andrew Boie73b4ee62015-10-07 11:33:22 -0700566 load_addr = int(words[4], 16)
567 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700568
569 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700570 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700571 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700572 if name in SizeCalculator.alloc_sections:
573 self.ram_size += size
574 stype = "alloc"
575 elif name in SizeCalculator.rw_sections:
576 self.ram_size += size
577 self.rom_size += size
578 stype = "rw"
579 elif name in SizeCalculator.ro_sections:
580 self.rom_size += size
581 if not self.is_xip:
582 self.ram_size += size
583 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700584 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700585 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800586 if name not in self.extra_sections:
587 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700588
Andrew Boie73b4ee62015-10-07 11:33:22 -0700589 self.sections.append({"name" : name, "load_addr" : load_addr,
590 "size" : size, "virt_addr" : virt_addr,
Andy Ross8d801a52016-10-04 11:48:21 -0700591 "type" : stype, "recognized" : recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700592
593
594class MakeGoal:
595 """Metadata class representing one of the sub-makes called by MakeGenerator
596
David B. Kinder29963c32017-06-16 12:32:42 -0700597 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -0700598 with TestInstances to get a complete picture of what happened during a test.
599 MakeGenerator is used for tasks outside of building tests (such as
600 defconfigs) which is why MakeGoal is a separate class from TestInstance.
601 """
602 def __init__(self, name, text, qemu, make_log, build_log, run_log,
603 qemu_log):
604 self.name = name
605 self.text = text
606 self.qemu = qemu
607 self.make_log = make_log
608 self.build_log = build_log
609 self.run_log = run_log
610 self.qemu_log = qemu_log
611 self.make_state = "waiting"
612 self.failed = False
613 self.finished = False
614 self.reason = None
615 self.metrics = {}
616
617 def get_error_log(self):
618 if self.make_state == "waiting":
619 # Shouldn't ever see this; breakage in the main Makefile itself.
620 return self.make_log
621 elif self.make_state == "building":
622 # Failure when calling the sub-make to build the code
623 return self.build_log
624 elif self.make_state == "running":
625 # Failure in sub-make for "make qemu", qemu probably failed to start
626 return self.run_log
627 elif self.make_state == "finished":
628 # QEMU finished, but timed out or otherwise wasn't successful
629 return self.qemu_log
630
631 def fail(self, reason):
632 self.failed = True
633 self.finished = True
634 self.reason = reason
635
636 def success(self):
637 self.finished = True
638
639 def __str__(self):
640 if self.finished:
641 if self.failed:
642 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
643 self.get_error_log())
644 else:
645 return "[%s] passed" % self.name
646 else:
647 return "[%s] in progress (%s)" % (self.name, self.make_state)
648
649
650class MakeGenerator:
651 """Generates a Makefile which just calls a bunch of sub-make sessions
652
653 In any given test suite we may need to build dozens if not hundreds of
654 test cases. The cleanest way to parallelize this is to just let Make
655 do the parallelization, sharing the jobserver among all the different
656 sub-make targets.
657 """
658
659 GOAL_HEADER_TMPL = """.PHONY: {goal}
660{goal}:
661"""
662
663 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Andrew Boief7a6e282017-02-02 13:04:57 -0800664\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 -0700665"""
666
667 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
668
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300669 re_make = re.compile("sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700670
Anas Nashife3febe92016-11-30 14:25:44 -0500671 def __init__(self, base_outdir, asserts=False, deprecations=False, ccache=0):
Andrew Boie6acbe632015-07-17 12:03:52 -0700672 """MakeGenerator constructor
673
674 @param base_outdir Intended to be the base out directory. A make.log
675 file will be created here which contains the output of the
676 top-level Make session, as well as the dynamic control Makefile
677 @param verbose If true, pass V=1 to all the sub-makes which greatly
678 increases their verbosity
679 """
680 self.goals = {}
681 if not os.path.exists(base_outdir):
682 os.makedirs(base_outdir)
683 self.logfile = os.path.join(base_outdir, "make.log")
684 self.makefile = os.path.join(base_outdir, "Makefile")
Andrew Boie55121052016-07-20 11:52:04 -0700685 self.asserts = asserts
Anas Nashife3febe92016-11-30 14:25:44 -0500686 self.deprecations = deprecations
Kumar Galad5719a62016-10-26 12:30:26 -0500687 self.ccache = ccache
Andrew Boie6acbe632015-07-17 12:03:52 -0700688
689 def _get_rule_header(self, name):
690 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
691
692 def _get_sub_make(self, name, phase, workdir, outdir, logfile, args):
693 verb = "1" if VERBOSE else "0"
694 args = " ".join(args)
Anas Nashife3febe92016-11-30 14:25:44 -0500695
Andrew Boie55121052016-07-20 11:52:04 -0700696 if self.asserts:
697 cflags="-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
698 else:
699 cflags=""
Anas Nashife3febe92016-11-30 14:25:44 -0500700
701 if self.deprecations:
702 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800703
704 if self.ccache:
705 args = args + " USE_CCACHE=1"
706
707 return MakeGenerator.MAKE_RULE_TMPL.format(phase=phase, goal=name,
Andrew Boie55121052016-07-20 11:52:04 -0700708 outdir=outdir, cflags=cflags,
Andrew Boie6acbe632015-07-17 12:03:52 -0700709 directory=workdir, verb=verb,
710 args=args, logfile=logfile)
711
712 def _get_rule_footer(self, name):
713 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
714
715 def _add_goal(self, outdir):
716 if not os.path.exists(outdir):
717 os.makedirs(outdir)
718
719 def add_build_goal(self, name, directory, outdir, args):
720 """Add a goal to invoke a Kbuild session
721
722 @param name A unique string name for this build goal. The results
723 dictionary returned by execute() will be keyed by this name.
724 @param directory Absolute path to working directory, will be passed
725 to make -C
726 @param outdir Absolute path to output directory, will be passed to
727 Kbuild via -O=<path>
728 @param args Extra command line arguments to pass to 'make', typically
729 environment variables or specific Make goals
730 """
731 self._add_goal(outdir)
732 build_logfile = os.path.join(outdir, "build.log")
733 text = (self._get_rule_header(name) +
734 self._get_sub_make(name, "building", directory,
735 outdir, build_logfile, args) +
736 self._get_rule_footer(name))
737 self.goals[name] = MakeGoal(name, text, None, self.logfile, build_logfile,
738 None, None)
739
740 def add_qemu_goal(self, name, directory, outdir, args, timeout=30):
741 """Add a goal to build a Zephyr project and then run it under QEMU
742
743 The generated make goal invokes Make twice, the first time it will
744 build the default goal, and the second will invoke the 'qemu' goal.
745 The output of the QEMU session will be monitored, and terminated
746 either upon pass/fail result of the test program, or the timeout
747 is reached.
748
749 @param name A unique string name for this build goal. The results
750 dictionary returned by execute() will be keyed by this name.
751 @param directory Absolute path to working directory, will be passed
752 to make -C
753 @param outdir Absolute path to output directory, will be passed to
754 Kbuild via -O=<path>
755 @param args Extra command line arguments to pass to 'make', typically
756 environment variables. Do not pass specific Make goals here.
757 @param timeout Maximum length of time QEMU session should be allowed
758 to run before automatically killing it. Default is 30 seconds.
759 """
760
761 self._add_goal(outdir)
762 build_logfile = os.path.join(outdir, "build.log")
763 run_logfile = os.path.join(outdir, "run.log")
764 qemu_logfile = os.path.join(outdir, "qemu.log")
765
766 q = QEMUHandler(name, outdir, qemu_logfile, timeout)
767 args.append("QEMU_PIPE=%s" % q.get_fifo())
768 text = (self._get_rule_header(name) +
769 self._get_sub_make(name, "building", directory,
770 outdir, build_logfile, args) +
771 self._get_sub_make(name, "running", directory,
772 outdir, run_logfile,
Mazen NEIFER7f046372017-01-31 12:41:33 +0100773 args + ["run"]) +
Andrew Boie6acbe632015-07-17 12:03:52 -0700774 self._get_rule_footer(name))
775 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
776 run_logfile, qemu_logfile)
777
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300778 def add_unit_goal(self, name, directory, outdir, args, timeout=30, coverage=False):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300779 self._add_goal(outdir)
780 build_logfile = os.path.join(outdir, "build.log")
781 run_logfile = os.path.join(outdir, "run.log")
782 qemu_logfile = os.path.join(outdir, "qemu.log")
783 valgrind_logfile = os.path.join(outdir, "valgrind.log")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300784 if coverage:
785 args += ["COVERAGE=1"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300786
787 # we handle running in the UnitHandler class
788 text = (self._get_rule_header(name) +
789 self._get_sub_make(name, "building", directory,
790 outdir, build_logfile, args) +
791 self._get_rule_footer(name))
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300792 q = UnitHandler(name, directory, outdir, run_logfile, valgrind_logfile, timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300793 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
794 run_logfile, valgrind_logfile)
795
Andrew Boie6acbe632015-07-17 12:03:52 -0700796
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300797 def add_test_instance(self, ti, build_only=False, enable_slow=False, coverage=False,
Andrew Boieba612002016-09-01 10:41:03 -0700798 extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -0700799 """Add a goal to build/test a TestInstance object
800
801 @param ti TestInstance object to build. The status dictionary returned
802 by execute() will be keyed by its .name field.
803 """
804 args = ti.test.extra_args[:]
Anas Nashifa792a3d2017-04-04 18:47:49 -0400805 args.extend(["ARCH=%s" % ti.platform.arch,
Anas Nashifc7406082015-12-13 15:00:31 -0500806 "BOARD=%s" % ti.platform.name])
Andrew Boieba612002016-09-01 10:41:03 -0700807 args.extend(extra_args)
Andrew Boie6bb087c2016-02-10 13:39:00 -0800808 if (ti.platform.qemu_support and (not ti.build_only) and
809 (not build_only) and (enable_slow or not ti.test.slow)):
Andrew Boie6acbe632015-07-17 12:03:52 -0700810 self.add_qemu_goal(ti.name, ti.test.code_location, ti.outdir,
811 args, ti.test.timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300812 elif ti.test.type == "unit":
813 self.add_unit_goal(ti.name, ti.test.code_location, ti.outdir,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300814 args, ti.test.timeout, coverage)
Andrew Boie6acbe632015-07-17 12:03:52 -0700815 else:
816 self.add_build_goal(ti.name, ti.test.code_location, ti.outdir, args)
817
818 def execute(self, callback_fn=None, context=None):
819 """Execute all the registered build goals
820
821 @param callback_fn If not None, a callback function will be called
822 as individual goals transition between states. This function
823 should accept two parameters: a string state and an arbitrary
824 context object, supplied here
825 @param context Context object to pass to the callback function.
826 Type and semantics are specific to that callback function.
827 @return A dictionary mapping goal names to final status.
828 """
829
Andrew Boie08ce5a52016-02-22 13:28:10 -0800830 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -0700831 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -0800832 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -0700833 # Create our dynamic Makefile and execute it.
834 # Watch stderr output which is where we will keep
835 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -0800836 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -0700837 tf.write(goal.text)
838 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
839 tf.flush()
840
Daniel Leung6b170072016-04-07 12:10:25 -0700841 cmd = ["make", "-k", "-j", str(CPU_COUNTS * 2), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700842 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
843 stdout=devnull)
844
845 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -0800846 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -0700847 make_log.write(line)
848 verbose("MAKE: " + repr(line.strip()))
849 m = MakeGenerator.re_make.match(line)
850 if not m:
851 continue
852
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300853 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -0700854 if error:
855 goal = self.goals[error]
856 else:
857 goal = self.goals[name]
858 goal.make_state = state
859
860
861 if error:
Andrew Boie822b0872017-01-10 13:32:40 -0800862 # Sometimes QEMU will run an image and then crash out, which
863 # will cause the 'make qemu' invocation to exit with
864 # nonzero status.
865 # Need to distinguish this case from a compilation failure.
866 if goal.qemu:
867 goal.fail("qemu_crash")
868 else:
869 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -0700870 else:
871 if state == "finished":
872 if goal.qemu:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300873 if goal.qemu.unit:
874 # We can't run unit tests with Make
875 goal.qemu.handle()
876 if goal.qemu.returncode == 2:
877 goal.qemu_log = goal.qemu.valgrind_log
878 elif goal.qemu.returncode:
879 goal.qemu_log = goal.qemu.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700880 thread_status, metrics = goal.qemu.get_state()
881 goal.metrics.update(metrics)
882 if thread_status == "passed":
883 goal.success()
884 else:
885 goal.fail(thread_status)
886 else:
887 goal.success()
888
889 if callback_fn:
890 callback_fn(context, self.goals, goal)
891
892 p.wait()
893 return self.goals
894
895
896# "list" - List of strings
897# "list:<type>" - List of <type>
898# "set" - Set of unordered, unique strings
899# "set:<type>" - Set of <type>
900# "float" - Floating point
901# "int" - Integer
902# "bool" - Boolean
903# "str" - String
904
905# XXX Be sure to update __doc__ if you change any of this!!
906
907arch_valid_keys = {"name" : {"type" : "str", "required" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500908 "platforms" : {"type" : "list", "required" : True},
909 "supported_toolchains" : {"type" : "list", "required" : True}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700910
911platform_valid_keys = {"qemu_support" : {"type" : "bool", "default" : False},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500912 "supported_toolchains" : {"type" : "list", "default" : []}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700913
914testcase_valid_keys = {"tags" : {"type" : "set", "required" : True},
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300915 "type" : {"type" : "str", "default": "integration"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700916 "extra_args" : {"type" : "list"},
917 "build_only" : {"type" : "bool", "default" : False},
Anas Nashifa792a3d2017-04-04 18:47:49 -0400918 "build_on_all" : {"type" : "bool", "default" : False},
Anas Nashif2bd99bc2015-10-12 13:10:57 -0400919 "skip" : {"type" : "bool", "default" : False},
Andrew Boie6bb087c2016-02-10 13:39:00 -0800920 "slow" : {"type" : "bool", "default" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700921 "timeout" : {"type" : "int", "default" : 60},
Anas Nashifa792a3d2017-04-04 18:47:49 -0400922 "min_ram" : {"type" : "int", "default" : 8},
923 "depends_on": {"type" : "set"},
924 "min_flash" : {"type" : "int", "default" : 32},
Andrew Boie6acbe632015-07-17 12:03:52 -0700925 "arch_whitelist" : {"type" : "set"},
Anas Nashif30d13872015-10-05 10:02:45 -0400926 "arch_exclude" : {"type" : "set"},
Andrew Boie52fef672016-11-29 12:21:59 -0800927 "extra_sections" : {"type" : "list", "default" : []},
Anas Nashif30d13872015-10-05 10:02:45 -0400928 "platform_exclude" : {"type" : "set"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700929 "platform_whitelist" : {"type" : "set"},
Andrew Boie3ea78922016-03-24 14:46:00 -0700930 "filter" : {"type" : "str"}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700931
932
933class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -0400934 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -0700935 """
936 def __init__(self, filename):
937 """Instantiate a new SanityConfigParser object
938
Anas Nashifa792a3d2017-04-04 18:47:49 -0400939 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -0700940 """
Anas Nashifa792a3d2017-04-04 18:47:49 -0400941 with open(filename, 'r') as stream:
942 cp = yaml.load(stream)
Andrew Boie6acbe632015-07-17 12:03:52 -0700943 self.filename = filename
944 self.cp = cp
945
946 def _cast_value(self, value, typestr):
Anas Nashifa792a3d2017-04-04 18:47:49 -0400947
948 if type(value) is str:
949 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -0700950 if typestr == "str":
951 return v
952
953 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400954 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -0700955
956 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400957 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -0700958
959 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400960 return value
Andrew Boie6acbe632015-07-17 12:03:52 -0700961
962 elif typestr.startswith("list"):
963 vs = v.split()
964 if len(typestr) > 4 and typestr[4] == ":":
965 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
966 else:
967 return vs
968
969 elif typestr.startswith("set"):
970 vs = v.split()
971 if len(typestr) > 3 and typestr[3] == ":":
972 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
973 else:
974 return set(vs)
975
976 else:
977 raise ConfigurationError(self.filename, "unknown type '%s'" % value)
978
Anas Nashifa792a3d2017-04-04 18:47:49 -0400979 def section(self,name):
980 for s in self.sections():
981 if name in s:
982 return s.get(name, {})
Andrew Boie6acbe632015-07-17 12:03:52 -0700983
984 def sections(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -0400985 """Get the set of test sections within the .yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -0700986
987 @return a list of string section names"""
Anas Nashifa792a3d2017-04-04 18:47:49 -0400988 return self.cp['tests']
Andrew Boie6acbe632015-07-17 12:03:52 -0700989
990 def get_section(self, section, valid_keys):
991 """Get a dictionary representing the keys/values within a section
992
Anas Nashifa792a3d2017-04-04 18:47:49 -0400993 @param section The section in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -0700994 @param valid_keys A dictionary representing the intended semantics
995 for this section. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -0400996 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -0700997 here, it will generate an error. Each value in this dictionary
998 is another dictionary containing metadata:
999
1000 "default" - Default value if not given
1001 "type" - Data type to convert the text value to. Simple types
1002 supported are "str", "float", "int", "bool" which will get
1003 converted to respective Python data types. "set" and "list"
1004 may also be specified which will split the value by
1005 whitespace (but keep the elements as strings). finally,
1006 "list:<type>" and "set:<type>" may be given which will
1007 perform a type conversion after splitting the value up.
1008 "required" - If true, raise an error if not defined. If false
1009 and "default" isn't specified, a type conversion will be
1010 done on an empty string
1011 @return A dictionary containing the section key-value pairs with
1012 type conversion and default values filled in per valid_keys
1013 """
1014
1015 d = {}
Anas Nashifa792a3d2017-04-04 18:47:49 -04001016 for k, v in self.section(section).items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001017 if k not in valid_keys:
1018 raise ConfigurationError(self.filename,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001019 "Unknown config key '%s' in definition for '%s'"
1020 % (k, section))
Andrew Boie6acbe632015-07-17 12:03:52 -07001021 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"""
Anas Nashifa792a3d2017-04-04 18:47:49 -04001055 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001056 """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 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001064 scp = SanityConfigParser(cfile)
1065 cp = scp.cp
1066
1067 self.name = cp['identifier']
1068 # if no RAM size is specified by the board, take a default of 128K
1069 self.ram = cp.get("ram", 128)
1070 testing = cp.get("testing", {})
1071 self.ignore_tags = testing.get("ignore_tags", [])
1072 self.default = testing.get("default", False)
1073 # if no flash size is specified by the board, take a default of 512K
1074 self.flash = cp.get("flash", 512)
1075 self.supported = set(cp.get("supported", []))
1076 self.qemu_support = True if cp.get('type', "na") == 'qemu' else False
1077 self.arch = cp['arch']
1078 self.supported_toolchains = cp.get("toolchain", [])
Andrew Boie41878222016-11-03 11:58:53 -07001079 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001080 pass
1081
Andrew Boie6acbe632015-07-17 12:03:52 -07001082 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001083 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001084
1085
1086class Architecture:
1087 """Class representing metadata for a particular architecture
1088 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001089 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001090 """Architecture constructor
1091
1092 @param cfile Path to Architecture configuration file, which gives
1093 info about the arch and all the platforms for it
1094 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001095 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001096
Anas Nashifa792a3d2017-04-04 18:47:49 -04001097 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001098
1099 def __repr__(self):
1100 return "<arch %s>" % self.name
1101
1102
1103class TestCase:
1104 """Class representing a test application
1105 """
Andrew Boie3ea78922016-03-24 14:46:00 -07001106 def __init__(self, testcase_root, workdir, name, tc_dict, inifile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001107 """TestCase constructor.
1108
Anas Nashifa792a3d2017-04-04 18:47:49 -04001109 This gets called by TestSuite as it finds and reads test yaml files.
1110 Multiple TestCase instances may be generated from a single testcase.yaml,
Andrew Boie6acbe632015-07-17 12:03:52 -07001111 each one corresponds to a section within that file.
1112
Andrew Boie6acbe632015-07-17 12:03:52 -07001113 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001114 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001115 the test case is <workdir>/<name>.
1116
1117 @param testcase_root Absolute path to the root directory where
1118 all the test cases live
1119 @param workdir Relative path to the project directory for this
1120 test application from the test_case root.
1121 @param name Name of this test case, corresponding to the section name
1122 in the test case configuration file. For many test cases that just
1123 define one test, can be anything and is usually "test". This is
1124 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001125 the testcase.yaml defines multiple tests
Andrew Boie6acbe632015-07-17 12:03:52 -07001126 @param tc_dict Dictionary with section values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001127 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001128 """
1129 self.code_location = os.path.join(testcase_root, workdir)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001130 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001131 self.tags = tc_dict["tags"]
1132 self.extra_args = tc_dict["extra_args"]
1133 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001134 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001135 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001136 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001137 self.platform_whitelist = tc_dict["platform_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001138 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001139 self.timeout = tc_dict["timeout"]
1140 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001141 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001142 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001143 self.min_ram = tc_dict["min_ram"]
1144 self.depends_on = tc_dict["depends_on"]
1145 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001146 self.extra_sections = tc_dict["extra_sections"]
Andrew Boie14ac9022016-04-08 13:31:53 -07001147 self.path = os.path.join(os.path.basename(os.path.abspath(testcase_root)),
1148 workdir, name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001149 self.name = self.path # for now
Anas Nashif2cf0df02015-10-10 09:29:43 -04001150 self.defconfig = {}
Andrew Boie3ea78922016-03-24 14:46:00 -07001151 self.inifile = inifile
Andrew Boie6acbe632015-07-17 12:03:52 -07001152
Andrew Boie6acbe632015-07-17 12:03:52 -07001153 def __repr__(self):
1154 return self.name
1155
1156
1157
1158class TestInstance:
1159 """Class representing the execution of a particular TestCase on a platform
1160
1161 @param test The TestCase object we want to build/execute
1162 @param platform Platform object that we want to build and run against
1163 @param base_outdir Base directory for all test results. The actual
1164 out directory used is <outdir>/<platform>/<test case name>
1165 """
Andrew Boie6bb087c2016-02-10 13:39:00 -08001166 def __init__(self, test, platform, base_outdir, build_only=False,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001167 slow=False, coverage=False):
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 self.test = test
1169 self.platform = platform
1170 self.name = os.path.join(platform.name, test.path)
1171 self.outdir = os.path.join(base_outdir, platform.name, test.path)
1172 self.build_only = build_only or test.build_only
1173
1174 def calculate_sizes(self):
1175 """Get the RAM/ROM sizes of a test case.
1176
1177 This can only be run after the instance has been executed by
1178 MakeGenerator, otherwise there won't be any binaries to measure.
1179
1180 @return A SizeCalculator object
1181 """
Andrew Boie5d4eb782015-10-02 10:04:56 -07001182 fns = glob.glob(os.path.join(self.outdir, "*.elf"))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001183 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001184 if (len(fns) != 1):
1185 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001186 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001187
1188 def __repr__(self):
1189 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1190
1191
Andrew Boie4ef16c52015-08-28 12:36:03 -07001192def defconfig_cb(context, goals, goal):
1193 if not goal.failed:
1194 return
1195
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001196
Andrew Boie4ef16c52015-08-28 12:36:03 -07001197 info("%sCould not build defconfig for %s%s" %
1198 (COLOR_RED, goal.name, COLOR_NORMAL));
1199 if INLINE_LOGS:
1200 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001201 data = fp.read()
1202 sys.stdout.write(data)
1203 if log_file:
1204 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001205 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001206 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001207
Andrew Boie6acbe632015-07-17 12:03:52 -07001208
1209class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001210 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001211
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001212 def __init__(self, arch_root, testcase_roots, outdir, coverage):
Andrew Boie6acbe632015-07-17 12:03:52 -07001213 # Keep track of which test cases we've filtered out and why
1214 discards = {}
1215 self.arches = {}
1216 self.testcases = {}
1217 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001218 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001219 self.instances = {}
1220 self.goals = None
1221 self.discards = None
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001222 self.coverage = coverage
Andrew Boie6acbe632015-07-17 12:03:52 -07001223
1224 arch_root = os.path.abspath(arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001225
Andrew Boie3d348712016-04-08 11:52:13 -07001226 for testcase_root in testcase_roots:
1227 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001228
Andrew Boie3d348712016-04-08 11:52:13 -07001229 debug("Reading test case configuration files under %s..." %
1230 testcase_root)
1231 for dirpath, dirnames, filenames in os.walk(testcase_root,
1232 topdown=True):
1233 verbose("scanning %s" % dirpath)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001234 if "sample.yaml" in filenames or "testcase.yaml" in filenames:
1235 verbose("Found possible test case in " + dirpath)
Andrew Boie3d348712016-04-08 11:52:13 -07001236 dirnames[:] = []
Anas Nashifa792a3d2017-04-04 18:47:49 -04001237 if "sample.yaml" in filenames:
1238 yaml_path = os.path.join(dirpath, "sample.yaml")
1239 else:
1240 yaml_path = os.path.join(dirpath, "testcase.yaml")
1241 cp = SanityConfigParser(yaml_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001242 workdir = os.path.relpath(dirpath, testcase_root)
1243
1244 for section in cp.sections():
Anas Nashifa792a3d2017-04-04 18:47:49 -04001245 name = list(section.keys())[0]
1246 tc_dict = cp.get_section(name, testcase_valid_keys)
1247 tc = TestCase(testcase_root, workdir, name, tc_dict,
1248 yaml_path)
Andrew Boie3d348712016-04-08 11:52:13 -07001249 self.testcases[tc.name] = tc
Andrew Boie6acbe632015-07-17 12:03:52 -07001250
Anas Nashifa792a3d2017-04-04 18:47:49 -04001251 debug("Reading platform configuration files under %s..." % arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001252 for dirpath, dirnames, filenames in os.walk(arch_root):
1253 for filename in filenames:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001254 if filename.endswith(".yaml"):
Andrew Boie6acbe632015-07-17 12:03:52 -07001255 fn = os.path.join(dirpath, filename)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001256 verbose("Found plaform configuration " + fn)
1257 platform = Platform(fn)
1258 self.platforms.append(platform)
Andrew Boie6acbe632015-07-17 12:03:52 -07001259
Anas Nashifa792a3d2017-04-04 18:47:49 -04001260 arches = []
1261 for p in self.platforms:
1262 arches.append(p.arch)
1263 for a in list(set(arches)):
1264 aplatforms = [ p for p in self.platforms if p.arch == a ]
1265 arch = Architecture(a, aplatforms)
1266 self.arches[a] = arch
1267
Andrew Boie6acbe632015-07-17 12:03:52 -07001268 self.instances = {}
1269
1270 def get_last_failed(self):
1271 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001272 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 result = []
1274 with open(LAST_SANITY, "r") as fp:
1275 cr = csv.DictReader(fp)
1276 for row in cr:
1277 if row["passed"] == "True":
1278 continue
1279 test = row["test"]
1280 platform = row["platform"]
1281 result.append((test, platform))
1282 return result
1283
Anas Nashifdfa86e22016-10-24 17:08:56 -04001284 def apply_filters(self, platform_filter, arch_filter, tag_filter, exclude_tag,
Andrew Boie821d8322016-03-22 10:08:35 -07001285 config_filter, testcase_filter, last_failed, all_plats,
Kumar Galad5719a62016-10-26 12:30:26 -05001286 platform_limit, toolchain, extra_args, enable_ccache):
Andrew Boie6acbe632015-07-17 12:03:52 -07001287 instances = []
1288 discards = {}
1289 verbose("platform filter: " + str(platform_filter))
1290 verbose(" arch_filter: " + str(arch_filter))
1291 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001292 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001293 verbose(" config_filter: " + str(config_filter))
Kumar Galad5719a62016-10-26 12:30:26 -05001294 verbose(" enable_ccache: " + str(enable_ccache))
Andrew Boie6acbe632015-07-17 12:03:52 -07001295
1296 if last_failed:
1297 failed_tests = self.get_last_failed()
1298
Andrew Boie821d8322016-03-22 10:08:35 -07001299 default_platforms = False
1300
1301 if all_plats:
1302 info("Selecting all possible platforms per test case")
1303 # When --all used, any --platform arguments ignored
1304 platform_filter = []
1305 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001306 info("Selecting default platforms per test case")
1307 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001308
Kumar Galad5719a62016-10-26 12:30:26 -05001309 mg = MakeGenerator(self.outdir, ccache=enable_ccache)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001310 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001311 for tc_name, tc in self.testcases.items():
1312 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001313 for plat in arch.platforms:
1314 instance = TestInstance(tc, plat, self.outdir)
1315
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001316 if (arch_name == "unit") != (tc.type == "unit"):
1317 continue
1318
Anas Nashifbfab06b2017-06-22 09:22:24 -04001319 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001320 platform_filter = []
1321
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001322 if tc.skip:
1323 continue
1324
Anas Nashif2cf0df02015-10-10 09:29:43 -04001325 if tag_filter and not tc.tags.intersection(tag_filter):
1326 continue
1327
Anas Nashifdfa86e22016-10-24 17:08:56 -04001328 if exclude_tag and tc.tags.intersection(exclude_tag):
1329 continue
1330
Anas Nashif2cf0df02015-10-10 09:29:43 -04001331 if testcase_filter and tc_name not in testcase_filter:
1332 continue
1333
1334 if last_failed and (tc.name, plat.name) not in failed_tests:
1335 continue
1336
1337 if arch_filter and arch_name not in arch_filter:
1338 continue
1339
1340 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1341 continue
1342
1343 if tc.arch_exclude and arch.name in tc.arch_exclude:
1344 continue
1345
1346 if tc.platform_exclude and plat.name in tc.platform_exclude:
1347 continue
1348
1349 if platform_filter and plat.name not in platform_filter:
1350 continue
1351
Anas Nashifa792a3d2017-04-04 18:47:49 -04001352 if plat.ram <= tc.min_ram:
1353 continue
1354
1355 if set(plat.ignore_tags) & tc.tags:
1356 continue
1357
1358 if not tc.depends_on.issubset(set(plat.supported)):
1359 continue
1360
1361 if plat.flash < tc.min_flash:
1362 continue
1363
Anas Nashif2cf0df02015-10-10 09:29:43 -04001364 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1365 continue
1366
Anas Nashifcf21f5f2017-06-22 06:48:34 -04001367 if (tc.tc_filter and (plat.default or all_plats or platform_filter) and
1368 toolchain in plat.supported_toolchains):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001369 args = tc.extra_args[:]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001370 args.extend(["ARCH=" + plat.arch,
Andy Gross25309ab2017-06-06 21:29:09 -05001371 "BOARD=" + plat.name, "config-sanitycheck"])
Andrew Boieba612002016-09-01 10:41:03 -07001372 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001373 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001374 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07001375 # need a way to avoid different Make processes from clobbering
Anas Nashif2cf0df02015-10-10 09:29:43 -04001376 # each other since they all try to build them simultaneously
1377
1378 o = os.path.join(self.outdir, plat.name, tc.path)
Andy Gross25309ab2017-06-06 21:29:09 -05001379 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o,".config-sanitycheck")
1380 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Anas Nashif2cf0df02015-10-10 09:29:43 -04001381 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location), o, args)
1382
1383 info("Building testcase defconfigs...")
1384 results = mg.execute(defconfig_cb)
1385
Andrew Boie08ce5a52016-02-22 13:28:10 -08001386 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001387 if goal.failed:
1388 raise SanityRuntimeError("Couldn't build some defconfigs")
1389
Andrew Boie08ce5a52016-02-22 13:28:10 -08001390 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001391 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001392 defconfig = {}
1393 with open(out_config, "r") as fp:
1394 for line in fp.readlines():
1395 m = TestSuite.config_re.match(line)
1396 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001397 if line.strip() and not line.startswith("#"):
1398 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001399 continue
1400 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001401 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001402
Andrew Boie08ce5a52016-02-22 13:28:10 -08001403 for tc_name, tc in self.testcases.items():
1404 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001405 instance_list = []
1406 for plat in arch.platforms:
1407 instance = TestInstance(tc, plat, self.outdir)
1408
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001409 if (arch_name == "unit") != (tc.type == "unit"):
1410 # Discard silently
1411 continue
1412
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001413 if tc.skip:
1414 discards[instance] = "Skip filter"
1415 continue
1416
Anas Nashifbfab06b2017-06-22 09:22:24 -04001417 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001418 platform_filter = []
1419
Andrew Boie6acbe632015-07-17 12:03:52 -07001420 if tag_filter and not tc.tags.intersection(tag_filter):
1421 discards[instance] = "Command line testcase tag filter"
1422 continue
1423
Anas Nashifdfa86e22016-10-24 17:08:56 -04001424 if exclude_tag and tc.tags.intersection(exclude_tag):
1425 discards[instance] = "Command line testcase exclude filter"
1426 continue
1427
Andrew Boie6acbe632015-07-17 12:03:52 -07001428 if testcase_filter and tc_name not in testcase_filter:
1429 discards[instance] = "Testcase name filter"
1430 continue
1431
1432 if last_failed and (tc.name, plat.name) not in failed_tests:
1433 discards[instance] = "Passed or skipped during last run"
1434 continue
1435
1436 if arch_filter and arch_name not in arch_filter:
1437 discards[instance] = "Command line testcase arch filter"
1438 continue
1439
1440 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1441 discards[instance] = "Not in test case arch whitelist"
1442 continue
1443
Anas Nashif30d13872015-10-05 10:02:45 -04001444 if tc.arch_exclude and arch.name in tc.arch_exclude:
1445 discards[instance] = "In test case arch exclude"
1446 continue
1447
1448 if tc.platform_exclude and plat.name in tc.platform_exclude:
1449 discards[instance] = "In test case platform exclude"
1450 continue
1451
Andrew Boie6acbe632015-07-17 12:03:52 -07001452 if platform_filter and plat.name not in platform_filter:
1453 discards[instance] = "Command line platform filter"
1454 continue
1455
1456 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1457 discards[instance] = "Not in testcase platform whitelist"
1458 continue
1459
Javier B Perez4b554ba2016-08-15 13:25:33 -05001460 if toolchain and toolchain not in plat.supported_toolchains:
1461 discards[instance] = "Not supported by the toolchain"
1462 continue
1463
Anas Nashifa792a3d2017-04-04 18:47:49 -04001464 if plat.ram <= tc.min_ram:
1465 discards[instance] = "Not enough RAM"
1466 continue
1467
1468 if not tc.depends_on.issubset(set(plat.supported)):
1469 discards[instance] = "No hardware support"
1470 continue
1471
1472 if plat.flash< tc.min_flash:
1473 discards[instance] = "Not enough FLASH"
1474 continue
1475
1476 if set(plat.ignore_tags) & tc.tags:
1477 discards[instance] = "Excluded tags per platform"
1478 continue
1479
Andrew Boie3ea78922016-03-24 14:46:00 -07001480 defconfig = {"ARCH" : arch.name, "PLATFORM" : plat.name}
Javier B Perez79414542016-08-08 12:24:59 -05001481 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001482 for p, tdefconfig in tc.defconfig.items():
1483 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001484 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001485 break
1486
Andrew Boie3ea78922016-03-24 14:46:00 -07001487 if tc.tc_filter:
1488 try:
1489 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07001490 except (ValueError, SyntaxError) as se:
Andrew Boie3ea78922016-03-24 14:46:00 -07001491 sys.stderr.write("Failed processing %s\n" % tc.inifile)
1492 raise se
1493 if not res:
1494 discards[instance] = ("defconfig doesn't satisfy expression '%s'" %
1495 tc.tc_filter)
1496 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001497
Andrew Boie6acbe632015-07-17 12:03:52 -07001498 instance_list.append(instance)
1499
1500 if not instance_list:
1501 # Every platform in this arch was rejected already
1502 continue
1503
Anas Nashifa792a3d2017-04-04 18:47:49 -04001504 if default_platforms and not tc.build_on_all:
1505 if not tc.platform_whitelist:
1506 instances = list(filter(lambda tc: tc.platform.default, instance_list))
1507 self.add_instances(instances)
1508 else:
1509 self.add_instances(instance_list[:platform_limit])
1510
1511 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
1512 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07001513 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001514 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001515 self.discards = discards
1516 return discards
1517
Andrew Boie821d8322016-03-22 10:08:35 -07001518 def add_instances(self, ti_list):
1519 for ti in ti_list:
1520 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001521
Anas Nashife3febe92016-11-30 14:25:44 -05001522 def execute(self, cb, cb_context, build_only, enable_slow, enable_asserts, enable_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001523 extra_args, enable_ccache):
Daniel Leung6b170072016-04-07 12:10:25 -07001524
1525 def calc_one_elf_size(name, goal):
1526 if not goal.failed:
1527 i = self.instances[name]
1528 sc = i.calculate_sizes()
1529 goal.metrics["ram_size"] = sc.get_ram_size()
1530 goal.metrics["rom_size"] = sc.get_rom_size()
1531 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07001532
Anas Nashife3febe92016-11-30 14:25:44 -05001533 mg = MakeGenerator(self.outdir, asserts=enable_asserts, deprecations=enable_deprecations,
1534 ccache=enable_ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001535 for i in self.instances.values():
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001536 mg.add_test_instance(i, build_only, enable_slow, self.coverage, extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001537 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07001538
1539 # Parallelize size calculation
1540 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
1541 futures = [executor.submit(calc_one_elf_size, name, goal) \
1542 for name, goal in self.goals.items()]
1543 concurrent.futures.wait(futures)
1544
Andrew Boie6acbe632015-07-17 12:03:52 -07001545 return self.goals
1546
1547 def discard_report(self, filename):
1548 if self.discards == None:
1549 raise SanityRuntimeException("apply_filters() hasn't been run!")
1550
1551 with open(filename, "wb") as csvfile:
1552 fieldnames = ["test", "arch", "platform", "reason"]
1553 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1554 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001555 for instance, reason in self.discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001556 rowdict = {"test" : i.test.name,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001557 "arch" : i.platform.arch,
Andrew Boie6acbe632015-07-17 12:03:52 -07001558 "platform" : i.platform.name,
1559 "reason" : reason}
1560 cw.writerow(rowdict)
1561
1562 def compare_metrics(self, filename):
1563 # name, datatype, lower results better
1564 interesting_metrics = [("ram_size", int, True),
1565 ("rom_size", int, True)]
1566
1567 if self.goals == None:
1568 raise SanityRuntimeException("execute() hasn't been run!")
1569
1570 if not os.path.exists(filename):
1571 info("Cannot compare metrics, %s not found" % filename)
1572 return []
1573
1574 results = []
1575 saved_metrics = {}
1576 with open(filename) as fp:
1577 cr = csv.DictReader(fp)
1578 for row in cr:
1579 d = {}
1580 for m, _, _ in interesting_metrics:
1581 d[m] = row[m]
1582 saved_metrics[(row["test"], row["platform"])] = d
1583
Andrew Boie08ce5a52016-02-22 13:28:10 -08001584 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001585 i = self.instances[name]
1586 mkey = (i.test.name, i.platform.name)
1587 if mkey not in saved_metrics:
1588 continue
1589 sm = saved_metrics[mkey]
1590 for metric, mtype, lower_better in interesting_metrics:
1591 if metric not in goal.metrics:
1592 continue
1593 if sm[metric] == "":
1594 continue
1595 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07001596 if delta == 0:
1597 continue
1598 results.append((i, metric, goal.metrics[metric], delta,
1599 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07001600 return results
1601
Anas Nashif0605fa32017-05-07 08:51:02 -04001602 def testcase_xunit_report(self, filename, duration, args):
Anas Nashifb3311ed2017-04-13 14:44:48 -04001603 if self.goals == None:
1604 raise SanityRuntimeException("execute() hasn't been run!")
1605
1606 fails = 0
1607 passes = 0
1608 errors = 0
1609
1610 for name, goal in self.goals.items():
1611 if goal.failed:
1612 if goal.reason in ['build_error', 'qemu_crash']:
1613 errors += 1
1614 else:
1615 fails += 1
1616 else:
1617 passes += 1
1618
1619 run = "Sanitycheck"
1620 eleTestsuite = None
1621 append = args.only_failed
1622
Anas Nashif0605fa32017-05-07 08:51:02 -04001623 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04001624 tree = ET.parse(filename)
1625 eleTestsuites = tree.getroot()
1626 eleTestsuite = tree.findall('testsuite')[0];
1627 else:
1628 eleTestsuites = ET.Element('testsuites')
Anas Nashif0605fa32017-05-07 08:51:02 -04001629 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite', name=run, time="%d" %duration,
Anas Nashifb3311ed2017-04-13 14:44:48 -04001630 tests="%d" %(errors + passes + fails), failures="%d" %fails, errors="%d" %errors, skip="0")
1631
1632 qemu_time = "0"
1633 for name, goal in self.goals.items():
1634
1635 i = self.instances[name]
1636 if append:
1637 for tc in eleTestsuite.findall('testcase'):
Anas Nashif202d1302017-05-07 08:19:20 -04001638 if tc.get('classname') == "%s:%s" %(i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04001639 eleTestsuite.remove(tc)
1640
1641 if not goal.failed and goal.qemu:
1642 qemu_time = "%s" %(goal.metrics["qemu_time"])
1643
Anas Nashif202d1302017-05-07 08:19:20 -04001644 eleTestcase = ET.SubElement(eleTestsuite, 'testcase', classname="%s:%s" %(i.platform.name, i.test.name), name="%s" %(name), time=qemu_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04001645 if goal.failed:
1646 failure = ET.SubElement(eleTestcase, 'failure', type="failure", message=goal.reason)
1647 p = ("%s/%s/%s" %(args.outdir, i.platform.name, i.test.name))
1648 bl = os.path.join(p, "build.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04001649 if goal.reason != 'build_error':
1650 bl = os.path.join(p, "qemu.log")
1651
Anas Nashifb3311ed2017-04-13 14:44:48 -04001652 if os.path.exists(bl):
1653 with open(bl, "r") as f:
1654 log = f.read()
Anas Nashife6fcc012017-05-17 09:29:09 -04001655 ansi_escape = re.compile(r'\x1b[^m]*m')
1656 output = ansi_escape.sub('', str(log))
1657 failure.text = (escape(output))
Anas Nashifb3311ed2017-04-13 14:44:48 -04001658
1659 result = ET.tostring(eleTestsuites)
1660 f = open(filename, 'wb')
1661 f.write(result)
1662 f.close()
1663
Andrew Boie6acbe632015-07-17 12:03:52 -07001664 def testcase_report(self, filename):
1665 if self.goals == None:
1666 raise SanityRuntimeException("execute() hasn't been run!")
1667
Andrew Boie08ce5a52016-02-22 13:28:10 -08001668 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07001669 fieldnames = ["test", "arch", "platform", "passed", "status",
1670 "extra_args", "qemu", "qemu_time", "ram_size",
1671 "rom_size"]
1672 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1673 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001674 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001675 i = self.instances[name]
1676 rowdict = {"test" : i.test.name,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001677 "arch" : i.platform.arch,
Andrew Boie6acbe632015-07-17 12:03:52 -07001678 "platform" : i.platform.name,
1679 "extra_args" : " ".join(i.test.extra_args),
1680 "qemu" : i.platform.qemu_support}
1681 if goal.failed:
1682 rowdict["passed"] = False
1683 rowdict["status"] = goal.reason
1684 else:
1685 rowdict["passed"] = True
1686 if goal.qemu:
1687 rowdict["qemu_time"] = goal.metrics["qemu_time"]
1688 rowdict["ram_size"] = goal.metrics["ram_size"]
1689 rowdict["rom_size"] = goal.metrics["rom_size"]
1690 cw.writerow(rowdict)
1691
1692
1693def parse_arguments():
1694
1695 parser = argparse.ArgumentParser(description = __doc__,
1696 formatter_class = argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05001697 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07001698
1699 parser.add_argument("-p", "--platform", action="append",
Andrew Boie821d8322016-03-22 10:08:35 -07001700 help="Platform filter for testing. This option may be used multiple "
1701 "times. Testcases will only be built/run on the platforms "
Anas Nashifa792a3d2017-04-04 18:47:49 -04001702 "specified. If this option is not used, then platforms marked "
1703 "as default in the platform metadata file will be chosen "
1704 "to build and test. ")
Andrew Boie821d8322016-03-22 10:08:35 -07001705 parser.add_argument("-L", "--platform-limit", action="store", type=int,
1706 metavar="N", default=1,
1707 help="Controls what platforms are tested if --platform or "
1708 "--all are not used. For each architecture specified by "
1709 "--arch (defaults to all of them), choose the first "
1710 "N platforms to test in the arch-specific .ini file "
1711 "'platforms' list. Defaults to 1.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001712 parser.add_argument("-a", "--arch", action="append",
1713 help="Arch filter for testing. Takes precedence over --platform. "
1714 "If unspecified, test all arches. Multiple invocations "
1715 "are treated as a logical 'or' relationship")
1716 parser.add_argument("-t", "--tag", action="append",
1717 help="Specify tags to restrict which tests to run by tag value. "
1718 "Default is to not do any tag filtering. Multiple invocations "
1719 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04001720 parser.add_argument("-e", "--exclude-tag", action="append",
Paul Sokolovskyff70add2017-06-16 01:31:54 +03001721 help="Specify tags of tests that should not run. "
Anas Nashifdfa86e22016-10-24 17:08:56 -04001722 "Default is to run all tests with all tags.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001723 parser.add_argument("-f", "--only-failed", action="store_true",
1724 help="Run only those tests that failed the previous sanity check "
1725 "invocation.")
1726 parser.add_argument("-c", "--config", action="append",
1727 help="Specify platform configuration values filtering. This can be "
1728 "specified two ways: <config>=<value> or just <config>. The "
Andrew Boie41878222016-11-03 11:58:53 -07001729 "defconfig for all platforms will be "
Andrew Boie6acbe632015-07-17 12:03:52 -07001730 "checked. For the <config>=<value> case, only match defconfig "
1731 "that have that value defined. For the <config> case, match "
1732 "defconfig that have that value assigned to any value. "
1733 "Prepend a '!' to invert the match.")
1734 parser.add_argument("-s", "--test", action="append",
1735 help="Run only the specified test cases. These are named by "
1736 "<path to test project relative to "
1737 "--testcase-root>/<testcase.ini section name>")
1738 parser.add_argument("-l", "--all", action="store_true",
Andrew Boie821d8322016-03-22 10:08:35 -07001739 help="Build/test on all platforms. Any --platform arguments "
1740 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001741
1742 parser.add_argument("-o", "--testcase-report",
1743 help="Output a CSV spreadsheet containing results of the test run")
1744 parser.add_argument("-d", "--discard-report",
David B. Kinder29963c32017-06-16 12:32:42 -07001745 help="Output a CSV spreadsheet showing tests that were skipped "
Andrew Boie6acbe632015-07-17 12:03:52 -07001746 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07001747 parser.add_argument("--compare-report",
David B. Kinder29963c32017-06-16 12:32:42 -07001748 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07001749
Kumar Galad5719a62016-10-26 12:30:26 -05001750 parser.add_argument("--ccache", action="store_const", const=1, default=0,
1751 help="Enable the use of ccache when building")
1752
Anas Nashif035799f2017-05-13 21:31:53 -04001753 parser.add_argument("-B", "--subset",
1754 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
1755 "3/5 means run the 3rd fifth of the total. "
1756 "This option is useful when running a large number of tests on "
1757 "different hosts to speed up execution time.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001758 parser.add_argument("-y", "--dry-run", action="store_true",
1759 help="Create the filtered list of test cases, but don't actually "
1760 "run them. Useful if you're just interested in "
1761 "--discard-report")
1762
1763 parser.add_argument("-r", "--release", action="store_true",
1764 help="Update the benchmark database with the results of this test "
1765 "run. Intended to be run by CI when tagging an official "
1766 "release. This database is used as a basis for comparison "
1767 "when looking for deltas in metrics such as footprint")
1768 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
1769 help="Treat warning conditions as errors")
1770 parser.add_argument("-v", "--verbose", action="count", default=0,
1771 help="Emit debugging information, call multiple times to increase "
1772 "verbosity")
1773 parser.add_argument("-i", "--inline-logs", action="store_true",
1774 help="Upon test failure, print relevant log data to stdout "
1775 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001776 parser.add_argument("--log-file", metavar="FILENAME", action="store",
1777 help="log also to file")
Andrew Boie6acbe632015-07-17 12:03:52 -07001778 parser.add_argument("-m", "--last-metrics", action="store_true",
1779 help="Instead of comparing metrics from the last --release, "
1780 "compare with the results of the previous sanity check "
1781 "invocation")
1782 parser.add_argument("-u", "--no-update", action="store_true",
1783 help="do not update the results of the last run of the sanity "
1784 "checks")
1785 parser.add_argument("-b", "--build-only", action="store_true",
1786 help="Only build the code, do not execute any of it in QEMU")
1787 parser.add_argument("-j", "--jobs", type=int,
1788 help="Number of cores to use when building, defaults to "
1789 "number of CPUs * 2")
1790 parser.add_argument("-H", "--footprint-threshold", type=float, default=5,
1791 help="When checking test case footprint sizes, warn the user if "
1792 "the new app size is greater then the specified percentage "
1793 "from the last release. Default is 5. 0 to warn on any "
1794 "increase on app size")
Andrew Boieea7928f2015-08-14 14:27:38 -07001795 parser.add_argument("-D", "--all-deltas", action="store_true",
1796 help="Show all footprint deltas, positive or negative. Implies "
1797 "--footprint-threshold=0")
Andrew Boie6acbe632015-07-17 12:03:52 -07001798 parser.add_argument("-O", "--outdir",
1799 default="%s/sanity-out" % ZEPHYR_BASE,
1800 help="Output directory for logs and binaries.")
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001801 parser.add_argument("-n", "--no-clean", action="store_true",
1802 help="Do not delete the outdir before building. Will result in "
1803 "faster compilation since builds will be incremental")
Andrew Boie3d348712016-04-08 11:52:13 -07001804 parser.add_argument("-T", "--testcase-root", action="append", default=[],
Andrew Boie6acbe632015-07-17 12:03:52 -07001805 help="Base directory to recursively search for test cases. All "
Andrew Boie3d348712016-04-08 11:52:13 -07001806 "testcase.ini files under here will be processed. May be "
1807 "called multiple times. Defaults to the 'samples' and "
1808 "'tests' directories in the Zephyr tree.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001809 parser.add_argument("-A", "--arch-root",
Anas Nashifa792a3d2017-04-04 18:47:49 -04001810 default="%s/boards" % ZEPHYR_BASE,
Andrew Boie6acbe632015-07-17 12:03:52 -07001811 help="Directory to search for arch configuration files. All .ini "
1812 "files in the directory will be processed.")
Andrew Boiebbd670c2015-08-17 13:16:11 -07001813 parser.add_argument("-z", "--size", action="append",
1814 help="Don't run sanity checks. Instead, produce a report to "
1815 "stdout detailing RAM/ROM sizes on the specified filenames. "
1816 "All other command line arguments ignored.")
Andrew Boie6bb087c2016-02-10 13:39:00 -08001817 parser.add_argument("-S", "--enable-slow", action="store_true",
1818 help="Execute time-consuming test cases that have been marked "
1819 "as 'slow' in testcase.ini. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07001820 parser.add_argument("-R", "--enable-asserts", action="store_true",
1821 help="Build all test cases with assertions enabled.")
Anas Nashife3febe92016-11-30 14:25:44 -05001822 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
1823 help="Error on deprecation warnings.")
Andrew Boieba612002016-09-01 10:41:03 -07001824 parser.add_argument("-x", "--extra-args", action="append", default=[],
1825 help="Extra arguments to pass to the build when compiling test "
1826 "cases. May be called multiple times. These will be passed "
1827 "in after any sanitycheck-supplied options.")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001828 parser.add_argument("-C", "--coverage", action="store_true",
1829 help="Scan for unit test coverage with gcov + lcov.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001830
1831 return parser.parse_args()
1832
1833def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01001834 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001835 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001836 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08001837
1838 try:
1839 with open(filename) as fp:
1840 data = fp.read()
1841 except Exception as e:
1842 data = "Unable to read log data (%s)\n" % (str(e))
1843
1844 sys.stdout.write(data)
1845 if log_file:
1846 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001847 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001848 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001849 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07001850
1851def terse_test_cb(instances, goals, goal):
1852 total_tests = len(goals)
1853 total_done = 0
1854 total_failed = 0
1855
Andrew Boie08ce5a52016-02-22 13:28:10 -08001856 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001857 if g.finished:
1858 total_done += 1
1859 if g.failed:
1860 total_failed += 1
1861
1862 if goal.failed:
1863 i = instances[goal.name]
1864 info("\n\n{:<25} {:<50} {}FAILED{}: {}".format(i.platform.name,
1865 i.test.name, COLOR_RED, COLOR_NORMAL, goal.reason))
1866 log_info(goal.get_error_log())
1867 info("")
1868
Andrew Boie7a992ae2017-01-17 10:40:02 -08001869 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" % (
Andrew Boie6acbe632015-07-17 12:03:52 -07001870 COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
Andrew Boie7a992ae2017-01-17 10:40:02 -08001871 int((float(total_done) / total_tests) * 100),
Andrew Boie6acbe632015-07-17 12:03:52 -07001872 COLOR_RED if total_failed > 0 else COLOR_NORMAL,
1873 total_failed, COLOR_NORMAL))
1874 sys.stdout.flush()
1875
1876def chatty_test_cb(instances, goals, goal):
1877 i = instances[goal.name]
1878
1879 if VERBOSE < 2 and not goal.finished:
1880 return
1881
1882 if goal.failed:
1883 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
1884 elif goal.finished:
1885 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
1886 else:
1887 status = goal.make_state
1888
1889 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
1890 if goal.failed:
1891 log_info(goal.get_error_log())
1892
Andrew Boiebbd670c2015-08-17 13:16:11 -07001893
1894def size_report(sc):
1895 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07001896 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07001897 for i in range(len(sc.sections)):
1898 v = sc.sections[i]
1899
Andrew Boie73b4ee62015-10-07 11:33:22 -07001900 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
1901 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
1902 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07001903
Andrew Boie73b4ee62015-10-07 11:33:22 -07001904 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
1905 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001906 info("")
1907
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001908def generate_coverage(outdir, ignores):
1909 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
1910 coveragefile = os.path.join(outdir, "coverage.info")
1911 ztestfile = os.path.join(outdir, "ztest.info")
1912 subprocess.call(["lcov", "--capture", "--directory", outdir,
1913 "--output-file", coveragefile], stdout=coveragelog)
1914 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
1915 subprocess.call(["lcov", "--extract", coveragefile,
1916 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
1917 "--output-file", ztestfile], stdout=coveragelog)
1918 subprocess.call(["lcov", "--remove", ztestfile,
1919 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
1920 "--output-file", ztestfile], stdout=coveragelog)
1921 for i in ignores:
1922 subprocess.call(["lcov", "--remove", coveragefile, i,
1923 "--output-file", coveragefile], stdout=coveragelog)
1924 subprocess.call(["genhtml", "-output-directory",
1925 os.path.join(outdir, "coverage"),
1926 coveragefile, ztestfile], stdout=coveragelog)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001927
Andrew Boie6acbe632015-07-17 12:03:52 -07001928def main():
Andrew Boie4b182472015-07-31 12:25:22 -07001929 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001930 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Andrew Boie6acbe632015-07-17 12:03:52 -07001931 args = parse_arguments()
Javier B Perez4b554ba2016-08-15 13:25:33 -05001932 toolchain = os.environ.get("ZEPHYR_GCC_VARIANT", None)
Andrew Boie1e4e68b2017-02-10 11:40:54 -08001933 if toolchain == "zephyr":
1934 os.environ["DISABLE_TRYRUN"] = "1"
Andrew Boiebbd670c2015-08-17 13:16:11 -07001935
1936 if args.size:
1937 for fn in args.size:
Andrew Boie52fef672016-11-29 12:21:59 -08001938 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001939 sys.exit(0)
1940
Andrew Boie6acbe632015-07-17 12:03:52 -07001941 VERBOSE += args.verbose
1942 INLINE_LOGS = args.inline_logs
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001943 if args.log_file:
1944 log_file = open(args.log_file, "w")
Andrew Boie6acbe632015-07-17 12:03:52 -07001945 if args.jobs:
Daniel Leung6b170072016-04-07 12:10:25 -07001946 CPU_COUNTS = args.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07001947
Anas Nashif035799f2017-05-13 21:31:53 -04001948 if args.subset:
1949 subset, sets = args.subset.split("/")
1950 if int(subset) > 0 and int(sets) >= int(subset):
1951 info("Running only a subset: %s/%s" %(subset,sets))
1952 else:
1953 error("You have provided a wrong subset value: %s." %args.subset)
1954 return
1955
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001956 if os.path.exists(args.outdir) and not args.no_clean:
Andrew Boie6acbe632015-07-17 12:03:52 -07001957 info("Cleaning output directory " + args.outdir)
1958 shutil.rmtree(args.outdir)
1959
Andrew Boie3d348712016-04-08 11:52:13 -07001960 if not args.testcase_root:
1961 args.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
1962 os.path.join(ZEPHYR_BASE, "samples")]
1963
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001964 ts = TestSuite(args.arch_root, args.testcase_root, args.outdir, args.coverage)
Anas Nashifdfa86e22016-10-24 17:08:56 -04001965 discards = ts.apply_filters(args.platform, args.arch, args.tag, args.exclude_tag, args.config,
Andrew Boie821d8322016-03-22 10:08:35 -07001966 args.test, args.only_failed, args.all,
Kumar Galad5719a62016-10-26 12:30:26 -05001967 args.platform_limit, toolchain, args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001968
1969 if args.discard_report:
1970 ts.discard_report(args.discard_report)
1971
1972 if VERBOSE:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001973 for i, reason in discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001974 debug("{:<25} {:<50} {}SKIPPED{}: {}".format(i.platform.name,
1975 i.test.name, COLOR_YELLOW, COLOR_NORMAL, reason))
1976
Anas Nashif035799f2017-05-13 21:31:53 -04001977 ts.instancets = OrderedDict(sorted(ts.instances.items(), key=lambda t: t[0]))
1978
1979 if args.subset:
1980 subset, sets = args.subset.split("/")
1981 total = len(ts.instancets)
1982 per_set = round(total / int(sets))
1983 start = (int(subset) - 1 ) * per_set
1984 if subset == sets:
1985 end = total
1986 else:
1987 end = start + per_set
1988
1989 sliced_instances = islice(ts.instancets.items(),start, end)
1990 ts.instances = OrderedDict(sliced_instances)
1991
Andrew Boie6acbe632015-07-17 12:03:52 -07001992 info("%d tests selected, %d tests discarded due to filters" %
1993 (len(ts.instances), len(discards)))
1994
1995 if args.dry_run:
1996 return
1997
1998 if VERBOSE or not TERMINAL:
Andrew Boie6bb087c2016-02-10 13:39:00 -08001999 goals = ts.execute(chatty_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05002000 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05002001 args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07002002 else:
Andrew Boie6bb087c2016-02-10 13:39:00 -08002003 goals = ts.execute(terse_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05002004 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05002005 args.extra_args, args.ccache)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002006 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07002007
Daniel Leung7f850102016-04-08 11:07:32 -07002008 # figure out which report to use for size comparison
2009 if args.compare_report:
2010 report_to_use = args.compare_report
2011 elif args.last_metrics:
2012 report_to_use = LAST_SANITY
2013 else:
2014 report_to_use = RELEASE_DATA
2015
2016 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07002017 warnings = 0
2018 if deltas:
Andrew Boieea7928f2015-08-14 14:27:38 -07002019 for i, metric, value, delta, lower_better in deltas:
2020 if not args.all_deltas and ((delta < 0 and lower_better) or
2021 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07002022 continue
2023
Andrew Boieea7928f2015-08-14 14:27:38 -07002024 percentage = (float(delta) / float(value - delta))
2025 if not args.all_deltas and (percentage <
2026 (args.footprint_threshold / 100.0)):
2027 continue
2028
Daniel Leung00525c22016-04-11 10:27:56 -07002029 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07002030 i.platform.name, i.test.name, COLOR_YELLOW,
2031 "INFO" if args.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07002032 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07002033 warnings += 1
2034
2035 if warnings:
2036 info("Deltas based on metrics from last %s" %
2037 ("release" if not args.last_metrics else "run"))
2038
2039 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08002040 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002041 if goal.failed:
2042 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002043 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07002044 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2045 (COLOR_RED, COLOR_NORMAL, goal.name,
2046 str(goal.metrics["unrecognized"])))
2047 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002048
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002049 if args.coverage:
2050 info("Generating coverage files...")
2051 generate_coverage(args.outdir, ["tests/*", "samples/*"])
2052
Anas Nashif0605fa32017-05-07 08:51:02 -04002053 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07002054 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Andrew Boie6acbe632015-07-17 12:03:52 -07002055 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
2056 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
Anas Nashif0605fa32017-05-07 08:51:02 -04002057 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07002058
2059 if args.testcase_report:
2060 ts.testcase_report(args.testcase_report)
2061 if not args.no_update:
Anas Nashif0605fa32017-05-07 08:51:02 -04002062 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration, args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002063 ts.testcase_report(LAST_SANITY)
2064 if args.release:
2065 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002066 if log_file:
2067 log_file.close()
Andrew Boie6acbe632015-07-17 12:03:52 -07002068 if failed or (warnings and args.warnings_as_errors):
2069 sys.exit(1)
2070
2071if __name__ == "__main__":
2072 main()