blob: f7c96c09016e810699bcb6b56753f6d796acc978 [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
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700179import logging
180log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
181logging.basicConfig(format = log_format, level = 30)
182
Andrew Boie6acbe632015-07-17 12:03:52 -0700183if "ZEPHYR_BASE" not in os.environ:
Anas Nashif427cdd32015-08-06 07:25:42 -0400184 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700185 exit(1)
186ZEPHYR_BASE = os.environ["ZEPHYR_BASE"]
Andrew Boie3ea78922016-03-24 14:46:00 -0700187
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700188import scl
189
Andrew Boie3ea78922016-03-24 14:46:00 -0700190sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
191
192import expr_parser
193
Andrew Boie6acbe632015-07-17 12:03:52 -0700194VERBOSE = 0
195LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
196 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400197LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
198 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700199RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
200 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700201CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700202
203if os.isatty(sys.stdout.fileno()):
204 TERMINAL = True
205 COLOR_NORMAL = '\033[0m'
206 COLOR_RED = '\033[91m'
207 COLOR_GREEN = '\033[92m'
208 COLOR_YELLOW = '\033[93m'
209else:
210 TERMINAL = False
211 COLOR_NORMAL = ""
212 COLOR_RED = ""
213 COLOR_GREEN = ""
214 COLOR_YELLOW = ""
215
216class SanityCheckException(Exception):
217 pass
218
219class SanityRuntimeError(SanityCheckException):
220 pass
221
222class ConfigurationError(SanityCheckException):
223 def __init__(self, cfile, message):
224 self.cfile = cfile
225 self.message = message
226
227 def __str__(self):
228 return repr(self.cfile + ": " + self.message)
229
230class MakeError(SanityCheckException):
231 pass
232
233class BuildError(MakeError):
234 pass
235
236class ExecutionError(MakeError):
237 pass
238
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800239log_file = None
240
Andrew Boie6acbe632015-07-17 12:03:52 -0700241# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800242def info(what):
243 sys.stdout.write(what + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800244 if log_file:
245 log_file.write(what + "\n")
246 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700247
248def error(what):
249 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800250 if log_file:
251 log_file(what + "\n")
252 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700253
Andrew Boie08ce5a52016-02-22 13:28:10 -0800254def debug(what):
255 if VERBOSE >= 1:
256 info(what)
257
Andrew Boie6acbe632015-07-17 12:03:52 -0700258def verbose(what):
259 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800260 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700261
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300262class Handler:
263 RUN_PASSED = "PROJECT EXECUTION SUCCESSFUL"
264 RUN_FAILED = "PROJECT EXECUTION FAILED"
265 def __init__(self, name, outdir, log_fn, timeout, unit=False):
266 """Constructor
267
268 @param name Arbitrary name of the created thread
269 @param outdir Working directory, should be where qemu.pid gets created
270 by kbuild
271 @param log_fn Absolute path to write out QEMU's log data
272 @param timeout Kill the QEMU process if it doesn't finish up within
273 the given number of seconds
274 """
275 self.lock = threading.Lock()
276 self.state = "waiting"
277 self.metrics = {}
278 self.metrics["qemu_time"] = 0
279 self.metrics["ram_size"] = 0
280 self.metrics["rom_size"] = 0
281 self.unit = unit
282
283 def set_state(self, state, metrics):
284 self.lock.acquire()
285 self.state = state
286 self.metrics.update(metrics)
287 self.lock.release()
288
289 def get_state(self):
290 self.lock.acquire()
291 ret = (self.state, self.metrics)
292 self.lock.release()
293 return ret
294
295class UnitHandler(Handler):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300296 def __init__(self, name, sourcedir, outdir, run_log, valgrind_log, timeout):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300297 """Constructor
298
299 @param name Arbitrary name of the created thread
300 @param outdir Working directory containing the test binary
301 @param run_log Absolute path to runtime logs
302 @param valgrind Absolute path to valgrind's log
303 @param timeout Kill the QEMU process if it doesn't finish up within
304 the given number of seconds
305 """
306 super().__init__(name, outdir, run_log, timeout, True)
307
308 self.timeout = timeout
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300309 self.sourcedir = sourcedir
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300310 self.outdir = outdir
311 self.run_log = run_log
312 self.valgrind_log = valgrind_log
313 self.returncode = 0
314 self.set_state("running", {})
315
316 def handle(self):
317 out_state = "failed"
318
319 with open(self.run_log, "wt") as rl, open(self.valgrind_log, "wt") as vl:
320 try:
321 binary = os.path.join(self.outdir, "testbinary")
322 command = [binary]
323 if shutil.which("valgrind"):
324 command = ["valgrind", "--error-exitcode=2",
325 "--leak-check=full"] + command
326 returncode = subprocess.call(command, timeout=self.timeout,
327 stdout=rl, stderr=vl)
328 self.returncode = returncode
329 if returncode != 0:
330 if self.returncode == 1:
331 out_state = "failed"
332 else:
333 out_state = "failed valgrind"
334 else:
335 out_state = "passed"
336 except subprocess.TimeoutExpired:
337 out_state = "timeout"
338 self.returncode = 1
339
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300340 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
341
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300342 self.set_state(out_state, {})
343
344class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700345 """Spawns a thread to monitor QEMU output from pipes
346
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400347 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700348 We need to do this as once qemu starts, it runs forever until killed.
349 Test cases emit special messages to the console as they run, we check
350 for these to collect whether the test passed or failed.
351 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700352
353 @staticmethod
354 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results):
355 fifo_in = fifo_fn + ".in"
356 fifo_out = fifo_fn + ".out"
357
358 # These in/out nodes are named from QEMU's perspective, not ours
359 if os.path.exists(fifo_in):
360 os.unlink(fifo_in)
361 os.mkfifo(fifo_in)
362 if os.path.exists(fifo_out):
363 os.unlink(fifo_out)
364 os.mkfifo(fifo_out)
365
366 # We don't do anything with out_fp but we need to open it for
367 # writing so that QEMU doesn't block, due to the way pipes work
368 out_fp = open(fifo_in, "wb")
369 # Disable internal buffering, we don't
370 # want read() or poll() to ever block if there is data in there
371 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800372 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700373
374 start_time = time.time()
375 timeout_time = start_time + timeout
376 p = select.poll()
377 p.register(in_fp, select.POLLIN)
378
379 metrics = {}
380 line = ""
381 while True:
382 this_timeout = int((timeout_time - time.time()) * 1000)
383 if this_timeout < 0 or not p.poll(this_timeout):
384 out_state = "timeout"
385 break
386
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500387 try:
388 c = in_fp.read(1).decode("utf-8")
389 except UnicodeDecodeError:
390 # Test is writing something weird, fail
391 out_state = "unexpected byte"
392 break
393
Andrew Boie6acbe632015-07-17 12:03:52 -0700394 if c == "":
395 # EOF, this shouldn't happen unless QEMU crashes
396 out_state = "unexpected eof"
397 break
398 line = line + c
399 if c != "\n":
400 continue
401
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300402 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700403 log_out_fp.write(line)
404 log_out_fp.flush()
405 line = line.strip()
406 verbose("QEMU: %s" % line)
407
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300408 if line == handler.RUN_PASSED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700409 out_state = "passed"
410 break
411
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300412 if line == handler.RUN_FAILED:
Andrew Boie6acbe632015-07-17 12:03:52 -0700413 out_state = "failed"
414 break
415
416 # TODO: Add support for getting numerical performance data
417 # from test cases. Will involve extending test case reporting
418 # APIs. Add whatever gets reported to the metrics dictionary
419 line = ""
420
421 metrics["qemu_time"] = time.time() - start_time
422 verbose("QEMU complete (%s) after %f seconds" %
423 (out_state, metrics["qemu_time"]))
424 handler.set_state(out_state, metrics)
425
426 log_out_fp.close()
427 out_fp.close()
428 in_fp.close()
429
430 pid = int(open(pid_fn).read())
431 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800432 try:
433 os.kill(pid, signal.SIGTERM)
434 except ProcessLookupError:
435 # Oh well, as long as it's dead! User probably sent Ctrl-C
436 pass
437
Andrew Boie6acbe632015-07-17 12:03:52 -0700438 os.unlink(fifo_in)
439 os.unlink(fifo_out)
440
Andrew Boie6acbe632015-07-17 12:03:52 -0700441 def __init__(self, name, outdir, log_fn, timeout):
442 """Constructor
443
444 @param name Arbitrary name of the created thread
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300445 @param outdir Working directory, should be where qemu.pid gets created
Andrew Boie6acbe632015-07-17 12:03:52 -0700446 by kbuild
447 @param log_fn Absolute path to write out QEMU's log data
448 @param timeout Kill the QEMU process if it doesn't finish up within
449 the given number of seconds
450 """
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300451 super().__init__(name, outdir, log_fn, timeout)
Andrew Boie6acbe632015-07-17 12:03:52 -0700452 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700453
454 # We pass this to QEMU which looks for fifos with .in and .out
455 # suffixes.
456 self.fifo_fn = os.path.join(outdir, "qemu-fifo")
457
458 self.pid_fn = os.path.join(outdir, "qemu.pid")
459 if os.path.exists(self.pid_fn):
460 os.unlink(self.pid_fn)
461
462 self.log_fn = log_fn
463 self.thread = threading.Thread(name=name, target=QEMUHandler._thread,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300464 args=(self, timeout, outdir,
465 self.log_fn, self.fifo_fn,
466 self.pid_fn, self.results))
Andrew Boie6acbe632015-07-17 12:03:52 -0700467 self.thread.daemon = True
468 verbose("Spawning QEMU process for %s" % name)
469 self.thread.start()
470
Andrew Boie6acbe632015-07-17 12:03:52 -0700471 def get_fifo(self):
472 return self.fifo_fn
473
Andrew Boie6acbe632015-07-17 12:03:52 -0700474class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700475
Andrew Boie8eed4b02017-06-14 16:08:16 -0700476 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800477 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500478 "_k_memory_pool", "exceptions", "initshell",
Andrew Boie65a9d2a2017-06-27 10:51:23 -0700479 "_static_thread_area", "_k_timer_area", "_k_work_area",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500480 "_k_mem_slab_area", "_k_mem_pool_area",
481 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
482 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
Tomasz Bursztykab56c4df2016-08-18 14:07:22 +0200483 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Tomasz Bursztykae38a9e82017-03-08 09:30:03 +0100484 "net_if", "net_if_event", "net_stack", "net_l2_data",
Andrew Boie8eed4b02017-06-14 16:08:16 -0700485 "_k_queue_area", "_net_buf_pool_area", "app_datas" ]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700486 # These get copied into RAM only on non-XIP
Andrew Boie3b930212016-06-07 13:11:45 -0700487 ro_sections = ["text", "ctors", "init_array", "reset",
Anas Nashifbfcdfaf2016-12-15 10:02:14 -0500488 "rodata", "devconfig", "net_l2", "vector"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700489
Andrew Boie52fef672016-11-29 12:21:59 -0800490 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700491 """Constructor
492
Andrew Boiebbd670c2015-08-17 13:16:11 -0700493 @param filename Path to the output binary
494 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700495 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700496 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700497 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700498 magic = f.read(4)
499
Andrew Boie08ce5a52016-02-22 13:28:10 -0800500 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700501 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700502
503 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
504 # GREP can not be used as it returns an error if the symbol is not found.
Andrew Boiebbd670c2015-08-17 13:16:11 -0700505 is_xip_command = "nm " + filename + " | awk '/CONFIG_XIP/ { print $3 }'"
Andrew Boie8f0211d2016-03-02 20:40:29 -0800506 is_xip_output = subprocess.check_output(is_xip_command, shell=True,
507 stderr=subprocess.STDOUT).decode("utf-8").strip()
508 if is_xip_output.endswith("no symbols"):
509 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700510 self.is_xip = (len(is_xip_output) != 0)
511
Andrew Boiebbd670c2015-08-17 13:16:11 -0700512 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700513 self.sections = []
514 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700515 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800516 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700517
518 self._calculate_sizes()
519
520 def get_ram_size(self):
521 """Get the amount of RAM the application will use up on the device
522
523 @return amount of RAM, in bytes
524 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700525 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700526
527 def get_rom_size(self):
528 """Get the size of the data that this application uses on device's flash
529
530 @return amount of ROM, in bytes
531 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700532 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700533
534 def unrecognized_sections(self):
535 """Get a list of sections inside the binary that weren't recognized
536
David B. Kinder29963c32017-06-16 12:32:42 -0700537 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700538 """
539 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700540 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700541 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700542 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700543 return slist
544
545 def _calculate_sizes(self):
546 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700547 objdump_command = "objdump -h " + self.filename
Andrew Boie6acbe632015-07-17 12:03:52 -0700548 objdump_output = subprocess.check_output(objdump_command,
Andrew Boie08ce5a52016-02-22 13:28:10 -0800549 shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700550
551 for line in objdump_output:
552 words = line.split()
553
554 if (len(words) == 0): # Skip lines that are too short
555 continue
556
557 index = words[0]
558 if (not index[0].isdigit()): # Skip lines that do not start
559 continue # with a digit
560
561 name = words[1] # Skip lines with section names
562 if (name[0] == '.'): # starting with '.'
563 continue
564
Andrew Boie73b4ee62015-10-07 11:33:22 -0700565 # TODO this doesn't actually reflect the size in flash or RAM as
566 # it doesn't include linker-imposed padding between sections.
567 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700568 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700569 if size == 0:
570 continue
571
Andrew Boie73b4ee62015-10-07 11:33:22 -0700572 load_addr = int(words[4], 16)
573 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700574
575 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700576 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700577 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700578 if name in SizeCalculator.alloc_sections:
579 self.ram_size += size
580 stype = "alloc"
581 elif name in SizeCalculator.rw_sections:
582 self.ram_size += size
583 self.rom_size += size
584 stype = "rw"
585 elif name in SizeCalculator.ro_sections:
586 self.rom_size += size
587 if not self.is_xip:
588 self.ram_size += size
589 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700590 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700591 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800592 if name not in self.extra_sections:
593 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700594
Andrew Boie73b4ee62015-10-07 11:33:22 -0700595 self.sections.append({"name" : name, "load_addr" : load_addr,
596 "size" : size, "virt_addr" : virt_addr,
Andy Ross8d801a52016-10-04 11:48:21 -0700597 "type" : stype, "recognized" : recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700598
599
600class MakeGoal:
601 """Metadata class representing one of the sub-makes called by MakeGenerator
602
David B. Kinder29963c32017-06-16 12:32:42 -0700603 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -0700604 with TestInstances to get a complete picture of what happened during a test.
605 MakeGenerator is used for tasks outside of building tests (such as
606 defconfigs) which is why MakeGoal is a separate class from TestInstance.
607 """
608 def __init__(self, name, text, qemu, make_log, build_log, run_log,
609 qemu_log):
610 self.name = name
611 self.text = text
612 self.qemu = qemu
613 self.make_log = make_log
614 self.build_log = build_log
615 self.run_log = run_log
616 self.qemu_log = qemu_log
617 self.make_state = "waiting"
618 self.failed = False
619 self.finished = False
620 self.reason = None
621 self.metrics = {}
622
623 def get_error_log(self):
624 if self.make_state == "waiting":
625 # Shouldn't ever see this; breakage in the main Makefile itself.
626 return self.make_log
627 elif self.make_state == "building":
628 # Failure when calling the sub-make to build the code
629 return self.build_log
630 elif self.make_state == "running":
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400631 # Failure in sub-make for "make run", qemu probably failed to start
Andrew Boie6acbe632015-07-17 12:03:52 -0700632 return self.run_log
633 elif self.make_state == "finished":
634 # QEMU finished, but timed out or otherwise wasn't successful
635 return self.qemu_log
636
637 def fail(self, reason):
638 self.failed = True
639 self.finished = True
640 self.reason = reason
641
642 def success(self):
643 self.finished = True
644
645 def __str__(self):
646 if self.finished:
647 if self.failed:
648 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
649 self.get_error_log())
650 else:
651 return "[%s] passed" % self.name
652 else:
653 return "[%s] in progress (%s)" % (self.name, self.make_state)
654
655
656class MakeGenerator:
657 """Generates a Makefile which just calls a bunch of sub-make sessions
658
659 In any given test suite we may need to build dozens if not hundreds of
660 test cases. The cleanest way to parallelize this is to just let Make
661 do the parallelization, sharing the jobserver among all the different
662 sub-make targets.
663 """
664
665 GOAL_HEADER_TMPL = """.PHONY: {goal}
666{goal}:
667"""
668
669 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Andrew Boief7a6e282017-02-02 13:04:57 -0800670\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 -0700671"""
672
673 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
674
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300675 re_make = re.compile("sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700676
Anas Nashife3febe92016-11-30 14:25:44 -0500677 def __init__(self, base_outdir, asserts=False, deprecations=False, ccache=0):
Andrew Boie6acbe632015-07-17 12:03:52 -0700678 """MakeGenerator constructor
679
680 @param base_outdir Intended to be the base out directory. A make.log
681 file will be created here which contains the output of the
682 top-level Make session, as well as the dynamic control Makefile
683 @param verbose If true, pass V=1 to all the sub-makes which greatly
684 increases their verbosity
685 """
686 self.goals = {}
687 if not os.path.exists(base_outdir):
688 os.makedirs(base_outdir)
689 self.logfile = os.path.join(base_outdir, "make.log")
690 self.makefile = os.path.join(base_outdir, "Makefile")
Andrew Boie55121052016-07-20 11:52:04 -0700691 self.asserts = asserts
Anas Nashife3febe92016-11-30 14:25:44 -0500692 self.deprecations = deprecations
Kumar Galad5719a62016-10-26 12:30:26 -0500693 self.ccache = ccache
Andrew Boie6acbe632015-07-17 12:03:52 -0700694
695 def _get_rule_header(self, name):
696 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
697
698 def _get_sub_make(self, name, phase, workdir, outdir, logfile, args):
699 verb = "1" if VERBOSE else "0"
700 args = " ".join(args)
Anas Nashife3febe92016-11-30 14:25:44 -0500701
Andrew Boie55121052016-07-20 11:52:04 -0700702 if self.asserts:
703 cflags="-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
704 else:
705 cflags=""
Anas Nashife3febe92016-11-30 14:25:44 -0500706
707 if self.deprecations:
708 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800709
710 if self.ccache:
711 args = args + " USE_CCACHE=1"
712
713 return MakeGenerator.MAKE_RULE_TMPL.format(phase=phase, goal=name,
Andrew Boie55121052016-07-20 11:52:04 -0700714 outdir=outdir, cflags=cflags,
Andrew Boie6acbe632015-07-17 12:03:52 -0700715 directory=workdir, verb=verb,
716 args=args, logfile=logfile)
717
718 def _get_rule_footer(self, name):
719 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
720
721 def _add_goal(self, outdir):
722 if not os.path.exists(outdir):
723 os.makedirs(outdir)
724
Kumar Gala914d92e2017-06-22 16:19:11 -0500725 def add_build_goal(self, name, directory, outdir, args, buildlog):
Andrew Boie6acbe632015-07-17 12:03:52 -0700726 """Add a goal to invoke a Kbuild session
727
728 @param name A unique string name for this build goal. The results
729 dictionary returned by execute() will be keyed by this name.
730 @param directory Absolute path to working directory, will be passed
731 to make -C
732 @param outdir Absolute path to output directory, will be passed to
733 Kbuild via -O=<path>
734 @param args Extra command line arguments to pass to 'make', typically
735 environment variables or specific Make goals
736 """
737 self._add_goal(outdir)
Kumar Gala914d92e2017-06-22 16:19:11 -0500738 build_logfile = os.path.join(outdir, buildlog)
Andrew Boie6acbe632015-07-17 12:03:52 -0700739 text = (self._get_rule_header(name) +
740 self._get_sub_make(name, "building", directory,
741 outdir, build_logfile, args) +
742 self._get_rule_footer(name))
743 self.goals[name] = MakeGoal(name, text, None, self.logfile, build_logfile,
744 None, None)
745
746 def add_qemu_goal(self, name, directory, outdir, args, timeout=30):
747 """Add a goal to build a Zephyr project and then run it under QEMU
748
749 The generated make goal invokes Make twice, the first time it will
750 build the default goal, and the second will invoke the 'qemu' goal.
751 The output of the QEMU session will be monitored, and terminated
752 either upon pass/fail result of the test program, or the timeout
753 is reached.
754
755 @param name A unique string name for this build goal. The results
756 dictionary returned by execute() will be keyed by this name.
757 @param directory Absolute path to working directory, will be passed
758 to make -C
759 @param outdir Absolute path to output directory, will be passed to
760 Kbuild via -O=<path>
761 @param args Extra command line arguments to pass to 'make', typically
762 environment variables. Do not pass specific Make goals here.
763 @param timeout Maximum length of time QEMU session should be allowed
764 to run before automatically killing it. Default is 30 seconds.
765 """
766
767 self._add_goal(outdir)
768 build_logfile = os.path.join(outdir, "build.log")
769 run_logfile = os.path.join(outdir, "run.log")
770 qemu_logfile = os.path.join(outdir, "qemu.log")
771
772 q = QEMUHandler(name, outdir, qemu_logfile, timeout)
773 args.append("QEMU_PIPE=%s" % q.get_fifo())
774 text = (self._get_rule_header(name) +
775 self._get_sub_make(name, "building", directory,
776 outdir, build_logfile, args) +
777 self._get_sub_make(name, "running", directory,
778 outdir, run_logfile,
Mazen NEIFER7f046372017-01-31 12:41:33 +0100779 args + ["run"]) +
Andrew Boie6acbe632015-07-17 12:03:52 -0700780 self._get_rule_footer(name))
781 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
782 run_logfile, qemu_logfile)
783
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300784 def add_unit_goal(self, name, directory, outdir, args, timeout=30, coverage=False):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300785 self._add_goal(outdir)
786 build_logfile = os.path.join(outdir, "build.log")
787 run_logfile = os.path.join(outdir, "run.log")
788 qemu_logfile = os.path.join(outdir, "qemu.log")
789 valgrind_logfile = os.path.join(outdir, "valgrind.log")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300790 if coverage:
791 args += ["COVERAGE=1"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300792
793 # we handle running in the UnitHandler class
794 text = (self._get_rule_header(name) +
795 self._get_sub_make(name, "building", directory,
796 outdir, build_logfile, args) +
797 self._get_rule_footer(name))
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300798 q = UnitHandler(name, directory, outdir, run_logfile, valgrind_logfile, timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300799 self.goals[name] = MakeGoal(name, text, q, self.logfile, build_logfile,
800 run_logfile, valgrind_logfile)
801
Andrew Boie6acbe632015-07-17 12:03:52 -0700802
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300803 def add_test_instance(self, ti, build_only=False, enable_slow=False, coverage=False,
Andrew Boieba612002016-09-01 10:41:03 -0700804 extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -0700805 """Add a goal to build/test a TestInstance object
806
807 @param ti TestInstance object to build. The status dictionary returned
808 by execute() will be keyed by its .name field.
809 """
810 args = ti.test.extra_args[:]
Anas Nashifa792a3d2017-04-04 18:47:49 -0400811 args.extend(["ARCH=%s" % ti.platform.arch,
Anas Nashifc7406082015-12-13 15:00:31 -0500812 "BOARD=%s" % ti.platform.name])
Andrew Boieba612002016-09-01 10:41:03 -0700813 args.extend(extra_args)
Andrew Boie6bb087c2016-02-10 13:39:00 -0800814 if (ti.platform.qemu_support and (not ti.build_only) and
815 (not build_only) and (enable_slow or not ti.test.slow)):
Andrew Boie6acbe632015-07-17 12:03:52 -0700816 self.add_qemu_goal(ti.name, ti.test.code_location, ti.outdir,
817 args, ti.test.timeout)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300818 elif ti.test.type == "unit":
819 self.add_unit_goal(ti.name, ti.test.code_location, ti.outdir,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300820 args, ti.test.timeout, coverage)
Andrew Boie6acbe632015-07-17 12:03:52 -0700821 else:
Kumar Gala914d92e2017-06-22 16:19:11 -0500822 self.add_build_goal(ti.name, ti.test.code_location, ti.outdir,
823 args, "build.log")
Andrew Boie6acbe632015-07-17 12:03:52 -0700824
825 def execute(self, callback_fn=None, context=None):
826 """Execute all the registered build goals
827
828 @param callback_fn If not None, a callback function will be called
829 as individual goals transition between states. This function
830 should accept two parameters: a string state and an arbitrary
831 context object, supplied here
832 @param context Context object to pass to the callback function.
833 Type and semantics are specific to that callback function.
834 @return A dictionary mapping goal names to final status.
835 """
836
Andrew Boie08ce5a52016-02-22 13:28:10 -0800837 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -0700838 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -0800839 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -0700840 # Create our dynamic Makefile and execute it.
841 # Watch stderr output which is where we will keep
842 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -0800843 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -0700844 tf.write(goal.text)
845 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
846 tf.flush()
847
Daniel Leung6b170072016-04-07 12:10:25 -0700848 cmd = ["make", "-k", "-j", str(CPU_COUNTS * 2), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700849 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
850 stdout=devnull)
851
852 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -0800853 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -0700854 make_log.write(line)
855 verbose("MAKE: " + repr(line.strip()))
856 m = MakeGenerator.re_make.match(line)
857 if not m:
858 continue
859
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300860 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -0700861 if error:
862 goal = self.goals[error]
863 else:
864 goal = self.goals[name]
865 goal.make_state = state
866
867
868 if error:
Andrew Boie822b0872017-01-10 13:32:40 -0800869 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400870 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -0800871 # nonzero status.
872 # Need to distinguish this case from a compilation failure.
873 if goal.qemu:
874 goal.fail("qemu_crash")
875 else:
876 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -0700877 else:
878 if state == "finished":
879 if goal.qemu:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300880 if goal.qemu.unit:
881 # We can't run unit tests with Make
882 goal.qemu.handle()
883 if goal.qemu.returncode == 2:
884 goal.qemu_log = goal.qemu.valgrind_log
885 elif goal.qemu.returncode:
886 goal.qemu_log = goal.qemu.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700887 thread_status, metrics = goal.qemu.get_state()
888 goal.metrics.update(metrics)
889 if thread_status == "passed":
890 goal.success()
891 else:
892 goal.fail(thread_status)
893 else:
894 goal.success()
895
896 if callback_fn:
897 callback_fn(context, self.goals, goal)
898
899 p.wait()
900 return self.goals
901
902
903# "list" - List of strings
904# "list:<type>" - List of <type>
905# "set" - Set of unordered, unique strings
906# "set:<type>" - Set of <type>
907# "float" - Floating point
908# "int" - Integer
909# "bool" - Boolean
910# "str" - String
911
912# XXX Be sure to update __doc__ if you change any of this!!
913
914arch_valid_keys = {"name" : {"type" : "str", "required" : True},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500915 "platforms" : {"type" : "list", "required" : True},
916 "supported_toolchains" : {"type" : "list", "required" : True}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700917
918platform_valid_keys = {"qemu_support" : {"type" : "bool", "default" : False},
Javier B Perez4b554ba2016-08-15 13:25:33 -0500919 "supported_toolchains" : {"type" : "list", "default" : []}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700920
921testcase_valid_keys = {"tags" : {"type" : "set", "required" : True},
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300922 "type" : {"type" : "str", "default": "integration"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700923 "extra_args" : {"type" : "list"},
924 "build_only" : {"type" : "bool", "default" : False},
Anas Nashifa792a3d2017-04-04 18:47:49 -0400925 "build_on_all" : {"type" : "bool", "default" : False},
Anas Nashif2bd99bc2015-10-12 13:10:57 -0400926 "skip" : {"type" : "bool", "default" : False},
Andrew Boie6bb087c2016-02-10 13:39:00 -0800927 "slow" : {"type" : "bool", "default" : False},
Andrew Boie6acbe632015-07-17 12:03:52 -0700928 "timeout" : {"type" : "int", "default" : 60},
Anas Nashifa792a3d2017-04-04 18:47:49 -0400929 "min_ram" : {"type" : "int", "default" : 8},
930 "depends_on": {"type" : "set"},
931 "min_flash" : {"type" : "int", "default" : 32},
Andrew Boie6acbe632015-07-17 12:03:52 -0700932 "arch_whitelist" : {"type" : "set"},
Anas Nashif30d13872015-10-05 10:02:45 -0400933 "arch_exclude" : {"type" : "set"},
Andrew Boie52fef672016-11-29 12:21:59 -0800934 "extra_sections" : {"type" : "list", "default" : []},
Anas Nashif30d13872015-10-05 10:02:45 -0400935 "platform_exclude" : {"type" : "set"},
Andrew Boie6acbe632015-07-17 12:03:52 -0700936 "platform_whitelist" : {"type" : "set"},
Anas Nashifb17e1ca2017-06-27 18:05:30 -0400937 "toolchain_exclude" : {"type" : "set"},
938 "toolchain_whitelist" : {"type" : "set"},
Andrew Boie3ea78922016-03-24 14:46:00 -0700939 "filter" : {"type" : "str"}}
Andrew Boie6acbe632015-07-17 12:03:52 -0700940
941
942class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -0400943 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -0700944 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700945 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -0700946 """Instantiate a new SanityConfigParser object
947
Anas Nashifa792a3d2017-04-04 18:47:49 -0400948 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -0700949 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700950 cp = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -0700951 self.filename = filename
952 self.cp = cp
953
954 def _cast_value(self, value, typestr):
Anas Nashifa792a3d2017-04-04 18:47:49 -0400955
956 if type(value) is str:
957 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -0700958 if typestr == "str":
959 return v
960
961 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400962 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -0700963
964 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400965 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -0700966
967 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -0400968 return value
Andrew Boie6acbe632015-07-17 12:03:52 -0700969
970 elif typestr.startswith("list"):
971 vs = v.split()
972 if len(typestr) > 4 and typestr[4] == ":":
973 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
974 else:
975 return vs
976
977 elif typestr.startswith("set"):
978 vs = v.split()
979 if len(typestr) > 3 and typestr[3] == ":":
980 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
981 else:
982 return set(vs)
983
984 else:
985 raise ConfigurationError(self.filename, "unknown type '%s'" % value)
986
Anas Nashifa792a3d2017-04-04 18:47:49 -0400987 def section(self,name):
988 for s in self.sections():
989 if name in s:
990 return s.get(name, {})
Andrew Boie6acbe632015-07-17 12:03:52 -0700991
992 def sections(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -0400993 """Get the set of test sections within the .yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -0700994
995 @return a list of string section names"""
Anas Nashifa792a3d2017-04-04 18:47:49 -0400996 return self.cp['tests']
Andrew Boie6acbe632015-07-17 12:03:52 -0700997
998 def get_section(self, section, valid_keys):
999 """Get a dictionary representing the keys/values within a section
1000
Anas Nashifa792a3d2017-04-04 18:47:49 -04001001 @param section The section in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001002 @param valid_keys A dictionary representing the intended semantics
1003 for this section. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001004 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001005 here, it will generate an error. Each value in this dictionary
1006 is another dictionary containing metadata:
1007
1008 "default" - Default value if not given
1009 "type" - Data type to convert the text value to. Simple types
1010 supported are "str", "float", "int", "bool" which will get
1011 converted to respective Python data types. "set" and "list"
1012 may also be specified which will split the value by
1013 whitespace (but keep the elements as strings). finally,
1014 "list:<type>" and "set:<type>" may be given which will
1015 perform a type conversion after splitting the value up.
1016 "required" - If true, raise an error if not defined. If false
1017 and "default" isn't specified, a type conversion will be
1018 done on an empty string
1019 @return A dictionary containing the section key-value pairs with
1020 type conversion and default values filled in per valid_keys
1021 """
1022
1023 d = {}
Anas Nashifa792a3d2017-04-04 18:47:49 -04001024 for k, v in self.section(section).items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001025 if k not in valid_keys:
1026 raise ConfigurationError(self.filename,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001027 "Unknown config key '%s' in definition for '%s'"
1028 % (k, section))
Andrew Boie6acbe632015-07-17 12:03:52 -07001029 d[k] = v
1030
Andrew Boie08ce5a52016-02-22 13:28:10 -08001031 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001032 if k not in d:
1033 if "required" in kinfo:
1034 required = kinfo["required"]
1035 else:
1036 required = False
1037
1038 if required:
1039 raise ConfigurationError(self.filename,
1040 "missing required value for '%s' in section '%s'"
1041 % (k, section))
1042 else:
1043 if "default" in kinfo:
1044 default = kinfo["default"]
1045 else:
1046 default = self._cast_value("", kinfo["type"])
1047 d[k] = default
1048 else:
1049 try:
1050 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001051 except ValueError as ve:
Andrew Boie6acbe632015-07-17 12:03:52 -07001052 raise ConfigurationError(self.filename,
1053 "bad %s value '%s' for key '%s' in section '%s'"
1054 % (kinfo["type"], d[k], k, section))
1055
1056 return d
1057
1058
1059class Platform:
1060 """Class representing metadata for a particular platform
1061
Anas Nashifc7406082015-12-13 15:00:31 -05001062 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001063
1064 yaml_platform_schema = scl.yaml_load(
1065 os.path.join(os.environ['ZEPHYR_BASE'],
1066 "scripts", "sanitycheck-platform-schema.yaml"))
1067
Anas Nashifa792a3d2017-04-04 18:47:49 -04001068 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001069 """Constructor.
1070
1071 @param arch Architecture object for this platform
Anas Nashifc7406082015-12-13 15:00:31 -05001072 @param name String name for this platform, same as BOARD
Andrew Boie6acbe632015-07-17 12:03:52 -07001073 @param plat_dict SanityConfigParser output on the relevant section
1074 in the architecture configuration file which has lots of metadata.
1075 See the Architecture class.
1076 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001077 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001078 cp = scp.cp
1079
1080 self.name = cp['identifier']
1081 # if no RAM size is specified by the board, take a default of 128K
1082 self.ram = cp.get("ram", 128)
1083 testing = cp.get("testing", {})
1084 self.ignore_tags = testing.get("ignore_tags", [])
1085 self.default = testing.get("default", False)
1086 # if no flash size is specified by the board, take a default of 512K
1087 self.flash = cp.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001088 self.supported = set()
1089 for supp_feature in cp.get("supported", []):
1090 for item in supp_feature.split(":"):
1091 self.supported.add(item)
1092
Anas Nashifa792a3d2017-04-04 18:47:49 -04001093 self.qemu_support = True if cp.get('type', "na") == 'qemu' else False
1094 self.arch = cp['arch']
1095 self.supported_toolchains = cp.get("toolchain", [])
Andrew Boie41878222016-11-03 11:58:53 -07001096 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001097 pass
1098
Andrew Boie6acbe632015-07-17 12:03:52 -07001099 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001100 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001101
1102
1103class Architecture:
1104 """Class representing metadata for a particular architecture
1105 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001106 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001107 """Architecture constructor
1108
1109 @param cfile Path to Architecture configuration file, which gives
1110 info about the arch and all the platforms for it
1111 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001112 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001113
Anas Nashifa792a3d2017-04-04 18:47:49 -04001114 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001115
1116 def __repr__(self):
1117 return "<arch %s>" % self.name
1118
1119
1120class TestCase:
1121 """Class representing a test application
1122 """
Andrew Boie3ea78922016-03-24 14:46:00 -07001123 def __init__(self, testcase_root, workdir, name, tc_dict, inifile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001124 """TestCase constructor.
1125
Anas Nashifa792a3d2017-04-04 18:47:49 -04001126 This gets called by TestSuite as it finds and reads test yaml files.
1127 Multiple TestCase instances may be generated from a single testcase.yaml,
Andrew Boie6acbe632015-07-17 12:03:52 -07001128 each one corresponds to a section within that file.
1129
Andrew Boie6acbe632015-07-17 12:03:52 -07001130 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001131 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001132 the test case is <workdir>/<name>.
1133
1134 @param testcase_root Absolute path to the root directory where
1135 all the test cases live
1136 @param workdir Relative path to the project directory for this
1137 test application from the test_case root.
1138 @param name Name of this test case, corresponding to the section name
1139 in the test case configuration file. For many test cases that just
1140 define one test, can be anything and is usually "test". This is
1141 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001142 the testcase.yaml defines multiple tests
Andrew Boie6acbe632015-07-17 12:03:52 -07001143 @param tc_dict Dictionary with section values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001144 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001145 """
1146 self.code_location = os.path.join(testcase_root, workdir)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001147 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001148 self.tags = tc_dict["tags"]
1149 self.extra_args = tc_dict["extra_args"]
1150 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001151 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001152 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001153 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001154 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001155 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1156 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001157 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001158 self.timeout = tc_dict["timeout"]
1159 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001160 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001161 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001162 self.min_ram = tc_dict["min_ram"]
1163 self.depends_on = tc_dict["depends_on"]
1164 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001165 self.extra_sections = tc_dict["extra_sections"]
Andrew Boie14ac9022016-04-08 13:31:53 -07001166 self.path = os.path.join(os.path.basename(os.path.abspath(testcase_root)),
1167 workdir, name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 self.name = self.path # for now
Anas Nashif2cf0df02015-10-10 09:29:43 -04001169 self.defconfig = {}
Andrew Boie3ea78922016-03-24 14:46:00 -07001170 self.inifile = inifile
Andrew Boie6acbe632015-07-17 12:03:52 -07001171
Andrew Boie6acbe632015-07-17 12:03:52 -07001172 def __repr__(self):
1173 return self.name
1174
1175
1176
1177class TestInstance:
1178 """Class representing the execution of a particular TestCase on a platform
1179
1180 @param test The TestCase object we want to build/execute
1181 @param platform Platform object that we want to build and run against
1182 @param base_outdir Base directory for all test results. The actual
1183 out directory used is <outdir>/<platform>/<test case name>
1184 """
Andrew Boie6bb087c2016-02-10 13:39:00 -08001185 def __init__(self, test, platform, base_outdir, build_only=False,
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001186 slow=False, coverage=False):
Andrew Boie6acbe632015-07-17 12:03:52 -07001187 self.test = test
1188 self.platform = platform
1189 self.name = os.path.join(platform.name, test.path)
1190 self.outdir = os.path.join(base_outdir, platform.name, test.path)
1191 self.build_only = build_only or test.build_only
1192
1193 def calculate_sizes(self):
1194 """Get the RAM/ROM sizes of a test case.
1195
1196 This can only be run after the instance has been executed by
1197 MakeGenerator, otherwise there won't be any binaries to measure.
1198
1199 @return A SizeCalculator object
1200 """
Andrew Boie5d4eb782015-10-02 10:04:56 -07001201 fns = glob.glob(os.path.join(self.outdir, "*.elf"))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001202 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001203 if (len(fns) != 1):
1204 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001205 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001206
1207 def __repr__(self):
1208 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1209
1210
Andrew Boie4ef16c52015-08-28 12:36:03 -07001211def defconfig_cb(context, goals, goal):
1212 if not goal.failed:
1213 return
1214
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001215
Andrew Boie4ef16c52015-08-28 12:36:03 -07001216 info("%sCould not build defconfig for %s%s" %
1217 (COLOR_RED, goal.name, COLOR_NORMAL));
1218 if INLINE_LOGS:
1219 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001220 data = fp.read()
1221 sys.stdout.write(data)
1222 if log_file:
1223 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001224 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001225 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001226
Andrew Boie6acbe632015-07-17 12:03:52 -07001227
1228class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001229 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001230
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001231 yaml_tc_schema = scl.yaml_load(
1232 os.path.join(os.environ['ZEPHYR_BASE'],
1233 "scripts", "sanitycheck-tc-schema.yaml"))
1234
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001235 def __init__(self, arch_root, testcase_roots, outdir, coverage):
Andrew Boie6acbe632015-07-17 12:03:52 -07001236 # Keep track of which test cases we've filtered out and why
1237 discards = {}
1238 self.arches = {}
1239 self.testcases = {}
1240 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001241 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001242 self.instances = {}
1243 self.goals = None
1244 self.discards = None
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001245 self.coverage = coverage
Andrew Boie6acbe632015-07-17 12:03:52 -07001246
1247 arch_root = os.path.abspath(arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001248
Andrew Boie3d348712016-04-08 11:52:13 -07001249 for testcase_root in testcase_roots:
1250 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001251
Andrew Boie3d348712016-04-08 11:52:13 -07001252 debug("Reading test case configuration files under %s..." %
1253 testcase_root)
1254 for dirpath, dirnames, filenames in os.walk(testcase_root,
1255 topdown=True):
1256 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001257 if 'sample.yaml' in filenames:
1258 filename = 'sample.yaml'
1259 elif 'testcase.yaml' in filenames:
1260 filename = 'testcase.yaml'
1261 else:
1262 continue
1263 verbose("Found possible test case in " + dirpath)
1264 dirnames[:] = []
1265 yaml_path = os.path.join(dirpath, filename)
1266 try:
1267 cp = SanityConfigParser(yaml_path, self.yaml_tc_schema)
1268 except RuntimeError as e:
1269 error("E: %s: can't load: %s" % (yaml_path, e))
Andrew Boie3d348712016-04-08 11:52:13 -07001270
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001271 workdir = os.path.relpath(dirpath, testcase_root)
1272
1273 for section in cp.sections():
1274 name = list(section.keys())[0]
1275 tc_dict = cp.get_section(name, testcase_valid_keys)
1276 tc = TestCase(testcase_root, workdir, name, tc_dict,
1277 yaml_path)
1278 self.testcases[tc.name] = tc
Andrew Boie6acbe632015-07-17 12:03:52 -07001279
Anas Nashifa792a3d2017-04-04 18:47:49 -04001280 debug("Reading platform configuration files under %s..." % arch_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001281 for dirpath, dirnames, filenames in os.walk(arch_root):
1282 for filename in filenames:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001283 if filename.endswith(".yaml"):
Andrew Boie6acbe632015-07-17 12:03:52 -07001284 fn = os.path.join(dirpath, filename)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001285 verbose("Found plaform configuration " + fn)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001286 try:
1287 platform = Platform(fn)
1288 self.platforms.append(platform)
1289 except RuntimeError as e:
1290 error("E: %s: can't load: %s" % (fn, e))
Andrew Boie6acbe632015-07-17 12:03:52 -07001291
Anas Nashifa792a3d2017-04-04 18:47:49 -04001292 arches = []
1293 for p in self.platforms:
1294 arches.append(p.arch)
1295 for a in list(set(arches)):
1296 aplatforms = [ p for p in self.platforms if p.arch == a ]
1297 arch = Architecture(a, aplatforms)
1298 self.arches[a] = arch
1299
Andrew Boie6acbe632015-07-17 12:03:52 -07001300 self.instances = {}
1301
1302 def get_last_failed(self):
1303 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001304 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001305 result = []
1306 with open(LAST_SANITY, "r") as fp:
1307 cr = csv.DictReader(fp)
1308 for row in cr:
1309 if row["passed"] == "True":
1310 continue
1311 test = row["test"]
1312 platform = row["platform"]
1313 result.append((test, platform))
1314 return result
1315
Anas Nashifdfa86e22016-10-24 17:08:56 -04001316 def apply_filters(self, platform_filter, arch_filter, tag_filter, exclude_tag,
Andrew Boie821d8322016-03-22 10:08:35 -07001317 config_filter, testcase_filter, last_failed, all_plats,
Kumar Galad5719a62016-10-26 12:30:26 -05001318 platform_limit, toolchain, extra_args, enable_ccache):
Andrew Boie6acbe632015-07-17 12:03:52 -07001319 instances = []
1320 discards = {}
1321 verbose("platform filter: " + str(platform_filter))
1322 verbose(" arch_filter: " + str(arch_filter))
1323 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001324 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001325 verbose(" config_filter: " + str(config_filter))
Kumar Galad5719a62016-10-26 12:30:26 -05001326 verbose(" enable_ccache: " + str(enable_ccache))
Andrew Boie6acbe632015-07-17 12:03:52 -07001327
1328 if last_failed:
1329 failed_tests = self.get_last_failed()
1330
Andrew Boie821d8322016-03-22 10:08:35 -07001331 default_platforms = False
1332
1333 if all_plats:
1334 info("Selecting all possible platforms per test case")
1335 # When --all used, any --platform arguments ignored
1336 platform_filter = []
1337 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001338 info("Selecting default platforms per test case")
1339 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001340
Kumar Galad5719a62016-10-26 12:30:26 -05001341 mg = MakeGenerator(self.outdir, ccache=enable_ccache)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001342 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001343 for tc_name, tc in self.testcases.items():
1344 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001345 for plat in arch.platforms:
1346 instance = TestInstance(tc, plat, self.outdir)
1347
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001348 if (arch_name == "unit") != (tc.type == "unit"):
1349 continue
1350
Anas Nashifbfab06b2017-06-22 09:22:24 -04001351 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001352 platform_filter = []
1353
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001354 if tc.skip:
1355 continue
1356
Anas Nashif2cf0df02015-10-10 09:29:43 -04001357 if tag_filter and not tc.tags.intersection(tag_filter):
1358 continue
1359
Anas Nashifdfa86e22016-10-24 17:08:56 -04001360 if exclude_tag and tc.tags.intersection(exclude_tag):
1361 continue
1362
Anas Nashif2cf0df02015-10-10 09:29:43 -04001363 if testcase_filter and tc_name not in testcase_filter:
1364 continue
1365
1366 if last_failed and (tc.name, plat.name) not in failed_tests:
1367 continue
1368
1369 if arch_filter and arch_name not in arch_filter:
1370 continue
1371
1372 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1373 continue
1374
1375 if tc.arch_exclude and arch.name in tc.arch_exclude:
1376 continue
1377
1378 if tc.platform_exclude and plat.name in tc.platform_exclude:
1379 continue
1380
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001381 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1382 continue
1383
Anas Nashif2cf0df02015-10-10 09:29:43 -04001384 if platform_filter and plat.name not in platform_filter:
1385 continue
1386
Anas Nashifa792a3d2017-04-04 18:47:49 -04001387 if plat.ram <= tc.min_ram:
1388 continue
1389
1390 if set(plat.ignore_tags) & tc.tags:
1391 continue
1392
Kumar Gala5141d522017-07-07 08:05:48 -05001393 if tc.depends_on:
1394 dep_intersection = tc.depends_on.intersection(set(plat.supported))
1395 if dep_intersection != set(tc.depends_on):
1396 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001397
1398 if plat.flash < tc.min_flash:
1399 continue
1400
Anas Nashif2cf0df02015-10-10 09:29:43 -04001401 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1402 continue
1403
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001404 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1405 continue
1406
Anas Nashifcf21f5f2017-06-22 06:48:34 -04001407 if (tc.tc_filter and (plat.default or all_plats or platform_filter) and
1408 toolchain in plat.supported_toolchains):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001409 args = tc.extra_args[:]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001410 args.extend(["ARCH=" + plat.arch,
Andy Gross25309ab2017-06-06 21:29:09 -05001411 "BOARD=" + plat.name, "config-sanitycheck"])
Andrew Boieba612002016-09-01 10:41:03 -07001412 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001413 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001414 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07001415 # need a way to avoid different Make processes from clobbering
Anas Nashif2cf0df02015-10-10 09:29:43 -04001416 # each other since they all try to build them simultaneously
1417
1418 o = os.path.join(self.outdir, plat.name, tc.path)
Andy Gross25309ab2017-06-06 21:29:09 -05001419 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o,".config-sanitycheck")
1420 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Kumar Gala914d92e2017-06-22 16:19:11 -05001421 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location), o,
1422 args, "config-sanitycheck.log")
Anas Nashif2cf0df02015-10-10 09:29:43 -04001423
1424 info("Building testcase defconfigs...")
1425 results = mg.execute(defconfig_cb)
1426
Andrew Boie08ce5a52016-02-22 13:28:10 -08001427 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001428 if goal.failed:
1429 raise SanityRuntimeError("Couldn't build some defconfigs")
1430
Andrew Boie08ce5a52016-02-22 13:28:10 -08001431 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001432 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001433 defconfig = {}
1434 with open(out_config, "r") as fp:
1435 for line in fp.readlines():
1436 m = TestSuite.config_re.match(line)
1437 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001438 if line.strip() and not line.startswith("#"):
1439 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001440 continue
1441 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001442 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001443
Andrew Boie08ce5a52016-02-22 13:28:10 -08001444 for tc_name, tc in self.testcases.items():
1445 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001446 instance_list = []
1447 for plat in arch.platforms:
1448 instance = TestInstance(tc, plat, self.outdir)
1449
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001450 if (arch_name == "unit") != (tc.type == "unit"):
1451 # Discard silently
1452 continue
1453
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001454 if tc.skip:
1455 discards[instance] = "Skip filter"
1456 continue
1457
Anas Nashifbfab06b2017-06-22 09:22:24 -04001458 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001459 platform_filter = []
1460
Andrew Boie6acbe632015-07-17 12:03:52 -07001461 if tag_filter and not tc.tags.intersection(tag_filter):
1462 discards[instance] = "Command line testcase tag filter"
1463 continue
1464
Anas Nashifdfa86e22016-10-24 17:08:56 -04001465 if exclude_tag and tc.tags.intersection(exclude_tag):
1466 discards[instance] = "Command line testcase exclude filter"
1467 continue
1468
Andrew Boie6acbe632015-07-17 12:03:52 -07001469 if testcase_filter and tc_name not in testcase_filter:
1470 discards[instance] = "Testcase name filter"
1471 continue
1472
1473 if last_failed and (tc.name, plat.name) not in failed_tests:
1474 discards[instance] = "Passed or skipped during last run"
1475 continue
1476
1477 if arch_filter and arch_name not in arch_filter:
1478 discards[instance] = "Command line testcase arch filter"
1479 continue
1480
1481 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1482 discards[instance] = "Not in test case arch whitelist"
1483 continue
1484
Anas Nashif30d13872015-10-05 10:02:45 -04001485 if tc.arch_exclude and arch.name in tc.arch_exclude:
1486 discards[instance] = "In test case arch exclude"
1487 continue
1488
1489 if tc.platform_exclude and plat.name in tc.platform_exclude:
1490 discards[instance] = "In test case platform exclude"
1491 continue
1492
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001493 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1494 discards[instance] = "In test case toolchain exclude"
1495 continue
1496
Andrew Boie6acbe632015-07-17 12:03:52 -07001497 if platform_filter and plat.name not in platform_filter:
1498 discards[instance] = "Command line platform filter"
1499 continue
1500
1501 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1502 discards[instance] = "Not in testcase platform whitelist"
1503 continue
1504
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001505 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1506 discards[instance] = "Not in testcase toolchain whitelist"
1507 continue
1508
Javier B Perez4b554ba2016-08-15 13:25:33 -05001509 if toolchain and toolchain not in plat.supported_toolchains:
1510 discards[instance] = "Not supported by the toolchain"
1511 continue
1512
Anas Nashifa792a3d2017-04-04 18:47:49 -04001513 if plat.ram <= tc.min_ram:
1514 discards[instance] = "Not enough RAM"
1515 continue
1516
Kumar Gala5141d522017-07-07 08:05:48 -05001517 if tc.depends_on:
1518 dep_intersection = tc.depends_on.intersection(set(plat.supported))
1519 if dep_intersection != set(tc.depends_on):
1520 discards[instance] = "No hardware support"
1521 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001522
1523 if plat.flash< tc.min_flash:
1524 discards[instance] = "Not enough FLASH"
1525 continue
1526
1527 if set(plat.ignore_tags) & tc.tags:
1528 discards[instance] = "Excluded tags per platform"
1529 continue
1530
Andrew Boie3ea78922016-03-24 14:46:00 -07001531 defconfig = {"ARCH" : arch.name, "PLATFORM" : plat.name}
Javier B Perez79414542016-08-08 12:24:59 -05001532 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001533 for p, tdefconfig in tc.defconfig.items():
1534 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001535 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001536 break
1537
Andrew Boie3ea78922016-03-24 14:46:00 -07001538 if tc.tc_filter:
1539 try:
1540 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07001541 except (ValueError, SyntaxError) as se:
Andrew Boie3ea78922016-03-24 14:46:00 -07001542 sys.stderr.write("Failed processing %s\n" % tc.inifile)
1543 raise se
1544 if not res:
1545 discards[instance] = ("defconfig doesn't satisfy expression '%s'" %
1546 tc.tc_filter)
1547 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001548
Andrew Boie6acbe632015-07-17 12:03:52 -07001549 instance_list.append(instance)
1550
1551 if not instance_list:
1552 # Every platform in this arch was rejected already
1553 continue
1554
Anas Nashifa792a3d2017-04-04 18:47:49 -04001555 if default_platforms and not tc.build_on_all:
1556 if not tc.platform_whitelist:
1557 instances = list(filter(lambda tc: tc.platform.default, instance_list))
1558 self.add_instances(instances)
1559 else:
1560 self.add_instances(instance_list[:platform_limit])
1561
1562 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
1563 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07001564 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001565 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001566 self.discards = discards
1567 return discards
1568
Andrew Boie821d8322016-03-22 10:08:35 -07001569 def add_instances(self, ti_list):
1570 for ti in ti_list:
1571 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001572
Anas Nashife3febe92016-11-30 14:25:44 -05001573 def execute(self, cb, cb_context, build_only, enable_slow, enable_asserts, enable_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05001574 extra_args, enable_ccache):
Daniel Leung6b170072016-04-07 12:10:25 -07001575
1576 def calc_one_elf_size(name, goal):
1577 if not goal.failed:
1578 i = self.instances[name]
1579 sc = i.calculate_sizes()
1580 goal.metrics["ram_size"] = sc.get_ram_size()
1581 goal.metrics["rom_size"] = sc.get_rom_size()
1582 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07001583
Anas Nashife3febe92016-11-30 14:25:44 -05001584 mg = MakeGenerator(self.outdir, asserts=enable_asserts, deprecations=enable_deprecations,
1585 ccache=enable_ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07001586 for i in self.instances.values():
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001587 mg.add_test_instance(i, build_only, enable_slow, self.coverage, extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001588 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07001589
1590 # Parallelize size calculation
1591 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
1592 futures = [executor.submit(calc_one_elf_size, name, goal) \
1593 for name, goal in self.goals.items()]
1594 concurrent.futures.wait(futures)
1595
Andrew Boie6acbe632015-07-17 12:03:52 -07001596 return self.goals
1597
1598 def discard_report(self, filename):
1599 if self.discards == None:
1600 raise SanityRuntimeException("apply_filters() hasn't been run!")
1601
1602 with open(filename, "wb") as csvfile:
1603 fieldnames = ["test", "arch", "platform", "reason"]
1604 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1605 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001606 for instance, reason in self.discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001607 rowdict = {"test" : i.test.name,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001608 "arch" : i.platform.arch,
Andrew Boie6acbe632015-07-17 12:03:52 -07001609 "platform" : i.platform.name,
1610 "reason" : reason}
1611 cw.writerow(rowdict)
1612
1613 def compare_metrics(self, filename):
1614 # name, datatype, lower results better
1615 interesting_metrics = [("ram_size", int, True),
1616 ("rom_size", int, True)]
1617
1618 if self.goals == None:
1619 raise SanityRuntimeException("execute() hasn't been run!")
1620
1621 if not os.path.exists(filename):
1622 info("Cannot compare metrics, %s not found" % filename)
1623 return []
1624
1625 results = []
1626 saved_metrics = {}
1627 with open(filename) as fp:
1628 cr = csv.DictReader(fp)
1629 for row in cr:
1630 d = {}
1631 for m, _, _ in interesting_metrics:
1632 d[m] = row[m]
1633 saved_metrics[(row["test"], row["platform"])] = d
1634
Andrew Boie08ce5a52016-02-22 13:28:10 -08001635 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001636 i = self.instances[name]
1637 mkey = (i.test.name, i.platform.name)
1638 if mkey not in saved_metrics:
1639 continue
1640 sm = saved_metrics[mkey]
1641 for metric, mtype, lower_better in interesting_metrics:
1642 if metric not in goal.metrics:
1643 continue
1644 if sm[metric] == "":
1645 continue
1646 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07001647 if delta == 0:
1648 continue
1649 results.append((i, metric, goal.metrics[metric], delta,
1650 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07001651 return results
1652
Anas Nashif0605fa32017-05-07 08:51:02 -04001653 def testcase_xunit_report(self, filename, duration, args):
Anas Nashifb3311ed2017-04-13 14:44:48 -04001654 if self.goals == None:
1655 raise SanityRuntimeException("execute() hasn't been run!")
1656
1657 fails = 0
1658 passes = 0
1659 errors = 0
1660
1661 for name, goal in self.goals.items():
1662 if goal.failed:
1663 if goal.reason in ['build_error', 'qemu_crash']:
1664 errors += 1
1665 else:
1666 fails += 1
1667 else:
1668 passes += 1
1669
1670 run = "Sanitycheck"
1671 eleTestsuite = None
1672 append = args.only_failed
1673
Anas Nashif0605fa32017-05-07 08:51:02 -04001674 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04001675 tree = ET.parse(filename)
1676 eleTestsuites = tree.getroot()
1677 eleTestsuite = tree.findall('testsuite')[0];
1678 else:
1679 eleTestsuites = ET.Element('testsuites')
Anas Nashif0605fa32017-05-07 08:51:02 -04001680 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite', name=run, time="%d" %duration,
Anas Nashifb3311ed2017-04-13 14:44:48 -04001681 tests="%d" %(errors + passes + fails), failures="%d" %fails, errors="%d" %errors, skip="0")
1682
1683 qemu_time = "0"
1684 for name, goal in self.goals.items():
1685
1686 i = self.instances[name]
1687 if append:
1688 for tc in eleTestsuite.findall('testcase'):
Anas Nashif202d1302017-05-07 08:19:20 -04001689 if tc.get('classname') == "%s:%s" %(i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04001690 eleTestsuite.remove(tc)
1691
1692 if not goal.failed and goal.qemu:
1693 qemu_time = "%s" %(goal.metrics["qemu_time"])
1694
Anas Nashif202d1302017-05-07 08:19:20 -04001695 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 -04001696 if goal.failed:
1697 failure = ET.SubElement(eleTestcase, 'failure', type="failure", message=goal.reason)
1698 p = ("%s/%s/%s" %(args.outdir, i.platform.name, i.test.name))
1699 bl = os.path.join(p, "build.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04001700 if goal.reason != 'build_error':
1701 bl = os.path.join(p, "qemu.log")
1702
Anas Nashifb3311ed2017-04-13 14:44:48 -04001703 if os.path.exists(bl):
1704 with open(bl, "r") as f:
1705 log = f.read()
Anas Nashife6fcc012017-05-17 09:29:09 -04001706 ansi_escape = re.compile(r'\x1b[^m]*m')
1707 output = ansi_escape.sub('', str(log))
1708 failure.text = (escape(output))
Anas Nashifb3311ed2017-04-13 14:44:48 -04001709
1710 result = ET.tostring(eleTestsuites)
1711 f = open(filename, 'wb')
1712 f.write(result)
1713 f.close()
1714
Andrew Boie6acbe632015-07-17 12:03:52 -07001715 def testcase_report(self, filename):
1716 if self.goals == None:
1717 raise SanityRuntimeException("execute() hasn't been run!")
1718
Andrew Boie08ce5a52016-02-22 13:28:10 -08001719 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07001720 fieldnames = ["test", "arch", "platform", "passed", "status",
1721 "extra_args", "qemu", "qemu_time", "ram_size",
1722 "rom_size"]
1723 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
1724 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08001725 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001726 i = self.instances[name]
1727 rowdict = {"test" : i.test.name,
Anas Nashifa792a3d2017-04-04 18:47:49 -04001728 "arch" : i.platform.arch,
Andrew Boie6acbe632015-07-17 12:03:52 -07001729 "platform" : i.platform.name,
1730 "extra_args" : " ".join(i.test.extra_args),
1731 "qemu" : i.platform.qemu_support}
1732 if goal.failed:
1733 rowdict["passed"] = False
1734 rowdict["status"] = goal.reason
1735 else:
1736 rowdict["passed"] = True
1737 if goal.qemu:
1738 rowdict["qemu_time"] = goal.metrics["qemu_time"]
1739 rowdict["ram_size"] = goal.metrics["ram_size"]
1740 rowdict["rom_size"] = goal.metrics["rom_size"]
1741 cw.writerow(rowdict)
1742
1743
1744def parse_arguments():
1745
1746 parser = argparse.ArgumentParser(description = __doc__,
1747 formatter_class = argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05001748 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07001749
1750 parser.add_argument("-p", "--platform", action="append",
Andrew Boie821d8322016-03-22 10:08:35 -07001751 help="Platform filter for testing. This option may be used multiple "
1752 "times. Testcases will only be built/run on the platforms "
Anas Nashifa792a3d2017-04-04 18:47:49 -04001753 "specified. If this option is not used, then platforms marked "
1754 "as default in the platform metadata file will be chosen "
1755 "to build and test. ")
Andrew Boie821d8322016-03-22 10:08:35 -07001756 parser.add_argument("-L", "--platform-limit", action="store", type=int,
1757 metavar="N", default=1,
1758 help="Controls what platforms are tested if --platform or "
1759 "--all are not used. For each architecture specified by "
1760 "--arch (defaults to all of them), choose the first "
Sebastian Bøed3409c52017-08-07 17:51:42 +02001761 "N platforms to test in the arch-specific .yaml file "
Andrew Boie821d8322016-03-22 10:08:35 -07001762 "'platforms' list. Defaults to 1.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001763 parser.add_argument("-a", "--arch", action="append",
1764 help="Arch filter for testing. Takes precedence over --platform. "
1765 "If unspecified, test all arches. Multiple invocations "
1766 "are treated as a logical 'or' relationship")
1767 parser.add_argument("-t", "--tag", action="append",
1768 help="Specify tags to restrict which tests to run by tag value. "
1769 "Default is to not do any tag filtering. Multiple invocations "
1770 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04001771 parser.add_argument("-e", "--exclude-tag", action="append",
Paul Sokolovskyff70add2017-06-16 01:31:54 +03001772 help="Specify tags of tests that should not run. "
Anas Nashifdfa86e22016-10-24 17:08:56 -04001773 "Default is to run all tests with all tags.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001774 parser.add_argument("-f", "--only-failed", action="store_true",
1775 help="Run only those tests that failed the previous sanity check "
1776 "invocation.")
1777 parser.add_argument("-c", "--config", action="append",
1778 help="Specify platform configuration values filtering. This can be "
1779 "specified two ways: <config>=<value> or just <config>. The "
Andrew Boie41878222016-11-03 11:58:53 -07001780 "defconfig for all platforms will be "
Andrew Boie6acbe632015-07-17 12:03:52 -07001781 "checked. For the <config>=<value> case, only match defconfig "
1782 "that have that value defined. For the <config> case, match "
1783 "defconfig that have that value assigned to any value. "
1784 "Prepend a '!' to invert the match.")
1785 parser.add_argument("-s", "--test", action="append",
1786 help="Run only the specified test cases. These are named by "
1787 "<path to test project relative to "
Sebastian Bøed3409c52017-08-07 17:51:42 +02001788 "--testcase-root>/<testcase.yaml section name>")
Andrew Boie6acbe632015-07-17 12:03:52 -07001789 parser.add_argument("-l", "--all", action="store_true",
Andrew Boie821d8322016-03-22 10:08:35 -07001790 help="Build/test on all platforms. Any --platform arguments "
1791 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001792
1793 parser.add_argument("-o", "--testcase-report",
1794 help="Output a CSV spreadsheet containing results of the test run")
1795 parser.add_argument("-d", "--discard-report",
David B. Kinder29963c32017-06-16 12:32:42 -07001796 help="Output a CSV spreadsheet showing tests that were skipped "
Andrew Boie6acbe632015-07-17 12:03:52 -07001797 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07001798 parser.add_argument("--compare-report",
David B. Kinder29963c32017-06-16 12:32:42 -07001799 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07001800
Kumar Galad5719a62016-10-26 12:30:26 -05001801 parser.add_argument("--ccache", action="store_const", const=1, default=0,
1802 help="Enable the use of ccache when building")
1803
Anas Nashif035799f2017-05-13 21:31:53 -04001804 parser.add_argument("-B", "--subset",
1805 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
1806 "3/5 means run the 3rd fifth of the total. "
1807 "This option is useful when running a large number of tests on "
1808 "different hosts to speed up execution time.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001809 parser.add_argument("-y", "--dry-run", action="store_true",
1810 help="Create the filtered list of test cases, but don't actually "
1811 "run them. Useful if you're just interested in "
1812 "--discard-report")
1813
1814 parser.add_argument("-r", "--release", action="store_true",
1815 help="Update the benchmark database with the results of this test "
1816 "run. Intended to be run by CI when tagging an official "
1817 "release. This database is used as a basis for comparison "
1818 "when looking for deltas in metrics such as footprint")
1819 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
1820 help="Treat warning conditions as errors")
1821 parser.add_argument("-v", "--verbose", action="count", default=0,
1822 help="Emit debugging information, call multiple times to increase "
1823 "verbosity")
1824 parser.add_argument("-i", "--inline-logs", action="store_true",
1825 help="Upon test failure, print relevant log data to stdout "
1826 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001827 parser.add_argument("--log-file", metavar="FILENAME", action="store",
1828 help="log also to file")
Andrew Boie6acbe632015-07-17 12:03:52 -07001829 parser.add_argument("-m", "--last-metrics", action="store_true",
1830 help="Instead of comparing metrics from the last --release, "
1831 "compare with the results of the previous sanity check "
1832 "invocation")
1833 parser.add_argument("-u", "--no-update", action="store_true",
1834 help="do not update the results of the last run of the sanity "
1835 "checks")
1836 parser.add_argument("-b", "--build-only", action="store_true",
1837 help="Only build the code, do not execute any of it in QEMU")
1838 parser.add_argument("-j", "--jobs", type=int,
1839 help="Number of cores to use when building, defaults to "
1840 "number of CPUs * 2")
1841 parser.add_argument("-H", "--footprint-threshold", type=float, default=5,
1842 help="When checking test case footprint sizes, warn the user if "
1843 "the new app size is greater then the specified percentage "
1844 "from the last release. Default is 5. 0 to warn on any "
1845 "increase on app size")
Andrew Boieea7928f2015-08-14 14:27:38 -07001846 parser.add_argument("-D", "--all-deltas", action="store_true",
1847 help="Show all footprint deltas, positive or negative. Implies "
1848 "--footprint-threshold=0")
Andrew Boie6acbe632015-07-17 12:03:52 -07001849 parser.add_argument("-O", "--outdir",
1850 default="%s/sanity-out" % ZEPHYR_BASE,
1851 help="Output directory for logs and binaries.")
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07001852 parser.add_argument("-n", "--no-clean", action="store_true",
1853 help="Do not delete the outdir before building. Will result in "
1854 "faster compilation since builds will be incremental")
Andrew Boie3d348712016-04-08 11:52:13 -07001855 parser.add_argument("-T", "--testcase-root", action="append", default=[],
Andrew Boie6acbe632015-07-17 12:03:52 -07001856 help="Base directory to recursively search for test cases. All "
Sebastian Bøed3409c52017-08-07 17:51:42 +02001857 "testcase.yaml files under here will be processed. May be "
Andrew Boie3d348712016-04-08 11:52:13 -07001858 "called multiple times. Defaults to the 'samples' and "
1859 "'tests' directories in the Zephyr tree.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001860 parser.add_argument("-A", "--arch-root",
Anas Nashifa792a3d2017-04-04 18:47:49 -04001861 default="%s/boards" % ZEPHYR_BASE,
Sebastian Bøed3409c52017-08-07 17:51:42 +02001862 help="Directory to search for arch configuration files. All .yaml "
Andrew Boie6acbe632015-07-17 12:03:52 -07001863 "files in the directory will be processed.")
Andrew Boiebbd670c2015-08-17 13:16:11 -07001864 parser.add_argument("-z", "--size", action="append",
1865 help="Don't run sanity checks. Instead, produce a report to "
1866 "stdout detailing RAM/ROM sizes on the specified filenames. "
1867 "All other command line arguments ignored.")
Andrew Boie6bb087c2016-02-10 13:39:00 -08001868 parser.add_argument("-S", "--enable-slow", action="store_true",
1869 help="Execute time-consuming test cases that have been marked "
Sebastian Bøed3409c52017-08-07 17:51:42 +02001870 "as 'slow' in testcase.yaml. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07001871 parser.add_argument("-R", "--enable-asserts", action="store_true",
1872 help="Build all test cases with assertions enabled.")
Anas Nashife3febe92016-11-30 14:25:44 -05001873 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
1874 help="Error on deprecation warnings.")
Andrew Boieba612002016-09-01 10:41:03 -07001875 parser.add_argument("-x", "--extra-args", action="append", default=[],
1876 help="Extra arguments to pass to the build when compiling test "
1877 "cases. May be called multiple times. These will be passed "
1878 "in after any sanitycheck-supplied options.")
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001879 parser.add_argument("-C", "--coverage", action="store_true",
1880 help="Scan for unit test coverage with gcov + lcov.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001881
1882 return parser.parse_args()
1883
1884def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01001885 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001886 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001887 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08001888
1889 try:
1890 with open(filename) as fp:
1891 data = fp.read()
1892 except Exception as e:
1893 data = "Unable to read log data (%s)\n" % (str(e))
1894
1895 sys.stdout.write(data)
1896 if log_file:
1897 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08001898 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07001899 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001900 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07001901
1902def terse_test_cb(instances, goals, goal):
1903 total_tests = len(goals)
1904 total_done = 0
1905 total_failed = 0
1906
Andrew Boie08ce5a52016-02-22 13:28:10 -08001907 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001908 if g.finished:
1909 total_done += 1
1910 if g.failed:
1911 total_failed += 1
1912
1913 if goal.failed:
1914 i = instances[goal.name]
1915 info("\n\n{:<25} {:<50} {}FAILED{}: {}".format(i.platform.name,
1916 i.test.name, COLOR_RED, COLOR_NORMAL, goal.reason))
1917 log_info(goal.get_error_log())
1918 info("")
1919
Andrew Boie7a992ae2017-01-17 10:40:02 -08001920 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" % (
Andrew Boie6acbe632015-07-17 12:03:52 -07001921 COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
Andrew Boie7a992ae2017-01-17 10:40:02 -08001922 int((float(total_done) / total_tests) * 100),
Andrew Boie6acbe632015-07-17 12:03:52 -07001923 COLOR_RED if total_failed > 0 else COLOR_NORMAL,
1924 total_failed, COLOR_NORMAL))
1925 sys.stdout.flush()
1926
1927def chatty_test_cb(instances, goals, goal):
1928 i = instances[goal.name]
1929
1930 if VERBOSE < 2 and not goal.finished:
1931 return
1932
1933 if goal.failed:
1934 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
1935 elif goal.finished:
1936 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
1937 else:
1938 status = goal.make_state
1939
1940 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
1941 if goal.failed:
1942 log_info(goal.get_error_log())
1943
Andrew Boiebbd670c2015-08-17 13:16:11 -07001944
1945def size_report(sc):
1946 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07001947 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07001948 for i in range(len(sc.sections)):
1949 v = sc.sections[i]
1950
Andrew Boie73b4ee62015-10-07 11:33:22 -07001951 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
1952 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
1953 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07001954
Andrew Boie73b4ee62015-10-07 11:33:22 -07001955 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
1956 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001957 info("")
1958
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03001959def generate_coverage(outdir, ignores):
1960 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
1961 coveragefile = os.path.join(outdir, "coverage.info")
1962 ztestfile = os.path.join(outdir, "ztest.info")
1963 subprocess.call(["lcov", "--capture", "--directory", outdir,
1964 "--output-file", coveragefile], stdout=coveragelog)
1965 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
1966 subprocess.call(["lcov", "--extract", coveragefile,
1967 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
1968 "--output-file", ztestfile], stdout=coveragelog)
1969 subprocess.call(["lcov", "--remove", ztestfile,
1970 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
1971 "--output-file", ztestfile], stdout=coveragelog)
1972 for i in ignores:
1973 subprocess.call(["lcov", "--remove", coveragefile, i,
1974 "--output-file", coveragefile], stdout=coveragelog)
1975 subprocess.call(["genhtml", "-output-directory",
1976 os.path.join(outdir, "coverage"),
1977 coveragefile, ztestfile], stdout=coveragelog)
Andrew Boiebbd670c2015-08-17 13:16:11 -07001978
Andrew Boie6acbe632015-07-17 12:03:52 -07001979def main():
Andrew Boie4b182472015-07-31 12:25:22 -07001980 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001981 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Andrew Boie6acbe632015-07-17 12:03:52 -07001982 args = parse_arguments()
Javier B Perez4b554ba2016-08-15 13:25:33 -05001983 toolchain = os.environ.get("ZEPHYR_GCC_VARIANT", None)
Andrew Boie1e4e68b2017-02-10 11:40:54 -08001984 if toolchain == "zephyr":
1985 os.environ["DISABLE_TRYRUN"] = "1"
Andrew Boiebbd670c2015-08-17 13:16:11 -07001986
1987 if args.size:
1988 for fn in args.size:
Andrew Boie52fef672016-11-29 12:21:59 -08001989 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07001990 sys.exit(0)
1991
Andrew Boie6acbe632015-07-17 12:03:52 -07001992 VERBOSE += args.verbose
1993 INLINE_LOGS = args.inline_logs
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001994 if args.log_file:
1995 log_file = open(args.log_file, "w")
Andrew Boie6acbe632015-07-17 12:03:52 -07001996 if args.jobs:
Daniel Leung6b170072016-04-07 12:10:25 -07001997 CPU_COUNTS = args.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07001998
Anas Nashif035799f2017-05-13 21:31:53 -04001999 if args.subset:
2000 subset, sets = args.subset.split("/")
2001 if int(subset) > 0 and int(sets) >= int(subset):
2002 info("Running only a subset: %s/%s" %(subset,sets))
2003 else:
2004 error("You have provided a wrong subset value: %s." %args.subset)
2005 return
2006
Andrew Boieae9e7f7b2015-07-31 12:26:12 -07002007 if os.path.exists(args.outdir) and not args.no_clean:
Andrew Boie6acbe632015-07-17 12:03:52 -07002008 info("Cleaning output directory " + args.outdir)
2009 shutil.rmtree(args.outdir)
2010
Andrew Boie3d348712016-04-08 11:52:13 -07002011 if not args.testcase_root:
2012 args.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
2013 os.path.join(ZEPHYR_BASE, "samples")]
2014
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002015 ts = TestSuite(args.arch_root, args.testcase_root, args.outdir, args.coverage)
Anas Nashifdfa86e22016-10-24 17:08:56 -04002016 discards = ts.apply_filters(args.platform, args.arch, args.tag, args.exclude_tag, args.config,
Andrew Boie821d8322016-03-22 10:08:35 -07002017 args.test, args.only_failed, args.all,
Kumar Galad5719a62016-10-26 12:30:26 -05002018 args.platform_limit, toolchain, args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07002019
2020 if args.discard_report:
2021 ts.discard_report(args.discard_report)
2022
2023 if VERBOSE:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002024 for i, reason in discards.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002025 debug("{:<25} {:<50} {}SKIPPED{}: {}".format(i.platform.name,
2026 i.test.name, COLOR_YELLOW, COLOR_NORMAL, reason))
2027
Anas Nashif035799f2017-05-13 21:31:53 -04002028 ts.instancets = OrderedDict(sorted(ts.instances.items(), key=lambda t: t[0]))
2029
2030 if args.subset:
2031 subset, sets = args.subset.split("/")
2032 total = len(ts.instancets)
2033 per_set = round(total / int(sets))
2034 start = (int(subset) - 1 ) * per_set
2035 if subset == sets:
2036 end = total
2037 else:
2038 end = start + per_set
2039
2040 sliced_instances = islice(ts.instancets.items(),start, end)
2041 ts.instances = OrderedDict(sliced_instances)
2042
Andrew Boie6acbe632015-07-17 12:03:52 -07002043 info("%d tests selected, %d tests discarded due to filters" %
2044 (len(ts.instances), len(discards)))
2045
2046 if args.dry_run:
2047 return
2048
2049 if VERBOSE or not TERMINAL:
Andrew Boie6bb087c2016-02-10 13:39:00 -08002050 goals = ts.execute(chatty_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05002051 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05002052 args.extra_args, args.ccache)
Andrew Boie6acbe632015-07-17 12:03:52 -07002053 else:
Andrew Boie6bb087c2016-02-10 13:39:00 -08002054 goals = ts.execute(terse_test_cb, ts.instances, args.build_only,
Anas Nashife3febe92016-11-30 14:25:44 -05002055 args.enable_slow, args.enable_asserts, args.error_on_deprecations,
Kumar Galad5719a62016-10-26 12:30:26 -05002056 args.extra_args, args.ccache)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002057 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07002058
Daniel Leung7f850102016-04-08 11:07:32 -07002059 # figure out which report to use for size comparison
2060 if args.compare_report:
2061 report_to_use = args.compare_report
2062 elif args.last_metrics:
2063 report_to_use = LAST_SANITY
2064 else:
2065 report_to_use = RELEASE_DATA
2066
2067 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07002068 warnings = 0
2069 if deltas:
Andrew Boieea7928f2015-08-14 14:27:38 -07002070 for i, metric, value, delta, lower_better in deltas:
2071 if not args.all_deltas and ((delta < 0 and lower_better) or
2072 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07002073 continue
2074
Andrew Boieea7928f2015-08-14 14:27:38 -07002075 percentage = (float(delta) / float(value - delta))
2076 if not args.all_deltas and (percentage <
2077 (args.footprint_threshold / 100.0)):
2078 continue
2079
Daniel Leung00525c22016-04-11 10:27:56 -07002080 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07002081 i.platform.name, i.test.name, COLOR_YELLOW,
2082 "INFO" if args.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07002083 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07002084 warnings += 1
2085
2086 if warnings:
2087 info("Deltas based on metrics from last %s" %
2088 ("release" if not args.last_metrics else "run"))
2089
2090 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08002091 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002092 if goal.failed:
2093 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002094 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07002095 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2096 (COLOR_RED, COLOR_NORMAL, goal.name,
2097 str(goal.metrics["unrecognized"])))
2098 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002099
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002100 if args.coverage:
2101 info("Generating coverage files...")
2102 generate_coverage(args.outdir, ["tests/*", "samples/*"])
2103
Anas Nashif0605fa32017-05-07 08:51:02 -04002104 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07002105 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Andrew Boie6acbe632015-07-17 12:03:52 -07002106 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
2107 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
Anas Nashif0605fa32017-05-07 08:51:02 -04002108 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07002109
2110 if args.testcase_report:
2111 ts.testcase_report(args.testcase_report)
2112 if not args.no_update:
Anas Nashif0605fa32017-05-07 08:51:02 -04002113 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration, args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002114 ts.testcase_report(LAST_SANITY)
2115 if args.release:
2116 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002117 if log_file:
2118 log_file.close()
Andrew Boie6acbe632015-07-17 12:03:52 -07002119 if failed or (warnings and args.warnings_as_errors):
2120 sys.exit(1)
2121
2122if __name__ == "__main__":
2123 main()