blob: 73fbe75650731c5208f05b3dbe0be2bca6c69d08 [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 :
Anas Nashif3ae52622019-04-06 09:08:09 -04003# SPDX-License-Identifier: Apache-2.0
Andrew Boie6acbe632015-07-17 12:03:52 -07004"""Zephyr Sanity Tests
5
6This script scans for the set of unit test applications in the git
7repository and attempts to execute them. By default, it tries to
8build each test case on one platform per architecture, using a precedence
Paul Sokolovskyff70add2017-06-16 01:31:54 +03009list defined in an architecture configuration file, and if possible
Andrew Boie6acbe632015-07-17 12:03:52 -070010run the tests in the QEMU emulator.
11
Anas Nashifa792a3d2017-04-04 18:47:49 -040012Test cases are detected by the presence of a 'testcase.yaml' or a sample.yaml
13files in the application's project directory. This file may contain one or more
14blocks, each identifying a test scenario. The title of the block is a name for
15the test case, which only needs to be unique for the test cases specified in
16that testcase meta-data. The full canonical name for each test case is <path to
17test case>/<block>.
Andrew Boie6acbe632015-07-17 12:03:52 -070018
Anas Nashif3ba1d432017-12-05 15:28:44 -050019Each test block in the testcase meta data can define the following key/value
20pairs:
Andrew Boie6acbe632015-07-17 12:03:52 -070021
Anas Nashiffa695d22017-10-04 16:14:27 -040022 tags: <list of tags> (required)
Andrew Boie6acbe632015-07-17 12:03:52 -070023 A set of string tags for the testcase. Usually pertains to
24 functional domains but can be anything. Command line invocations
25 of this script can filter the set of tests to run based on tag.
26
Anas Nashifa792a3d2017-04-04 18:47:49 -040027 skip: <True|False> (default False)
Anas Nashif2bd99bc2015-10-12 13:10:57 -040028 skip testcase unconditionally. This can be used for broken tests.
29
Anas Nashifa792a3d2017-04-04 18:47:49 -040030 slow: <True|False> (default False)
Andy Rossdc4151f2019-01-03 14:17:43 -080031 Don't build or run this test case unless --enable-slow was passed
32 in on the command line. Intended for time-consuming test cases
33 that are only run under certain circumstances, like daily
34 builds.
Andrew Boie6bb087c2016-02-10 13:39:00 -080035
Anas Nashifa792a3d2017-04-04 18:47:49 -040036 extra_args: <list of extra arguments>
Sebastian Bøec2182612017-11-09 12:25:02 +010037 Extra cache entries to pass to CMake when building or running the
Andrew Boie6acbe632015-07-17 12:03:52 -070038 test case.
39
Anas Nashifebc329d2017-10-17 09:00:33 -040040 extra_configs: <list of extra configurations>
41 Extra configuration options to be merged with a master prj.conf
42 when building or running the test case.
43
Anas Nashifa792a3d2017-04-04 18:47:49 -040044 build_only: <True|False> (default False)
Andrew Boie6acbe632015-07-17 12:03:52 -070045 If true, don't try to run the test under QEMU even if the
46 selected platform supports it.
47
Anas Nashifa792a3d2017-04-04 18:47:49 -040048 build_on_all: <True|False> (default False)
49 If true, attempt to build test on all available platforms.
50
51 depends_on: <list of features>
52 A board or platform can announce what features it supports, this option
53 will enable the test only those platforms that provide this feature.
54
55 min_ram: <integer>
56 minimum amount of RAM needed for this test to build and run. This is
57 compared with information provided by the board metadata.
58
59 min_flash: <integer>
60 minimum amount of ROM needed for this test to build and run. This is
61 compared with information provided by the board metadata.
62
63 timeout: <number of seconds>
Andrew Boie6acbe632015-07-17 12:03:52 -070064 Length of time to run test in QEMU before automatically killing it.
65 Default to 60 seconds.
66
Anas Nashifa792a3d2017-04-04 18:47:49 -040067 arch_whitelist: <list of arches, such as x86, arm, arc>
Andrew Boie6acbe632015-07-17 12:03:52 -070068 Set of architectures that this test case should only be run for.
69
Anas Nashifa792a3d2017-04-04 18:47:49 -040070 arch_exclude: <list of arches, such as x86, arm, arc>
Anas Nashif30d13872015-10-05 10:02:45 -040071 Set of architectures that this test case should not run on.
72
Anas Nashifa792a3d2017-04-04 18:47:49 -040073 platform_whitelist: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040074 Set of platforms that this test case should only be run for.
75
Anas Nashifa792a3d2017-04-04 18:47:49 -040076 platform_exclude: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040077 Set of platforms that this test case should not run on.
Andrew Boie6acbe632015-07-17 12:03:52 -070078
Anas Nashifa792a3d2017-04-04 18:47:49 -040079 extra_sections: <list of extra binary sections>
Andrew Boie52fef672016-11-29 12:21:59 -080080 When computing sizes, sanitycheck will report errors if it finds
81 extra, unexpected sections in the Zephyr binary unless they are named
82 here. They will not be included in the size calculation.
83
Anas Nashifa792a3d2017-04-04 18:47:49 -040084 filter: <expression>
Andrew Boie3ea78922016-03-24 14:46:00 -070085 Filter whether the testcase should be run by evaluating an expression
86 against an environment containing the following values:
87
88 { ARCH : <architecture>,
89 PLATFORM : <platform>,
Javier B Perez79414542016-08-08 12:24:59 -050090 <all CONFIG_* key/value pairs in the test's generated defconfig>,
Anas Nashif45a97862019-01-09 08:46:42 -050091 <all DT_* key/value pairs in the test's generated device tree file>,
92 <all CMake key/value pairs in the test's generated CMakeCache.txt file>,
Javier B Perez79414542016-08-08 12:24:59 -050093 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070094 }
95
96 The grammar for the expression language is as follows:
97
98 expression ::= expression "and" expression
99 | expression "or" expression
100 | "not" expression
101 | "(" expression ")"
102 | symbol "==" constant
103 | symbol "!=" constant
104 | symbol "<" number
105 | symbol ">" number
106 | symbol ">=" number
107 | symbol "<=" number
108 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700109 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -0700110 | symbol
111
112 list ::= "[" list_contents "]"
113
114 list_contents ::= constant
115 | list_contents "," constant
116
117 constant ::= number
118 | string
119
120
121 For the case where expression ::= symbol, it evaluates to true
122 if the symbol is defined to a non-empty string.
123
124 Operator precedence, starting from lowest to highest:
125
126 or (left associative)
127 and (left associative)
128 not (right associative)
129 all comparison operators (non-associative)
130
131 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
132 are all syntactic sugar for these expressions. For instance
133
134 arch_exclude = x86 arc
135
136 Is the same as:
137
138 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700139
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700140 The ':' operator compiles the string argument as a regular expression,
141 and then returns a true value only if the symbol's value in the environment
142 matches. For example, if CONFIG_SOC="quark_se" then
143
144 filter = CONFIG_SOC : "quark.*"
145
146 Would match it.
147
Anas Nashifa792a3d2017-04-04 18:47:49 -0400148The set of test cases that actually run depends on directives in the testcase
149filed and options passed in on the command line. If there is any confusion,
150running with -v or --discard-report can help show why particular test cases
151were skipped.
Andrew Boie6acbe632015-07-17 12:03:52 -0700152
153Metrics (such as pass/fail state and binary size) for the last code
154release are stored in scripts/sanity_chk/sanity_last_release.csv.
155To update this, pass the --all --release options.
156
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500157To load arguments from a file, write '+' before the file name, e.g.,
158+file_name. File content must be one or more valid arguments separated by
159line break instead of white spaces.
160
Andrew Boie6acbe632015-07-17 12:03:52 -0700161Most everyday users will run with no arguments.
Andy Rossdc4151f2019-01-03 14:17:43 -0800162
Andrew Boie6acbe632015-07-17 12:03:52 -0700163"""
164
Sebastian Bøe56d74712019-01-21 15:48:46 +0100165import os
Sebastian Bøe56d74712019-01-21 15:48:46 +0100166if os.name == 'nt':
167 print("Running sanitycheck on Windows is not supported yet.")
168 print("https://github.com/zephyrproject-rtos/zephyr/issues/2664")
169 exit(1)
170
Anas Nashifaae71d72018-04-21 22:26:48 -0500171import contextlib
Anas Nashifa4c368e2018-10-15 09:45:59 -0400172import string
Anas Nashifaae71d72018-04-21 22:26:48 -0500173import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700174import argparse
Andrew Boie6acbe632015-07-17 12:03:52 -0700175import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700176import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700177import subprocess
178import multiprocessing
179import select
180import shutil
Marc Herbertaf1090c2019-04-30 14:11:29 -0700181import shlex
Andrew Boie6acbe632015-07-17 12:03:52 -0700182import signal
183import threading
184import time
Anas Nashif654ec5982019-04-11 08:38:21 -0400185import datetime
Andrew Boie6acbe632015-07-17 12:03:52 -0700186import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700187import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600188import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700189import concurrent
190import concurrent.futures
Anas Nashifb3311ed2017-04-13 14:44:48 -0400191import xml.etree.ElementTree as ET
Anas Nashife6fcc012017-05-17 09:29:09 -0400192from xml.sax.saxutils import escape
Anas Nashif035799f2017-05-13 21:31:53 -0400193from collections import OrderedDict
194from itertools import islice
Anas Nashif1a5bba72018-01-05 08:07:45 -0500195from functools import cmp_to_key
Anas Nashife24350c2018-07-11 15:09:22 -0500196from pathlib import Path
197from distutils.spawn import find_executable
Andrew Boie6acbe632015-07-17 12:03:52 -0700198
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700199import logging
Anas Nashif3ba1d432017-12-05 15:28:44 -0500200from sanity_chk import scl
201from sanity_chk import expr_parser
202
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700203log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500204logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700205
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +0300206ZEPHYR_BASE = os.environ.get("ZEPHYR_BASE")
207if not ZEPHYR_BASE:
Anas Nashif427cdd32015-08-06 07:25:42 -0400208 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700209 exit(1)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700210
Marc Herbert1c8632c2019-04-15 17:58:45 -0700211# Use this for internal comparisons; that's what canonicalization is
212# for. Don't use it when invoking other components of the build system
213# to avoid confusing and hard to trace inconsistencies in error messages
214# and logs, generated Makefiles, etc. compared to when users invoke these
215# components directly.
216# Note "normalization" is different from canonicalization, see os.path.
217canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
218
Andrew Boie3ea78922016-03-24 14:46:00 -0700219sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
220
Andrew Boie3ea78922016-03-24 14:46:00 -0700221
Andrew Boie6acbe632015-07-17 12:03:52 -0700222VERBOSE = 0
223LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
224 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400225LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
Anas Nashif3ba1d432017-12-05 15:28:44 -0500226 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700227RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
228 "sanity_last_release.csv")
Oleg Zhurakivskyyf3bc9672018-08-17 18:31:38 +0300229JOBS = multiprocessing.cpu_count() * 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700230
231if os.isatty(sys.stdout.fileno()):
232 TERMINAL = True
233 COLOR_NORMAL = '\033[0m'
234 COLOR_RED = '\033[91m'
235 COLOR_GREEN = '\033[92m'
236 COLOR_YELLOW = '\033[93m'
237else:
238 TERMINAL = False
239 COLOR_NORMAL = ""
240 COLOR_RED = ""
241 COLOR_GREEN = ""
242 COLOR_YELLOW = ""
243
Anas Nashif45a97862019-01-09 08:46:42 -0500244class CMakeCacheEntry:
245 '''Represents a CMake cache entry.
246
247 This class understands the type system in a CMakeCache.txt, and
248 converts the following cache types to Python types:
249
250 Cache Type Python type
251 ---------- -------------------------------------------
252 FILEPATH str
253 PATH str
254 STRING str OR list of str (if ';' is in the value)
255 BOOL bool
256 INTERNAL str OR list of str (if ';' is in the value)
257 ---------- -------------------------------------------
258 '''
259
260 # Regular expression for a cache entry.
261 #
262 # CMake variable names can include escape characters, allowing a
263 # wider set of names than is easy to match with a regular
264 # expression. To be permissive here, use a non-greedy match up to
265 # the first colon (':'). This breaks if the variable name has a
266 # colon inside, but it's good enough.
267 CACHE_ENTRY = re.compile(
268 r'''(?P<name>.*?) # name
269 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
270 =(?P<value>.*) # value
271 ''', re.X)
272
273 @classmethod
274 def _to_bool(cls, val):
275 # Convert a CMake BOOL string into a Python bool.
276 #
277 # "True if the constant is 1, ON, YES, TRUE, Y, or a
278 # non-zero number. False if the constant is 0, OFF, NO,
279 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
280 # the suffix -NOTFOUND. Named boolean constants are
281 # case-insensitive. If the argument is not one of these
282 # constants, it is treated as a variable."
283 #
284 # https://cmake.org/cmake/help/v3.0/command/if.html
285 val = val.upper()
286 if val in ('ON', 'YES', 'TRUE', 'Y'):
287 return 1
288 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
289 return 0
290 elif val.endswith('-NOTFOUND'):
291 return 0
292 else:
293 try:
294 v = int(val)
295 return v != 0
296 except ValueError as exc:
297 raise ValueError('invalid bool {}'.format(val)) from exc
298
299 @classmethod
300 def from_line(cls, line, line_no):
301 # Comments can only occur at the beginning of a line.
302 # (The value of an entry could contain a comment character).
303 if line.startswith('//') or line.startswith('#'):
304 return None
305
306 # Whitespace-only lines do not contain cache entries.
307 if not line.strip():
308 return None
309
310 m = cls.CACHE_ENTRY.match(line)
311 if not m:
312 return None
313
314 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
315 if type_ == 'BOOL':
316 try:
317 value = cls._to_bool(value)
318 except ValueError as exc:
319 args = exc.args + ('on line {}: {}'.format(line_no, line),)
320 raise ValueError(args) from exc
321 elif type_ == 'STRING' or type_ == 'INTERNAL':
322 # If the value is a CMake list (i.e. is a string which
323 # contains a ';'), convert to a Python list.
324 if ';' in value:
325 value = value.split(';')
326
327 return CMakeCacheEntry(name, value)
328
329 def __init__(self, name, value):
330 self.name = name
331 self.value = value
332
333 def __str__(self):
334 fmt = 'CMakeCacheEntry(name={}, value={})'
335 return fmt.format(self.name, self.value)
336
337
338class CMakeCache:
339 '''Parses and represents a CMake cache file.'''
340
341 @staticmethod
342 def from_file(cache_file):
343 return CMakeCache(cache_file)
344
345 def __init__(self, cache_file):
346 self.cache_file = cache_file
347 self.load(cache_file)
348
349 def load(self, cache_file):
350 entries = []
351 with open(cache_file, 'r') as cache:
352 for line_no, line in enumerate(cache):
353 entry = CMakeCacheEntry.from_line(line, line_no)
354 if entry:
355 entries.append(entry)
356 self._entries = OrderedDict((e.name, e) for e in entries)
357
358 def get(self, name, default=None):
359 entry = self._entries.get(name)
360 if entry is not None:
361 return entry.value
362 else:
363 return default
364
365 def get_list(self, name, default=None):
366 if default is None:
367 default = []
368 entry = self._entries.get(name)
369 if entry is not None:
370 value = entry.value
371 if isinstance(value, list):
372 return value
373 elif isinstance(value, str):
374 return [value] if value else []
375 else:
376 msg = 'invalid value {} type {}'
377 raise RuntimeError(msg.format(value, type(value)))
378 else:
379 return default
380
381 def __contains__(self, name):
382 return name in self._entries
383
384 def __getitem__(self, name):
385 return self._entries[name].value
386
387 def __setitem__(self, name, entry):
388 if not isinstance(entry, CMakeCacheEntry):
389 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
390 raise TypeError(msg.format(type(entry), entry))
391 self._entries[name] = entry
392
393 def __delitem__(self, name):
394 del self._entries[name]
395
396 def __iter__(self):
397 return iter(self._entries.values())
398
Andrew Boie6acbe632015-07-17 12:03:52 -0700399class SanityCheckException(Exception):
400 pass
401
Anas Nashif3ba1d432017-12-05 15:28:44 -0500402
Andrew Boie6acbe632015-07-17 12:03:52 -0700403class SanityRuntimeError(SanityCheckException):
404 pass
405
Anas Nashif3ba1d432017-12-05 15:28:44 -0500406
Andrew Boie6acbe632015-07-17 12:03:52 -0700407class ConfigurationError(SanityCheckException):
408 def __init__(self, cfile, message):
409 self.cfile = cfile
410 self.message = message
411
412 def __str__(self):
413 return repr(self.cfile + ": " + self.message)
414
Anas Nashif3ba1d432017-12-05 15:28:44 -0500415
Andrew Boie6acbe632015-07-17 12:03:52 -0700416class MakeError(SanityCheckException):
417 pass
418
Anas Nashif3ba1d432017-12-05 15:28:44 -0500419
Andrew Boie6acbe632015-07-17 12:03:52 -0700420class BuildError(MakeError):
421 pass
422
Anas Nashif3ba1d432017-12-05 15:28:44 -0500423
Andrew Boie6acbe632015-07-17 12:03:52 -0700424class ExecutionError(MakeError):
425 pass
426
Anas Nashif3ba1d432017-12-05 15:28:44 -0500427
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800428log_file = None
429
Andrew Boie6acbe632015-07-17 12:03:52 -0700430# Debug Functions
Anas Nashif654ec5982019-04-11 08:38:21 -0400431def info(what, show_time=True):
432 if options.timestamps and show_time:
433 date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
434 what = "{}: {}".format(date, what)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800435 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300436 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800437 if log_file:
438 log_file.write(what + "\n")
439 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700440
Anas Nashif3ba1d432017-12-05 15:28:44 -0500441
Andrew Boie6acbe632015-07-17 12:03:52 -0700442def error(what):
Anas Nashif654ec5982019-04-11 08:38:21 -0400443 if options.timestamps:
444 date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
445 what = "{}: {}".format(date, what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700446 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800447 if log_file:
448 log_file(what + "\n")
449 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700450
Anas Nashif3ba1d432017-12-05 15:28:44 -0500451
Andrew Boie08ce5a52016-02-22 13:28:10 -0800452def debug(what):
453 if VERBOSE >= 1:
454 info(what)
455
Anas Nashif3ba1d432017-12-05 15:28:44 -0500456
Andrew Boie6acbe632015-07-17 12:03:52 -0700457def verbose(what):
458 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800459 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700460
Anas Nashif576be982017-12-23 20:20:27 -0500461class HarnessImporter:
462
463 def __init__(self, name):
464 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
465 module = __import__("harness")
466 if name:
467 my_class = getattr(module, name)
468 else:
469 my_class = getattr(module, "Test")
470
471 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500472
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300473class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400474 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300475 """Constructor
476
477 @param name Arbitrary name of the created thread
Anas Nashif66bdb322017-11-25 17:20:07 -0500478 @param outdir Working directory, should be where handler pid file (qemu.pid for example)
479 gets created by the build system
480 @param log_fn Absolute path to write out handler's log data
481 @param timeout Kill the handler process if it doesn't finish up within
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300482 the given number of seconds
483 """
484 self.lock = threading.Lock()
485 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500486 self.run = False
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300487 self.metrics = {}
Anas Nashifc8390f12017-11-25 17:14:12 -0500488 self.metrics["handler_time"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300489 self.metrics["ram_size"] = 0
490 self.metrics["rom_size"] = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400491 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300492
Anas Nashifdf7ee612018-07-07 06:09:01 -0500493 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100494 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500495 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500496
Anas Nashifd3384fb2018-02-22 06:44:16 -0600497 self.name = instance.name
498 self.instance = instance
499 self.timeout = instance.test.timeout
Anas Nashiff18ad9d2018-11-20 09:03:17 -0500500 self.sourcedir = instance.test.test_path
Anas Nashifd3384fb2018-02-22 06:44:16 -0600501 self.outdir = instance.outdir
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500502 self.log = os.path.join(self.outdir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600503 self.returncode = 0
504 self.set_state("running", {})
505
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300506 def set_state(self, state, metrics):
507 self.lock.acquire()
508 self.state = state
509 self.metrics.update(metrics)
510 self.lock.release()
511
512 def get_state(self):
513 self.lock.acquire()
514 ret = (self.state, self.metrics)
515 self.lock.release()
516 return ret
517
Anas Nashifdf7ee612018-07-07 06:09:01 -0500518class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400519 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500520 """Constructor
521
522 @param instance Test Instance
523 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400524 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500525
526 self.valgrind = False
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100527 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500528
Jan Kowalewski265895b2019-01-07 16:40:24 +0100529 def try_kill_process_by_pid(self):
530 if self.pid_fn != None:
531 pid = int(open(self.pid_fn).read())
532 os.unlink(self.pid_fn)
533 self.pid_fn = None # clear so we don't try to kill the binary twice
534 try:
535 os.kill(pid, signal.SIGTERM)
536 except ProcessLookupError:
537 pass
538
Anas Nashifdf7ee612018-07-07 06:09:01 -0500539 def _output_reader(self, proc, harness):
540 log_out_fp = open(self.log, "wt")
541 for line in iter(proc.stdout.readline, b''):
542 verbose("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
543 log_out_fp.write(line.decode('utf-8'))
544 log_out_fp.flush()
545 harness.handle(line.decode('utf-8').rstrip())
546 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100547 try:
548 #POSIX arch based ztests end on their own,
549 #so let's give it up to 100ms to do so
550 proc.wait(0.1)
551 except subprocess.TimeoutExpired:
552 proc.terminate()
553 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500554 break
555
556 log_out_fp.close()
557
558 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500559
560 harness_name = self.instance.test.harness.capitalize()
561 harness_import = HarnessImporter(harness_name)
562 harness = harness_import.instance
563 harness.configure(self.instance)
564
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500565 if self.call_make_run:
566 if options.ninja:
567 generator_cmd = "ninja"
568 else:
569 generator_cmd = "make"
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100570 command = [generator_cmd, "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500571 else:
572 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500573
Alberto Escolar Piedrasb91f3742019-07-02 10:06:22 +0200574 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500575 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100576 "--leak-check=full",
577 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
578 "--log-file="+self.outdir+"/valgrind.log"
579 ] + command
Anas Nashifdf7ee612018-07-07 06:09:01 -0500580
Marc Herbertaf1090c2019-04-30 14:11:29 -0700581 verbose("Spawning process: " +
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100582 " ".join(shlex.quote(word) for word in command) + os.linesep +
583 "Spawning process in directory: " + self.outdir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200584
585 start_time = time.time();
586
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100587 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.outdir) as proc:
Marc Herbertaf1090c2019-04-30 14:11:29 -0700588 verbose("Spawning BinaryHandler Thread for %s" % self.name)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500589 t = threading.Thread(target=self._output_reader, args=(proc, harness, ))
590 t.start()
591 t.join(self.timeout)
592 if t.is_alive():
Jan Kowalewski265895b2019-01-07 16:40:24 +0100593 self.try_kill_process_by_pid()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500594 proc.terminate()
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100595 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500596 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500597 proc.wait()
598 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500599
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200600 self.metrics["handler_time"] = time.time() - start_time;
601
Anas Nashifdf7ee612018-07-07 06:09:01 -0500602 if options.enable_coverage:
603 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir,
604 "gcov", self.sourcedir, "-b", "-s", self.outdir], shell=True)
605
Jan Kowalewski265895b2019-01-07 16:40:24 +0100606 self.try_kill_process_by_pid()
607
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500608 # FIME: This is needed when killing the simulator, the console is
609 # garbled and needs to be reset. Did not find a better way to do that.
610
611 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500612 self.instance.results = harness.tests
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100613 if self.terminated==False and self.returncode != 0:
614 #When a process is killed, the default handler returns 128 + SIGTERM
615 #so in that case the return code itself is not meaningful
616 self.set_state("error", {})
617 elif harness.state:
Anas Nashifdf7ee612018-07-07 06:09:01 -0500618 self.set_state(harness.state, {})
619 else:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100620 self.set_state("timeout", {})
Anas Nashif73440ea2018-02-19 10:57:03 -0600621
622class DeviceHandler(Handler):
623
Anas Nashifd18ec532019-04-11 23:20:39 -0400624 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600625 """Constructor
626
627 @param instance Test Instance
628 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400629 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600630
Marti Bolivar5591ca22019-02-07 15:53:39 -0700631 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500632 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600633
Marti Bolivar5591ca22019-02-07 15:53:39 -0700634 ser_fileno = ser.fileno()
635 readlist = [halt_fileno, ser_fileno]
636
Anas Nashif73440ea2018-02-19 10:57:03 -0600637 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700638 readable, _, _ = select.select(readlist, [], [], self.timeout)
639
640 if halt_fileno in readable:
641 verbose('halted')
642 ser.close()
643 break
644 if ser_fileno not in readable:
645 continue # Timeout.
646
647 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500648 try:
649 serial_line = ser.readline()
650 except TypeError:
651 pass
Anas Nashif59237602019-03-03 10:36:35 -0500652 except serial.serialutil.SerialException:
653 ser.close()
654 break
Anas Nashif61e21632018-04-08 13:30:16 -0500655
Marti Bolivar5591ca22019-02-07 15:53:39 -0700656 # Just because ser_fileno has data doesn't mean an entire line
657 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600658 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600659 sl = serial_line.decode('utf-8', 'ignore')
660 verbose("DEVICE: {0}".format(sl.rstrip()))
661
662 log_out_fp.write(sl)
663 log_out_fp.flush()
664 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700665
Anas Nashif73440ea2018-02-19 10:57:03 -0600666 if harness.state:
667 ser.close()
668 break
669
670 log_out_fp.close()
671
672 def handle(self):
673 out_state = "failed"
674
Andy Doan79c48842019-02-08 10:09:04 -0600675 if options.west_flash is not None:
676 command = ["west", "flash", "--skip-rebuild", "-d", self.outdir]
677 # There are two ways this option is used.
678 # 1) bare: --west-flash
679 # This results in options.west_flash == []
680 # 2) with a value: --west-flash="--board-id=42"
681 # This results in options.west_flash == "--board-id=42"
682 if options.west_flash != []:
683 command.append('--')
684 command.append(options.west_flash)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600685 else:
Andy Doan79c48842019-02-08 10:09:04 -0600686 if options.ninja:
687 generator_cmd = "ninja"
688 else:
689 generator_cmd = "make"
Anas Nashifd3384fb2018-02-22 06:44:16 -0600690
Andy Doan79c48842019-02-08 10:09:04 -0600691 command = [generator_cmd, "-C", self.outdir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600692
Anas Nashif73440ea2018-02-19 10:57:03 -0600693 device = options.device_serial
694 ser = serial.Serial(
695 device,
696 baudrate=115200,
697 parity=serial.PARITY_NONE,
698 stopbits=serial.STOPBITS_ONE,
699 bytesize=serial.EIGHTBITS,
700 timeout=self.timeout
701 )
702
703 ser.flush()
704
705 harness_name = self.instance.test.harness.capitalize()
706 harness_import = HarnessImporter(harness_name)
707 harness = harness_import.instance
708 harness.configure(self.instance)
Marti Bolivar5591ca22019-02-07 15:53:39 -0700709 rpipe, wpipe = os.pipe()
Anas Nashif73440ea2018-02-19 10:57:03 -0600710
Marti Bolivar5591ca22019-02-07 15:53:39 -0700711 t = threading.Thread(target=self.monitor_serial, daemon=True,
712 args=(ser, rpipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600713 t.start()
714
Andy Doan79c48842019-02-08 10:09:04 -0600715 logging.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500716 try:
Marti Bolivar303b5222019-02-07 15:50:55 -0700717 if VERBOSE:
718 subprocess.check_call(command)
719 else:
720 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif61e21632018-04-08 13:30:16 -0500721 except subprocess.CalledProcessError:
Marti Bolivar5591ca22019-02-07 15:53:39 -0700722 os.write(wpipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600723
724 t.join(self.timeout)
725 if t.is_alive():
726 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600727
728 if ser.isOpen():
729 ser.close()
730
Anas Nashifd3384fb2018-02-22 06:44:16 -0600731 if out_state == "timeout":
732 for c in self.instance.test.cases:
733 if c not in harness.tests:
734 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500735
736 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600737 if harness.state:
738 self.set_state(harness.state, {})
739 else:
740 self.set_state(out_state, {})
741
Anas Nashif3ba1d432017-12-05 15:28:44 -0500742
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300743class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700744 """Spawns a thread to monitor QEMU output from pipes
745
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400746 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700747 We need to do this as once qemu starts, it runs forever until killed.
748 Test cases emit special messages to the console as they run, we check
749 for these to collect whether the test passed or failed.
750 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700751
752 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500753 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700754 fifo_in = fifo_fn + ".in"
755 fifo_out = fifo_fn + ".out"
756
757 # These in/out nodes are named from QEMU's perspective, not ours
758 if os.path.exists(fifo_in):
759 os.unlink(fifo_in)
760 os.mkfifo(fifo_in)
761 if os.path.exists(fifo_out):
762 os.unlink(fifo_out)
763 os.mkfifo(fifo_out)
764
765 # We don't do anything with out_fp but we need to open it for
766 # writing so that QEMU doesn't block, due to the way pipes work
767 out_fp = open(fifo_in, "wb")
768 # Disable internal buffering, we don't
769 # want read() or poll() to ever block if there is data in there
770 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800771 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700772
773 start_time = time.time()
774 timeout_time = start_time + timeout
775 p = select.poll()
776 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400777 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700778
779 metrics = {}
780 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500781 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700782 while True:
783 this_timeout = int((timeout_time - time.time()) * 1000)
784 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400785 if not out_state:
786 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700787 break
788
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500789 try:
790 c = in_fp.read(1).decode("utf-8")
791 except UnicodeDecodeError:
792 # Test is writing something weird, fail
793 out_state = "unexpected byte"
794 break
795
Andrew Boie6acbe632015-07-17 12:03:52 -0700796 if c == "":
797 # EOF, this shouldn't happen unless QEMU crashes
798 out_state = "unexpected eof"
799 break
800 line = line + c
801 if c != "\n":
802 continue
803
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300804 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700805 log_out_fp.write(line)
806 log_out_fp.flush()
807 line = line.strip()
808 verbose("QEMU: %s" % line)
809
Anas Nashif576be982017-12-23 20:20:27 -0500810 harness.handle(line)
811 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400812 # if we have registered a fail make sure the state is not
813 # overridden by a false success message coming from the
814 # testsuite
815 if out_state != 'failed':
816 out_state = harness.state
817
818 # if we get some state, that means test is doing well, we reset
Anas Nashif74dbe332019-01-17 17:11:37 -0500819 # the timeout and wait for 2 more seconds just in case we have
Anas Nashif39ae72b2018-08-29 01:45:38 -0400820 # crashed after test has completed
Anas Nashiff29087e2019-01-25 09:37:38 -0500821 if not timeout_extended or harness.capture_coverage:
822 timeout_extended= True
823 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700824 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500825 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500826 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700827
828 # TODO: Add support for getting numerical performance data
829 # from test cases. Will involve extending test case reporting
830 # APIs. Add whatever gets reported to the metrics dictionary
831 line = ""
832
Anas Nashifc8390f12017-11-25 17:14:12 -0500833 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700834 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500835 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700836 handler.set_state(out_state, metrics)
837
838 log_out_fp.close()
839 out_fp.close()
840 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400841 if os.path.exists(pid_fn):
842 pid = int(open(pid_fn).read())
843 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700844
Anas Nashifd6476ee2019-04-11 11:40:09 -0400845 try:
846 if pid:
847 os.kill(pid, signal.SIGTERM)
848 except ProcessLookupError:
849 # Oh well, as long as it's dead! User probably sent Ctrl-C
850 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800851
Andrew Boie6acbe632015-07-17 12:03:52 -0700852 os.unlink(fifo_in)
853 os.unlink(fifo_out)
854
Anas Nashifd18ec532019-04-11 23:20:39 -0400855 def __init__(self, instance, type_str):
Andrew Boie6acbe632015-07-17 12:03:52 -0700856 """Constructor
857
Anas Nashifd3384fb2018-02-22 06:44:16 -0600858 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700859 """
Anas Nashif576be982017-12-23 20:20:27 -0500860
Anas Nashifd18ec532019-04-11 23:20:39 -0400861 super().__init__(instance, type_str)
Anas Nashif576be982017-12-23 20:20:27 -0500862
Andrew Boie6acbe632015-07-17 12:03:52 -0700863 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500864 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700865
866 # We pass this to QEMU which looks for fifos with .in and .out
867 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500868 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700869
Anas Nashif576be982017-12-23 20:20:27 -0500870 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700871 if os.path.exists(self.pid_fn):
872 os.unlink(self.pid_fn)
873
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500874 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500875
876 harness_import = HarnessImporter(instance.test.harness.capitalize())
877 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600878 harness.configure(self.instance)
879 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
880 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300881 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500882 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600883
884 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700885 self.thread.daemon = True
Marc Herbertaf1090c2019-04-30 14:11:29 -0700886 verbose("Spawning QEMUHandler Thread for %s 'make run'" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700887 self.thread.start()
888
Andrew Boie6acbe632015-07-17 12:03:52 -0700889 def get_fifo(self):
890 return self.fifo_fn
891
Anas Nashif3ba1d432017-12-05 15:28:44 -0500892
Andrew Boie6acbe632015-07-17 12:03:52 -0700893class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700894
Erwin Rolcb3d1272018-02-10 11:40:40 +0100895 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
896 "ccm_noinit"]
Daniel Leungc8066c52019-03-09 00:35:40 -0800897 rw_sections = ["datas", "initlevel", "exceptions", "initshell",
Andrew Boiec2e01df2018-11-12 15:16:54 -0800898 "_static_thread_area", "_k_timer_area",
Andrew Boie506f15c2018-11-08 14:44:31 -0800899 "_k_mem_slab_area", "_k_mem_pool_area", "sw_isr_table",
Andrew Boiec253a682019-01-29 12:56:02 -0800900 "_k_sem_area", "_k_mutex_area", "app_shmem_regions",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700901 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
902 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200903 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700904 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100905 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500906 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
907 'log_backends_sections', 'log_dynamic_sections',
Johann Fischerfcffebf2018-09-13 00:53:15 +0200908 'log_const_sections',"app_smem", 'shell_root_cmds_sections',
Adithya Baglody1fa8cf92018-10-19 10:15:19 -0700909 'log_const_sections',"app_smem", "font_entry_sections",
Anas Nashifdb9592a2018-10-08 10:19:41 -0400910 "priv_stacks_noinit", "_TEXT_SECTION_NAME_2",
Kumar Galae6393dd2019-02-16 10:08:33 -0600911 '_GCOV_BSS_SECTION_NAME', 'gcov', 'nocache']
Anas Nashifdb9592a2018-10-08 10:19:41 -0400912
Andrew Boie73b4ee62015-10-07 11:33:22 -0700913 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700914 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andrew Boie506f15c2018-11-08 14:44:31 -0800915 "rodata", "devconfig", "net_l2", "vector", "sw_isr_table",
Luiz Augusto von Dentza3bea882019-05-20 15:11:39 +0300916 "_bt_settings_area", "_bt_channels_area","_bt_services_area",
917 "vectors", "net_socket_register"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700918
Andrew Boie52fef672016-11-29 12:21:59 -0800919 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700920 """Constructor
921
Andrew Boiebbd670c2015-08-17 13:16:11 -0700922 @param filename Path to the output binary
923 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700924 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700925 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700926 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700927 magic = f.read(4)
928
Anas Nashifb4bdd662018-08-15 17:12:28 -0500929 try:
930 if (magic != b'\x7fELF'):
931 raise SanityRuntimeError("%s is not an ELF binary" % filename)
932 except Exception as e:
933 print(str(e))
934 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -0700935
936 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500937 # GREP can not be used as it returns an error if the symbol is not
938 # found.
939 is_xip_command = "nm " + filename + \
940 " | awk '/CONFIG_XIP/ { print $3 }'"
941 is_xip_output = subprocess.check_output(
942 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
943 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -0500944 try:
945 if is_xip_output.endswith("no symbols"):
946 raise SanityRuntimeError("%s has no symbol information" % filename)
947 except Exception as e:
948 print(str(e))
949 sys.exit(2)
950
Andrew Boie6acbe632015-07-17 12:03:52 -0700951 self.is_xip = (len(is_xip_output) != 0)
952
Andrew Boiebbd670c2015-08-17 13:16:11 -0700953 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700954 self.sections = []
955 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700956 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800957 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700958
959 self._calculate_sizes()
960
961 def get_ram_size(self):
962 """Get the amount of RAM the application will use up on the device
963
964 @return amount of RAM, in bytes
965 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700966 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700967
968 def get_rom_size(self):
969 """Get the size of the data that this application uses on device's flash
970
971 @return amount of ROM, in bytes
972 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700973 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700974
975 def unrecognized_sections(self):
976 """Get a list of sections inside the binary that weren't recognized
977
David B. Kinder29963c32017-06-16 12:32:42 -0700978 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700979 """
980 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700981 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700982 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700983 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700984 return slist
985
986 def _calculate_sizes(self):
987 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700988 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500989 objdump_output = subprocess.check_output(
990 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700991
992 for line in objdump_output:
993 words = line.split()
994
995 if (len(words) == 0): # Skip lines that are too short
996 continue
997
998 index = words[0]
999 if (not index[0].isdigit()): # Skip lines that do not start
1000 continue # with a digit
1001
1002 name = words[1] # Skip lines with section names
1003 if (name[0] == '.'): # starting with '.'
1004 continue
1005
Andrew Boie73b4ee62015-10-07 11:33:22 -07001006 # TODO this doesn't actually reflect the size in flash or RAM as
1007 # it doesn't include linker-imposed padding between sections.
1008 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001009 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001010 if size == 0:
1011 continue
1012
Andrew Boie73b4ee62015-10-07 11:33:22 -07001013 load_addr = int(words[4], 16)
1014 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001015
1016 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001017 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001018 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001019 if name in SizeCalculator.alloc_sections:
1020 self.ram_size += size
1021 stype = "alloc"
1022 elif name in SizeCalculator.rw_sections:
1023 self.ram_size += size
1024 self.rom_size += size
1025 stype = "rw"
1026 elif name in SizeCalculator.ro_sections:
1027 self.rom_size += size
1028 if not self.is_xip:
1029 self.ram_size += size
1030 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001031 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001032 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001033 if name not in self.extra_sections:
1034 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001035
Anas Nashif3ba1d432017-12-05 15:28:44 -05001036 self.sections.append({"name": name, "load_addr": load_addr,
1037 "size": size, "virt_addr": virt_addr,
1038 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001039
1040
1041class MakeGoal:
1042 """Metadata class representing one of the sub-makes called by MakeGenerator
1043
David B. Kinder29963c32017-06-16 12:32:42 -07001044 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -07001045 with TestInstances to get a complete picture of what happened during a test.
1046 MakeGenerator is used for tasks outside of building tests (such as
1047 defconfigs) which is why MakeGoal is a separate class from TestInstance.
1048 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001049
Anas Nashif4d25b502017-11-25 17:37:17 -05001050 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -07001051 self.name = name
1052 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -05001053 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001054 self.make_log = make_log
1055 self.build_log = build_log
1056 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -05001057 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001058 self.make_state = "waiting"
1059 self.failed = False
1060 self.finished = False
1061 self.reason = None
1062 self.metrics = {}
1063
1064 def get_error_log(self):
1065 if self.make_state == "waiting":
1066 # Shouldn't ever see this; breakage in the main Makefile itself.
1067 return self.make_log
1068 elif self.make_state == "building":
1069 # Failure when calling the sub-make to build the code
1070 return self.build_log
1071 elif self.make_state == "running":
Marc Herbert75276a72019-05-13 14:41:59 -07001072 # Failure in sub-make for "make run", qemu probably failed.
1073 # Return qemu's output if there is one, otherwise make's.
1074 h = Path(self.handler_log)
1075 if h.exists() and h.stat().st_size > 0:
1076 return self.handler_log
1077 else:
1078 return self.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001079 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -05001080 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -05001081 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001082
1083 def fail(self, reason):
1084 self.failed = True
1085 self.finished = True
1086 self.reason = reason
1087
1088 def success(self):
1089 self.finished = True
1090
1091 def __str__(self):
1092 if self.finished:
1093 if self.failed:
1094 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
1095 self.get_error_log())
1096 else:
1097 return "[%s] passed" % self.name
1098 else:
1099 return "[%s] in progress (%s)" % (self.name, self.make_state)
1100
1101
1102class MakeGenerator:
1103 """Generates a Makefile which just calls a bunch of sub-make sessions
1104
1105 In any given test suite we may need to build dozens if not hundreds of
1106 test cases. The cleanest way to parallelize this is to just let Make
1107 do the parallelization, sharing the jobserver among all the different
1108 sub-make targets.
1109 """
1110
1111 GOAL_HEADER_TMPL = """.PHONY: {goal}
1112{goal}:
1113"""
1114
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001115 MAKE_RULE_TMPL_CMAKE = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -04001116\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -05001117\t\t-G"{generator}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001118\t\t-H{directory}\\
1119\t\t-B{outdir}\\
1120\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
1121\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -05001122\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001123\t\t{args}\\
1124\t\t>{logfile} 2>&1
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001125"""
1126 MAKE_RULE_TMPL_BLD = """\t{generator_cmd} -C {outdir}\\
Anas Nashifa8a13882017-12-30 13:01:06 -05001127\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001128\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -07001129"""
Anas Nashif20f553f2018-03-23 11:26:41 -05001130 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
1131\t{generator_cmd} -C {outdir}\\
1132\t\t{verb} {make_args}\\
1133\t\t>>{logfile} 2>&1
1134"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001135
1136 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
1137
Anas Nashif3ba1d432017-12-05 15:28:44 -05001138 re_make = re.compile(
1139 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -07001140
Anas Nashif37f9dc52018-02-23 08:53:46 -06001141 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001142 """MakeGenerator constructor
1143
1144 @param base_outdir Intended to be the base out directory. A make.log
1145 file will be created here which contains the output of the
1146 top-level Make session, as well as the dynamic control Makefile
1147 @param verbose If true, pass V=1 to all the sub-makes which greatly
1148 increases their verbosity
1149 """
1150 self.goals = {}
1151 if not os.path.exists(base_outdir):
1152 os.makedirs(base_outdir)
1153 self.logfile = os.path.join(base_outdir, "make.log")
1154 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -06001155 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -07001156
1157 def _get_rule_header(self, name):
1158 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
1159
Anas Nashif3ba1d432017-12-05 15:28:44 -05001160 def _get_sub_make(self, name, phase, workdir, outdir,
1161 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -04001162 """
1163 @param args Arguments given to CMake
1164 @param make_args Arguments given to the Makefile generated by CMake
1165 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001166 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -05001167 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -07001168 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -05001169
1170 if self.deprecations:
1171 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -08001172
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +02001173 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -05001174
Anas Nashif25f6ab62018-03-06 07:15:11 -06001175 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001176 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -07001177 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001178 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -06001179 else:
1180 generator = "Unix Makefiles"
1181 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -07001182 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -05001183
Anas Nashif20f553f2018-03-23 11:26:41 -05001184 if phase == 'running':
1185 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
1186 generator_cmd=generator_cmd,
1187 phase=phase,
1188 goal=name,
1189 outdir=outdir,
1190 verb=verb,
1191 logfile=logfile,
1192 make_args=make_args
1193 )
1194 else:
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001195 cmake_rule = MakeGenerator.MAKE_RULE_TMPL_CMAKE.format(
Anas Nashif20f553f2018-03-23 11:26:41 -05001196 generator=generator,
Anas Nashif20f553f2018-03-23 11:26:41 -05001197 phase=phase,
1198 goal=name,
1199 outdir=outdir,
1200 cflags=cflags,
1201 ldflags=ldflags,
1202 directory=workdir,
Anas Nashif20f553f2018-03-23 11:26:41 -05001203 args=args,
1204 logfile=logfile,
Anas Nashif20f553f2018-03-23 11:26:41 -05001205 )
Andrew Boie6acbe632015-07-17 12:03:52 -07001206
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001207 if options.cmake_only:
1208 build_rule = ""
1209 else:
1210 build_rule = MakeGenerator.MAKE_RULE_TMPL_BLD.format(
1211 generator_cmd=generator_cmd,
1212 outdir=outdir,
1213 verb=verb,
1214 make_args=make_args,
1215 logfile=logfile,
1216 )
1217
1218 return cmake_rule + build_rule
1219
Andrew Boie6acbe632015-07-17 12:03:52 -07001220 def _get_rule_footer(self, name):
1221 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
1222
Anas Nashif3ba1d432017-12-05 15:28:44 -05001223 def add_build_goal(self, name, directory, outdir,
1224 args, buildlog, make_args=""):
Anas Nashif13773752018-07-06 18:20:23 -05001225 """Add a goal to invoke a build session
Andrew Boie6acbe632015-07-17 12:03:52 -07001226
1227 @param name A unique string name for this build goal. The results
1228 dictionary returned by execute() will be keyed by this name.
1229 @param directory Absolute path to working directory, will be passed
1230 to make -C
1231 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001232 cmake via -B=<path>
1233 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -07001234 environment variables or specific Make goals
1235 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001236
Anas Nashif13773752018-07-06 18:20:23 -05001237 if not os.path.exists(outdir):
1238 os.makedirs(outdir)
1239
1240 build_logfile = os.path.join(outdir, buildlog)
1241 text = self._get_rule_header(name)
1242 text += self._get_sub_make(name, "building", directory, outdir, build_logfile,
1243 args, make_args=make_args)
1244 text += self._get_rule_footer(name)
1245
1246 self.goals[name] = MakeGoal( name, text, None, self.logfile, build_logfile, None, None)
1247
1248 def add_goal(self, instance, type, args, make_args=""):
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001249
Anas Nashif13773752018-07-06 18:20:23 -05001250 """Add a goal to build a Zephyr project and then run it using a handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001251
1252 The generated make goal invokes Make twice, the first time it will
1253 build the default goal, and the second will invoke the 'qemu' goal.
Anas Nashif13773752018-07-06 18:20:23 -05001254 The output of the handler session will be monitored, and terminated
Andrew Boie6acbe632015-07-17 12:03:52 -07001255 either upon pass/fail result of the test program, or the timeout
1256 is reached.
1257
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001258 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001259 """
1260
Anas Nashif576be982017-12-23 20:20:27 -05001261 name = instance.name
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001262 directory = instance.test.test_path
Anas Nashif576be982017-12-23 20:20:27 -05001263 outdir = instance.outdir
1264
Andrew Boie6acbe632015-07-17 12:03:52 -07001265 build_logfile = os.path.join(outdir, "build.log")
1266 run_logfile = os.path.join(outdir, "run.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001267
Anas Nashif13773752018-07-06 18:20:23 -05001268 if not os.path.exists(outdir):
1269 os.makedirs(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001270
Anas Nashif13773752018-07-06 18:20:23 -05001271 handler = None
1272 if type == "qemu":
Anas Nashifd18ec532019-04-11 23:20:39 -04001273 handler = QEMUHandler(instance, "qemu")
Anas Nashif13773752018-07-06 18:20:23 -05001274 elif type == "native":
Anas Nashifd18ec532019-04-11 23:20:39 -04001275 handler = BinaryHandler(instance, "native")
Anas Nashifdf7ee612018-07-07 06:09:01 -05001276 handler.binary = os.path.join(outdir, "zephyr", "zephyr.exe")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001277 if options.enable_coverage:
1278 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001279 elif type == "nsim":
Anas Nashifd18ec532019-04-11 23:20:39 -04001280 handler = BinaryHandler(instance, "nsim")
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001281 handler.call_make_run = True
Anas Nashif13773752018-07-06 18:20:23 -05001282 elif type == "unit":
Anas Nashifd18ec532019-04-11 23:20:39 -04001283 handler = BinaryHandler(instance, "unit")
Anas Nashifdf7ee612018-07-07 06:09:01 -05001284 handler.binary = os.path.join(outdir, "testbinary")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001285 if options.enable_coverage:
1286 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif13773752018-07-06 18:20:23 -05001287 elif type == "device":
Anas Nashifd18ec532019-04-11 23:20:39 -04001288 handler = DeviceHandler(instance, "device")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001289 elif type == "renode":
Anas Nashifd18ec532019-04-11 23:20:39 -04001290 handler = BinaryHandler(instance, "renode")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001291 handler.pid_fn = os.path.join(instance.outdir, "renode.pid")
1292 handler.call_make_run = True
Anas Nashif576be982017-12-23 20:20:27 -05001293
Anas Nashif13773752018-07-06 18:20:23 -05001294 if type == 'qemu':
1295 args.append("QEMU_PIPE=%s" % handler.get_fifo())
1296
Andy Doancbecadd2019-02-08 10:19:10 -06001297 text = self._get_rule_header(name)
1298 if not options.test_only:
1299 text += self._get_sub_make(name, "building", directory, outdir,
1300 build_logfile, args, make_args=make_args)
Anas Nashif13773752018-07-06 18:20:23 -05001301 if handler and handler.run:
1302 text += self._get_sub_make(name, "running", directory,
1303 outdir, run_logfile,
1304 args, make_args="run")
Anas Nashif4d25b502017-11-25 17:37:17 -05001305
Anas Nashif13773752018-07-06 18:20:23 -05001306 text += self._get_rule_footer(name)
Anas Nashif73440ea2018-02-19 10:57:03 -06001307
Anas Nashif73440ea2018-02-19 10:57:03 -06001308 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001309 run_logfile, handler.log if handler else None)
Anas Nashif73440ea2018-02-19 10:57:03 -06001310
Anas Nashif13773752018-07-06 18:20:23 -05001311
Anas Nashif37f9dc52018-02-23 08:53:46 -06001312 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001313 """Add a goal to build/test a TestInstance object
1314
1315 @param ti TestInstance object to build. The status dictionary returned
1316 by execute() will be keyed by its .name field.
1317 """
1318 args = ti.test.extra_args[:]
Anas Nashiff9e73c92019-02-12 13:59:08 -05001319
1320 # merge overlay files into one variable
1321 overlays = ""
1322 idx = 0
1323 for a in args:
1324 m = re.search('OVERLAY_CONFIG="(.*)"', a)
1325 if m:
1326 overlays += m.group(1)
1327 del args[idx]
1328 idx += 1
1329
Anas Nashif3cbffef2018-11-07 23:50:54 -05001330 if len(ti.test.extra_configs) > 0 or options.coverage:
Anas Nashiff9e73c92019-02-12 13:59:08 -05001331 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
1332 os.path.join(ti.outdir, "overlay.conf")))
Anas Nashiffa695d22017-10-04 16:14:27 -04001333
Anas Nashif3cbffef2018-11-07 23:50:54 -05001334 if ti.test.type == "unit" and options.enable_coverage:
1335 args.append("COVERAGE=1")
1336
Anas Nashiffb91ad62017-10-31 08:33:17 -04001337 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001338 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001339
Anas Nashif37f9dc52018-02-23 08:53:46 -06001340 do_build_only = ti.build_only or options.build_only
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001341 do_run = not do_build_only and not options.cmake_only
Andy Rossdc4151f2019-01-03 14:17:43 -08001342 skip_slow = ti.test.slow and not options.enable_slow
Anas Nashif5df8cff2018-02-23 08:37:14 -06001343
Anas Nashif13773752018-07-06 18:20:23 -05001344 # FIXME: Need refactoring and cleanup
1345 type = None
Anas Nashif5df8cff2018-02-23 08:37:14 -06001346 if ti.platform.qemu_support and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001347 type = "qemu"
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001348 elif ti.test.type == "unit":
Anas Nashif13773752018-07-06 18:20:23 -05001349 type = "unit"
Anas Nashif5df8cff2018-02-23 08:37:14 -06001350 elif ti.platform.type == "native" and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001351 type = "native"
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001352 elif ti.platform.simulation == "nsim" and do_run:
Anas Nashife24350c2018-07-11 15:09:22 -05001353 if find_executable("nsimdrv"):
1354 type = "nsim"
Jan Kowalewski265895b2019-01-07 16:40:24 +01001355 elif ti.platform.simulation == "renode" and do_run:
1356 if find_executable("renode"):
1357 type = "renode"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001358 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif13773752018-07-06 18:20:23 -05001359 type = "device"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001360
Andy Rossdc4151f2019-01-03 14:17:43 -08001361 if not skip_slow:
1362 self.add_goal(ti, type, args)
1363 else:
1364 verbose("Skipping slow test: " + ti.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001365
1366 def execute(self, callback_fn=None, context=None):
1367 """Execute all the registered build goals
1368
1369 @param callback_fn If not None, a callback function will be called
1370 as individual goals transition between states. This function
1371 should accept two parameters: a string state and an arbitrary
1372 context object, supplied here
1373 @param context Context object to pass to the callback function.
1374 Type and semantics are specific to that callback function.
1375 @return A dictionary mapping goal names to final status.
1376 """
1377
Andrew Boie08ce5a52016-02-22 13:28:10 -08001378 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001379 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001380 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001381 # Create our dynamic Makefile and execute it.
1382 # Watch stderr output which is where we will keep
1383 # track of build state
Marc Herbert6f011c92019-03-20 13:58:55 -07001384 tf.write('\n# Generated by %s which is expected\n' % __file__)
Marc Herbertaf1090c2019-04-30 14:11:29 -07001385 tf.write('# to create QEMU_PIPE, spawn zephyr.exe, etc. \n\n')
Marc Herbert6f011c92019-03-20 13:58:55 -07001386 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07001387 tf.write(goal.text)
Marc Herbert6f011c92019-03-20 13:58:55 -07001388 tf.write("all: %s\n" % (" ".join(sorted(self.goals.keys()))))
Andrew Boie6acbe632015-07-17 12:03:52 -07001389 tf.flush()
1390
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03001391 cmd = ["make", "-k", "-j", str(JOBS), "-f", tf.name, "all"]
Andy Rossdec163f2018-05-21 10:12:59 -07001392
Anas Nashiff2cb20c2019-06-18 14:45:40 -04001393 # assure language neutral environment
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001394 make_env = os.environ.copy()
1395 make_env['LC_MESSAGES'] = 'C.UTF-8'
1396 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1397 stdout=devnull, env=make_env)
Andrew Boie6acbe632015-07-17 12:03:52 -07001398
1399 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001400 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001401 make_log.write(line)
1402 verbose("MAKE: " + repr(line.strip()))
1403 m = MakeGenerator.re_make.match(line)
1404 if not m:
1405 continue
1406
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001407 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001408 if error:
1409 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001410 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001411 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001412 # nonzero status.
1413 # Need to distinguish this case from a compilation failure.
Anas Nashif042d9b72019-04-11 10:31:01 -04001414 if goal.make_state == "building":
1415 goal.fail("build_error")
1416 elif goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001417 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001418 else:
Anas Nashif042d9b72019-04-11 10:31:01 -04001419 goal.fail("unknown_error")
1420
Andrew Boie6acbe632015-07-17 12:03:52 -07001421 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001422 goal = self.goals[name]
1423 goal.make_state = state
1424
Andrew Boie6acbe632015-07-17 12:03:52 -07001425 if state == "finished":
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001426 if goal.handler and not options.cmake_only:
Anas Nashif576be982017-12-23 20:20:27 -05001427 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001428 goal.handler.handle()
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001429 goal.handler_log = goal.handler.log
Anas Nashifcc164222017-12-26 11:02:46 -05001430
Anas Nashif4d25b502017-11-25 17:37:17 -05001431 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001432 goal.metrics.update(metrics)
1433 if thread_status == "passed":
1434 goal.success()
1435 else:
1436 goal.fail(thread_status)
1437 else:
1438 goal.success()
1439
1440 if callback_fn:
1441 callback_fn(context, self.goals, goal)
1442
1443 p.wait()
1444 return self.goals
1445
1446
1447# "list" - List of strings
1448# "list:<type>" - List of <type>
1449# "set" - Set of unordered, unique strings
1450# "set:<type>" - Set of <type>
1451# "float" - Floating point
1452# "int" - Integer
1453# "bool" - Boolean
1454# "str" - String
1455
1456# XXX Be sure to update __doc__ if you change any of this!!
1457
Anas Nashif3ba1d432017-12-05 15:28:44 -05001458platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
Anas Nashif924a4e72018-10-18 12:25:55 -04001459 "supported_toolchains": {"type": "list", "default": []},
1460 "env": {"type": "list", "default": []}
1461 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001462
Anas Nashif3ba1d432017-12-05 15:28:44 -05001463testcase_valid_keys = {"tags": {"type": "set", "required": False},
1464 "type": {"type": "str", "default": "integration"},
1465 "extra_args": {"type": "list"},
1466 "extra_configs": {"type": "list"},
1467 "build_only": {"type": "bool", "default": False},
1468 "build_on_all": {"type": "bool", "default": False},
1469 "skip": {"type": "bool", "default": False},
1470 "slow": {"type": "bool", "default": False},
1471 "timeout": {"type": "int", "default": 60},
1472 "min_ram": {"type": "int", "default": 8},
1473 "depends_on": {"type": "set"},
1474 "min_flash": {"type": "int", "default": 32},
1475 "arch_whitelist": {"type": "set"},
1476 "arch_exclude": {"type": "set"},
1477 "extra_sections": {"type": "list", "default": []},
1478 "platform_exclude": {"type": "set"},
1479 "platform_whitelist": {"type": "set"},
1480 "toolchain_exclude": {"type": "set"},
1481 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001482 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001483 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301484 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001485 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001486
1487
1488class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001489 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001490 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001491
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001492 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001493 """Instantiate a new SanityConfigParser object
1494
Anas Nashifa792a3d2017-04-04 18:47:49 -04001495 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001496 """
Anas Nashif255625b2017-12-05 15:08:26 -05001497 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001498 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001499 self.tests = {}
1500 self.common = {}
1501 if 'tests' in self.data:
1502 self.tests = self.data['tests']
1503 if 'common' in self.data:
1504 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001505
1506 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001507 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001508 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001509 if typestr == "str":
1510 return v
1511
1512 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001513 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001514
1515 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001516 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001517
1518 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001519 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001520
Anas Nashif3ba1d432017-12-05 15:28:44 -05001521 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001522 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001523 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001524 vs = v.split()
1525 if len(typestr) > 4 and typestr[4] == ":":
1526 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1527 else:
1528 return vs
1529
1530 elif typestr.startswith("set"):
1531 vs = v.split()
1532 if len(typestr) > 3 and typestr[3] == ":":
1533 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1534 else:
1535 return set(vs)
1536
Anas Nashif576be982017-12-23 20:20:27 -05001537 elif typestr.startswith("map"):
1538 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001539 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001540 raise ConfigurationError(
1541 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001542
Anas Nashifb4754ed2017-12-05 17:27:58 -05001543 def get_test(self, name, valid_keys):
1544 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001545
Anas Nashifb4754ed2017-12-05 17:27:58 -05001546 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001547 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001548 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001549 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001550 here, it will generate an error. Each value in this dictionary
1551 is another dictionary containing metadata:
1552
1553 "default" - Default value if not given
1554 "type" - Data type to convert the text value to. Simple types
1555 supported are "str", "float", "int", "bool" which will get
1556 converted to respective Python data types. "set" and "list"
1557 may also be specified which will split the value by
1558 whitespace (but keep the elements as strings). finally,
1559 "list:<type>" and "set:<type>" may be given which will
1560 perform a type conversion after splitting the value up.
1561 "required" - If true, raise an error if not defined. If false
1562 and "default" isn't specified, a type conversion will be
1563 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001564 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001565 type conversion and default values filled in per valid_keys
1566 """
1567
1568 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001569 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001570 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001571
Anas Nashifb4754ed2017-12-05 17:27:58 -05001572 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001573 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001574 raise ConfigurationError(
1575 self.filename,
1576 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001577 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001578
Anas Nashiffa695d22017-10-04 16:14:27 -04001579 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001580 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001581 d[k] += " " + v
1582 else:
1583 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001584
Andrew Boie08ce5a52016-02-22 13:28:10 -08001585 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001586 if k not in d:
1587 if "required" in kinfo:
1588 required = kinfo["required"]
1589 else:
1590 required = False
1591
1592 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001593 raise ConfigurationError(
1594 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001595 "missing required value for '%s' in test '%s'" %
1596 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001597 else:
1598 if "default" in kinfo:
1599 default = kinfo["default"]
1600 else:
1601 default = self._cast_value("", kinfo["type"])
1602 d[k] = default
1603 else:
1604 try:
1605 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001606 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001607 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001608 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1609 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001610
1611 return d
1612
1613
1614class Platform:
1615 """Class representing metadata for a particular platform
1616
Anas Nashifc7406082015-12-13 15:00:31 -05001617 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001618
1619 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001620 os.path.join(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001621 ZEPHYR_BASE,
Anas Nashif3ba1d432017-12-05 15:28:44 -05001622 "scripts",
1623 "sanity_chk",
1624 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001625
Anas Nashifa792a3d2017-04-04 18:47:49 -04001626 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001627 """Constructor.
1628
Anas Nashif877d3ca2017-12-05 17:39:29 -05001629 @param cfile Path to platform configuration file, which gives
1630 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001631 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001632 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001633 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001634
Anas Nashif255625b2017-12-05 15:08:26 -05001635 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001636 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001637 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001638 self.ram = data.get("ram", 128)
1639 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001640 self.ignore_tags = testing.get("ignore_tags", [])
1641 self.default = testing.get("default", False)
1642 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001643 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001644 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001645 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001646 for item in supp_feature.split(":"):
1647 self.supported.add(item)
1648
Anas Nashif8acdbd72018-01-04 14:15:22 -05001649 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001650 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001651 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001652 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001653 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001654 self.env = data.get("env", [])
1655 self.env_satisfied = True
1656 for env in self.env:
1657 if os.environ.get(env, None) == None:
1658 self.env_satisfied = False
Andrew Boie41878222016-11-03 11:58:53 -07001659 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001660 pass
1661
Andrew Boie6acbe632015-07-17 12:03:52 -07001662 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001663 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001664
1665
1666class Architecture:
1667 """Class representing metadata for a particular architecture
1668 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001669
Anas Nashifa792a3d2017-04-04 18:47:49 -04001670 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001671 """Architecture constructor
1672
Anas Nashif877d3ca2017-12-05 17:39:29 -05001673 @param name String name for this architecture
1674 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001675 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001676 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001677
Anas Nashifa792a3d2017-04-04 18:47:49 -04001678 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001679
1680 def __repr__(self):
1681 return "<arch %s>" % self.name
1682
1683
1684class TestCase:
1685 """Class representing a test application
1686 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001687
Anas Nashif7fae29c2017-10-09 13:19:12 -04001688 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001689 """TestCase constructor.
1690
Anas Nashif877d3ca2017-12-05 17:39:29 -05001691 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001692 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001693 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001694
Andrew Boie6acbe632015-07-17 12:03:52 -07001695 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001696 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001697 the test case is <workdir>/<name>.
1698
Marc Herbert1c8632c2019-04-15 17:58:45 -07001699 @param testcase_root os.path.abspath() of one of the --testcase-root
1700 @param workdir Sub-directory of testcase_root where the
1701 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001702 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001703 in the test case configuration file. For many test cases that just
1704 define one test, can be anything and is usually "test". This is
1705 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001706 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001707 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001708 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001709 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001710 self.test_path = os.path.join(testcase_root, workdir)
1711
Anas Nashifaae71d72018-04-21 22:26:48 -05001712 self.id = name
1713 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001714 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001715 self.tags = tc_dict["tags"]
1716 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001717 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001718 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001719 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001720 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001721 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001722 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001723 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1724 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001725 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001726 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001727 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001728 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001729 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001730 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001731 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001732 self.min_ram = tc_dict["min_ram"]
1733 self.depends_on = tc_dict["depends_on"]
1734 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001735 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001736
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001737 self.name = self.get_unique(testcase_root, workdir, name)
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001738
Anas Nashif2cf0df02015-10-10 09:29:43 -04001739 self.defconfig = {}
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001740 self.dt_config = {}
Anas Nashif45a97862019-01-09 08:46:42 -05001741 self.cmake_cache = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001742 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001743
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001744
1745 def get_unique(self, testcase_root, workdir, name):
1746
Marc Herbert1c8632c2019-04-15 17:58:45 -07001747 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001748 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001749 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001750 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001751 relative_tc_root = os.path.relpath(canonical_testcase_root,
1752 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001753 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001754 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001755
Marc Herbert1c8632c2019-04-15 17:58:45 -07001756 # workdir can be "."
1757 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001758 return unique
1759
Anas Nashifaae71d72018-04-21 22:26:48 -05001760 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001761 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001762 # do not match until end-of-line, otherwise we won't allow
1763 # stc_regex below to catch the ones that are declared in the same
1764 # line--as we only search starting the end of this match
1765 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001766 re.MULTILINE)
1767 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001768 br"^\s*" # empy space at the beginning is ok
1769 # catch the case where it is declared in the same sentence, e.g:
1770 #
1771 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1772 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1773 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1774 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1775 # Consume the argument that becomes the extra testcse
1776 br"\(\s*"
1777 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1778 # _setup_teardown() variant has two extra arguments that we ignore
1779 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1780 br"\s*\)",
1781 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001782 re.MULTILINE)
1783 suite_run_regex = re.compile(
1784 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1785 re.MULTILINE)
1786 achtung_regex = re.compile(
1787 br"(#ifdef|#endif)",
1788 re.MULTILINE)
1789 warnings = None
1790
1791 with open(inf_name) as inf:
1792 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1793 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001794 suite_regex_match = suite_regex.search(main_c)
1795 if not suite_regex_match:
1796 # can't find ztest_test_suite, maybe a client, because
1797 # it includes ztest.h
1798 return None, None
1799
1800 suite_run_match = suite_run_regex.search(main_c)
1801 if not suite_run_match:
1802 raise ValueError("can't find ztest_run_test_suite")
1803
1804 achtung_matches = re.findall(
1805 achtung_regex,
1806 main_c[suite_regex_match.end():suite_run_match.start()])
1807 if achtung_matches:
1808 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001809 % ", ".join(set([
1810 match.decode() for match in achtung_matches
1811 ]))
1812 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001813 stc_regex,
1814 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001815 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001816 return matches, warnings
1817
1818 def scan_path(self, path):
1819 subcases = []
1820 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1821 try:
1822 _subcases, warnings = self.scan_file(filename)
1823 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001824 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001825 if _subcases:
1826 subcases += _subcases
1827 except ValueError as e:
Flavio Ceolinbf878ce2019-04-19 22:24:09 -07001828 error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001829 return subcases
1830
1831
1832 def parse_subcases(self):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001833 results = self.scan_path(self.test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001834 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001835 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001836 self.cases.append(name)
1837
Anas Nashiff16e92c2019-03-31 16:58:12 -04001838 if not results:
1839 self.cases.append(self.id)
1840
Anas Nashifaae71d72018-04-21 22:26:48 -05001841
Anas Nashif75547e22018-02-24 08:32:14 -06001842 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001843 return self.name
1844
1845
Andrew Boie6acbe632015-07-17 12:03:52 -07001846class TestInstance:
1847 """Class representing the execution of a particular TestCase on a platform
1848
1849 @param test The TestCase object we want to build/execute
1850 @param platform Platform object that we want to build and run against
1851 @param base_outdir Base directory for all test results. The actual
1852 out directory used is <outdir>/<platform>/<test case name>
1853 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001854
Anas Nashif37f9dc52018-02-23 08:53:46 -06001855 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001856 self.test = test
1857 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001858 self.name = os.path.join(platform.name, test.name)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001859 self.outdir = os.path.join(base_outdir, platform.name, test.name)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001860
1861 self.build_only = options.build_only or test.build_only \
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001862 or self.check_dependency() or options.cmake_only
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001863 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001864
Marc Herbert0f7255c2019-04-05 14:14:21 -07001865 def __lt__(self, other):
1866 return self.name < other.name
1867
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001868 def check_dependency(self):
1869 build_only = False
1870 if self.test.harness == 'console':
1871 if "fixture" in self.test.harness_config:
1872 fixture = self.test.harness_config['fixture']
1873 if fixture not in options.fixture:
1874 build_only = True
1875 elif self.test.harness:
1876 build_only = True
1877
1878 return build_only
1879
Anas Nashifdbd76492018-11-23 20:24:19 -05001880 def create_overlay(self, platform):
Anas Nashif3cbffef2018-11-07 23:50:54 -05001881 file = os.path.join(self.outdir, "overlay.conf")
1882 os.makedirs(self.outdir, exist_ok=True)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001883 with open(file, "w") as f:
1884 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001885
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001886 if len(self.test.extra_configs) > 0:
1887 content = "\n".join(self.test.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001888
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001889 if options.enable_coverage:
1890 if platform in options.coverage_platform:
1891 content = content + "\nCONFIG_COVERAGE=y"
1892
1893 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001894
Andrew Boie6acbe632015-07-17 12:03:52 -07001895 def calculate_sizes(self):
1896 """Get the RAM/ROM sizes of a test case.
1897
1898 This can only be run after the instance has been executed by
1899 MakeGenerator, otherwise there won't be any binaries to measure.
1900
1901 @return A SizeCalculator object
1902 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001903 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001904 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001905 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001906 if (len(fns) != 1):
1907 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001908 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001909
1910 def __repr__(self):
1911 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1912
1913
Andrew Boie4ef16c52015-08-28 12:36:03 -07001914def defconfig_cb(context, goals, goal):
1915 if not goal.failed:
1916 return
1917
1918 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001919 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001920 if INLINE_LOGS:
1921 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001922 data = fp.read()
1923 sys.stdout.write(data)
1924 if log_file:
1925 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001926 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001927 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001928
Andrew Boie6acbe632015-07-17 12:03:52 -07001929
1930class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001931 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001932 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001933
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001934 yaml_tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001935 os.path.join(ZEPHYR_BASE,
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001936 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001937
Anas Nashif37f9dc52018-02-23 08:53:46 -06001938 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001939 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001940 self.arches = {}
1941 self.testcases = {}
1942 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001943 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001944 self.instances = {}
1945 self.goals = None
1946 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001947 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001948
Andrew Boie3d348712016-04-08 11:52:13 -07001949 for testcase_root in testcase_roots:
1950 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001951
Andrew Boie3d348712016-04-08 11:52:13 -07001952 debug("Reading test case configuration files under %s..." %
1953 testcase_root)
1954 for dirpath, dirnames, filenames in os.walk(testcase_root,
1955 topdown=True):
1956 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001957 if 'sample.yaml' in filenames:
1958 filename = 'sample.yaml'
1959 elif 'testcase.yaml' in filenames:
1960 filename = 'testcase.yaml'
1961 else:
1962 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001963
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001964 verbose("Found possible test case in " + dirpath)
1965 dirnames[:] = []
1966 yaml_path = os.path.join(dirpath, filename)
1967 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001968 parsed_data = SanityConfigParser(
1969 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001970
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001971 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001972
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001973 for name in parsed_data.tests.keys():
1974 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1975 tc = TestCase(testcase_root, workdir, name, tc_dict,
1976 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001977 tc.parse_subcases()
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001978 self.testcases[tc.name] = tc
1979
1980 except Exception as e:
1981 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001982 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001983
Andrew Boie6acbe632015-07-17 12:03:52 -07001984
Anas Nashif86c8e232017-10-09 13:42:28 -04001985 for board_root in board_root_list:
1986 board_root = os.path.abspath(board_root)
1987
Anas Nashif3ba1d432017-12-05 15:28:44 -05001988 debug(
1989 "Reading platform configuration files under %s..." %
1990 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001991 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1992 verbose("Found plaform configuration " + fn)
1993 try:
1994 platform = Platform(fn)
Anas Nashiff3d48e12018-07-24 08:14:42 -05001995 if platform.sanitycheck:
1996 self.platforms.append(platform)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001997 except RuntimeError as e:
1998 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001999 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002000
Anas Nashifa792a3d2017-04-04 18:47:49 -04002001 arches = []
2002 for p in self.platforms:
2003 arches.append(p.arch)
2004 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002005 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04002006 arch = Architecture(a, aplatforms)
2007 self.arches[a] = arch
2008
Andrew Boie6acbe632015-07-17 12:03:52 -07002009 self.instances = {}
2010
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002011 def get_platform(self, name):
2012 selected_platform = None
2013 for platform in self.platforms:
2014 if platform.name == name:
2015 selected_platform = platform
2016 break
2017 return selected_platform
Anas Nashifb4bdd662018-08-15 17:12:28 -05002018
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002019 def get_last_failed(self):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002020 try:
2021 if not os.path.exists(LAST_SANITY):
2022 raise SanityRuntimeError("Couldn't find last sanity run.")
2023 except Exception as e:
2024 print(str(e))
2025 sys.exit(2)
2026
Andrew Boie6acbe632015-07-17 12:03:52 -07002027 result = []
2028 with open(LAST_SANITY, "r") as fp:
2029 cr = csv.DictReader(fp)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002030 instance_list = []
Andrew Boie6acbe632015-07-17 12:03:52 -07002031 for row in cr:
2032 if row["passed"] == "True":
2033 continue
2034 test = row["test"]
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002035 platform = self.get_platform(row["platform"])
2036 instance = TestInstance(self.testcases[test], platform, self.outdir)
2037 instance.create_overlay(platform.name)
2038 instance_list.append(instance)
2039 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07002040
Anas Nashifbd166f42017-09-02 12:32:08 -04002041 def load_from_file(self, file):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002042 try:
2043 if not os.path.exists(file):
2044 raise SanityRuntimeError(
2045 "Couldn't find input file with list of tests.")
2046 except Exception as e:
2047 print(str(e))
2048 sys.exit(2)
2049
Anas Nashifbd166f42017-09-02 12:32:08 -04002050 with open(file, "r") as fp:
2051 cr = csv.reader(fp)
2052 instance_list = []
2053 for row in cr:
2054 name = os.path.join(row[0], row[1])
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002055 platform = self.get_platform(row[2])
2056 instance = TestInstance(self.testcases[name], platform, self.outdir)
2057 instance.create_overlay(platform.name)
Anas Nashifbd166f42017-09-02 12:32:08 -04002058 instance_list.append(instance)
2059 self.add_instances(instance_list)
2060
Anas Nashif4f028882017-12-30 11:48:43 -05002061 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04002062
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002063 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2064 os.environ.get("ZEPHYR_GCC_VARIANT", None)
Anas Nashifb4bdd662018-08-15 17:12:28 -05002065
Sebastian Bøe5681f872018-10-12 16:03:49 +02002066 if toolchain == "gccarmemb":
2067 # Remove this translation when gccarmemb is no longer supported.
2068 toolchain = "gnuarmemb"
Anas Nashifb4bdd662018-08-15 17:12:28 -05002069
2070 try:
2071 if not toolchain:
2072 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
2073 except Exception as e:
2074 print(str(e))
2075 sys.exit(2)
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002076
2077
Andrew Boie6acbe632015-07-17 12:03:52 -07002078 instances = []
2079 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05002080 platform_filter = options.platform
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002081 testcase_filter = run_individual_tests
Anas Nashif4f028882017-12-30 11:48:43 -05002082 arch_filter = options.arch
2083 tag_filter = options.tag
2084 exclude_tag = options.exclude_tag
2085 config_filter = options.config
2086 extra_args = options.extra_args
2087 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04002088
Andrew Boie6acbe632015-07-17 12:03:52 -07002089 verbose("platform filter: " + str(platform_filter))
2090 verbose(" arch_filter: " + str(arch_filter))
2091 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04002092 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07002093 verbose(" config_filter: " + str(config_filter))
2094
Andrew Boie821d8322016-03-22 10:08:35 -07002095 default_platforms = False
2096
2097 if all_plats:
2098 info("Selecting all possible platforms per test case")
2099 # When --all used, any --platform arguments ignored
2100 platform_filter = []
2101 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07002102 info("Selecting default platforms per test case")
2103 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07002104
Sebastian Bøe781e3982017-11-09 11:43:33 +01002105 mg = MakeGenerator(self.outdir)
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002106 defconfig_list = {}
2107 dt_list = {}
Anas Nashif45a97862019-01-09 08:46:42 -05002108 cmake_list = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08002109 for tc_name, tc in self.testcases.items():
2110 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04002111 for plat in arch.platforms:
2112 instance = TestInstance(tc, plat, self.outdir)
2113
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002114 if (arch_name == "unit") != (tc.type == "unit"):
2115 continue
2116
Anas Nashifbfab06b2017-06-22 09:22:24 -04002117 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002118 platform_filter = []
2119
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002120 if tc.skip:
2121 continue
2122
Anas Nashif2cf0df02015-10-10 09:29:43 -04002123 if tag_filter and not tc.tags.intersection(tag_filter):
2124 continue
2125
Anas Nashifdfa86e22016-10-24 17:08:56 -04002126 if exclude_tag and tc.tags.intersection(exclude_tag):
2127 continue
2128
Anas Nashif2cf0df02015-10-10 09:29:43 -04002129 if testcase_filter and tc_name not in testcase_filter:
2130 continue
2131
Anas Nashif2cf0df02015-10-10 09:29:43 -04002132 if arch_filter and arch_name not in arch_filter:
2133 continue
2134
2135 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2136 continue
2137
2138 if tc.arch_exclude and arch.name in tc.arch_exclude:
2139 continue
2140
2141 if tc.platform_exclude and plat.name in tc.platform_exclude:
2142 continue
2143
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002144 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2145 continue
2146
Anas Nashif2cf0df02015-10-10 09:29:43 -04002147 if platform_filter and plat.name not in platform_filter:
2148 continue
2149
Anas Nashif62224182017-08-09 23:55:53 -04002150 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002151 continue
2152
2153 if set(plat.ignore_tags) & tc.tags:
2154 continue
2155
Kumar Gala5141d522017-07-07 08:05:48 -05002156 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002157 dep_intersection = tc.depends_on.intersection(
2158 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002159 if dep_intersection != set(tc.depends_on):
2160 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002161
2162 if plat.flash < tc.min_flash:
2163 continue
2164
Anas Nashif2cf0df02015-10-10 09:29:43 -04002165 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2166 continue
2167
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002168 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2169 continue
2170
Anas Nashif924a4e72018-10-18 12:25:55 -04002171 if (plat.env_satisfied and tc.tc_filter
2172 and (plat.default or all_plats or platform_filter)
Anas Nashif07d54c02018-07-21 19:29:08 -05002173 and (toolchain in plat.supported_toolchains or options.force_toolchain)):
Anas Nashif8ea9d022015-11-10 12:24:20 -05002174 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04002175 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07002176 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002177 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07002178 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07002179 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05002180 # each other since they all try to build them
2181 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04002182
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002183 o = os.path.join(self.outdir, plat.name, tc.name)
Anas Nashif45a97862019-01-09 08:46:42 -05002184 cmake_cache_path = os.path.join(o, "CMakeCache.txt")
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002185 generated_dt_confg = "include/generated/generated_dts_board.conf"
2186 dt_config_path = os.path.join(o, "zephyr", generated_dt_confg)
2187 dt_list[tc, plat, tc.name.split("/")[-1]] = dt_config_path
Anas Nashif45a97862019-01-09 08:46:42 -05002188 cmake_list[tc, plat, tc.name.split("/")[-1]] = cmake_cache_path
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002189 defconfig_list[tc, plat, tc.name.split("/")[-1]] = os.path.join(o, "zephyr", ".config")
Anas Nashif13773752018-07-06 18:20:23 -05002190 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002191 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.test_path),
Anas Nashif13773752018-07-06 18:20:23 -05002192 o, args, "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04002193
2194 info("Building testcase defconfigs...")
2195 results = mg.execute(defconfig_cb)
2196
Andrew Boie9e69c542019-04-09 12:14:59 -07002197 info("Filtering test cases...")
Andrew Boie08ce5a52016-02-22 13:28:10 -08002198 for name, goal in results.items():
Anas Nashifb4bdd662018-08-15 17:12:28 -05002199 try:
2200 if goal.failed:
2201 raise SanityRuntimeError("Couldn't build some defconfigs")
2202 except Exception as e:
2203 error(str(e))
2204 sys.exit(2)
2205
Anas Nashif2cf0df02015-10-10 09:29:43 -04002206
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002207 for k, out_config in defconfig_list.items():
Andrew Boie41878222016-11-03 11:58:53 -07002208 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04002209 defconfig = {}
2210 with open(out_config, "r") as fp:
2211 for line in fp.readlines():
2212 m = TestSuite.config_re.match(line)
2213 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07002214 if line.strip() and not line.startswith("#"):
2215 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002216 continue
2217 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07002218 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04002219
Anas Nashif45a97862019-01-09 08:46:42 -05002220 for k, cache_file in cmake_list.items():
2221 if not os.path.exists(out_config):
2222 continue
2223
2224 test, plat, name = k
2225 cmake_conf = {}
2226 try:
2227 cache = CMakeCache.from_file(cache_file)
2228 except FileNotFoundError:
2229 cache = {}
2230
2231 for k in iter(cache):
2232 cmake_conf[k.name] = k.value
2233
2234 test.cmake_cache[plat] = cmake_conf
2235
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002236 for k, out_config in dt_list.items():
2237 if not os.path.exists(out_config):
2238 continue
2239
2240 test, plat, name = k
2241 dt_conf = {}
2242 with open(out_config, "r") as fp:
2243 for line in fp.readlines():
2244 m = TestSuite.dt_re.match(line)
2245 if not m:
2246 if line.strip() and not line.startswith("#"):
2247 sys.stderr.write("Unrecognized line %s\n" % line)
2248 continue
2249 dt_conf[m.group(1)] = m.group(2).strip()
2250 test.dt_config[plat] = dt_conf
2251
Andrew Boie08ce5a52016-02-22 13:28:10 -08002252 for tc_name, tc in self.testcases.items():
2253 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002254 instance_list = []
2255 for plat in arch.platforms:
2256 instance = TestInstance(tc, plat, self.outdir)
2257
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002258 if (arch_name == "unit") != (tc.type == "unit"):
2259 # Discard silently
2260 continue
2261
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002262 if tc.skip:
2263 discards[instance] = "Skip filter"
2264 continue
2265
Anas Nashifbfab06b2017-06-22 09:22:24 -04002266 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002267 platform_filter = []
2268
Andrew Boie6acbe632015-07-17 12:03:52 -07002269 if tag_filter and not tc.tags.intersection(tag_filter):
2270 discards[instance] = "Command line testcase tag filter"
2271 continue
2272
Anas Nashifdfa86e22016-10-24 17:08:56 -04002273 if exclude_tag and tc.tags.intersection(exclude_tag):
2274 discards[instance] = "Command line testcase exclude filter"
2275 continue
2276
Andrew Boie6acbe632015-07-17 12:03:52 -07002277 if testcase_filter and tc_name not in testcase_filter:
2278 discards[instance] = "Testcase name filter"
2279 continue
2280
Andrew Boie6acbe632015-07-17 12:03:52 -07002281 if arch_filter and arch_name not in arch_filter:
2282 discards[instance] = "Command line testcase arch filter"
2283 continue
2284
2285 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2286 discards[instance] = "Not in test case arch whitelist"
2287 continue
2288
Anas Nashif30d13872015-10-05 10:02:45 -04002289 if tc.arch_exclude and arch.name in tc.arch_exclude:
2290 discards[instance] = "In test case arch exclude"
2291 continue
2292
2293 if tc.platform_exclude and plat.name in tc.platform_exclude:
2294 discards[instance] = "In test case platform exclude"
2295 continue
2296
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002297 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2298 discards[instance] = "In test case toolchain exclude"
2299 continue
2300
Andrew Boie6acbe632015-07-17 12:03:52 -07002301 if platform_filter and plat.name not in platform_filter:
2302 discards[instance] = "Command line platform filter"
2303 continue
2304
2305 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2306 discards[instance] = "Not in testcase platform whitelist"
2307 continue
2308
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002309 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2310 discards[instance] = "Not in testcase toolchain whitelist"
2311 continue
2312
Anas Nashif924a4e72018-10-18 12:25:55 -04002313 if not plat.env_satisfied:
2314 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2315 continue
2316
2317 if not options.force_toolchain \
2318 and toolchain and (toolchain not in plat.supported_toolchains) \
2319 and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05002320 discards[instance] = "Not supported by the toolchain"
2321 continue
2322
Anas Nashif62224182017-08-09 23:55:53 -04002323 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002324 discards[instance] = "Not enough RAM"
2325 continue
2326
Kumar Gala5141d522017-07-07 08:05:48 -05002327 if tc.depends_on:
Anas Nashif07d54c02018-07-21 19:29:08 -05002328 dep_intersection = tc.depends_on.intersection(set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002329 if dep_intersection != set(tc.depends_on):
2330 discards[instance] = "No hardware support"
2331 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002332
Anas Nashif62224182017-08-09 23:55:53 -04002333 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002334 discards[instance] = "Not enough FLASH"
2335 continue
2336
2337 if set(plat.ignore_tags) & tc.tags:
2338 discards[instance] = "Excluded tags per platform"
2339 continue
2340
Anas Nashif674bb282018-01-09 09:12:15 -05002341 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05002342 "ARCH": arch.name,
2343 "PLATFORM": plat.name
2344 }
Javier B Perez79414542016-08-08 12:24:59 -05002345 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002346 for p, tdefconfig in tc.defconfig.items():
2347 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002348 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002349 break
2350
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002351 for p, tdefconfig in tc.dt_config.items():
2352 if p == plat:
2353 defconfig.update(tdefconfig)
2354 break
2355
Anas Nashif45a97862019-01-09 08:46:42 -05002356 for p, tdefconfig in tc.cmake_cache.items():
2357 if p == plat:
2358 defconfig.update(tdefconfig)
2359 break
2360
Andrew Boie3ea78922016-03-24 14:46:00 -07002361 if tc.tc_filter:
2362 try:
2363 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002364 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002365 sys.stderr.write(
2366 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002367 raise se
2368 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002369 discards[instance] = (
2370 "defconfig doesn't satisfy expression '%s'" %
2371 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002372 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002373
Andrew Boie6acbe632015-07-17 12:03:52 -07002374 instance_list.append(instance)
2375
2376 if not instance_list:
2377 # Every platform in this arch was rejected already
2378 continue
2379
Anas Nashifa792a3d2017-04-04 18:47:49 -04002380 if default_platforms and not tc.build_on_all:
2381 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002382 instances = list(
2383 filter(
2384 lambda tc: tc.platform.default,
2385 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002386 self.add_instances(instances)
2387 else:
Anas Nashifab747062017-12-05 17:59:01 -05002388 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002389
Anas Nashif3ba1d432017-12-05 15:28:44 -05002390 for instance in list(
2391 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002392 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002393 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002394 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002395
2396 for name, case in self.instances.items():
Anas Nashifdbd76492018-11-23 20:24:19 -05002397 case.create_overlay(case.platform.name)
Anas Nashifab351f42018-04-08 08:57:48 -05002398
Andrew Boie6acbe632015-07-17 12:03:52 -07002399 self.discards = discards
Anas Nashif49b22d42019-06-14 13:45:34 -04002400
Andrew Boie6acbe632015-07-17 12:03:52 -07002401 return discards
2402
Andrew Boie821d8322016-03-22 10:08:35 -07002403 def add_instances(self, ti_list):
2404 for ti in ti_list:
2405 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002406
Anas Nashif37f9dc52018-02-23 08:53:46 -06002407 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002408
2409 def calc_one_elf_size(name, goal):
2410 if not goal.failed:
Alberto Escolar Piedrasb1045fe2018-07-14 13:11:02 +02002411 if self.instances[name].platform.type != "native":
2412 i = self.instances[name]
2413 sc = i.calculate_sizes()
2414 goal.metrics["ram_size"] = sc.get_ram_size()
2415 goal.metrics["rom_size"] = sc.get_rom_size()
2416 goal.metrics["unrecognized"] = sc.unrecognized_sections()
2417 else:
2418 goal.metrics["ram_size"] = 0
2419 goal.metrics["rom_size"] = 0
2420 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002421
Anas Nashif37f9dc52018-02-23 08:53:46 -06002422 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002423 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002424 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002425 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002426
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002427 if not options.disable_size_report and not options.cmake_only:
Andy Ross9c9162d2019-01-03 10:50:53 -08002428 # Parallelize size calculation
2429 executor = concurrent.futures.ThreadPoolExecutor(JOBS)
2430 futures = [executor.submit(calc_one_elf_size, name, goal)
2431 for name, goal in self.goals.items()]
2432 concurrent.futures.wait(futures)
2433 else:
2434 for goal in self.goals.values():
2435 goal.metrics["ram_size"] = 0
2436 goal.metrics["rom_size"] = 0
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002437 goal.metrics["handler_time"] = 0
Andy Ross9c9162d2019-01-03 10:50:53 -08002438 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002439
Andrew Boie6acbe632015-07-17 12:03:52 -07002440 return self.goals
2441
Marc Herbert682961a2019-03-20 16:48:49 -07002442 def save_tests(self, filename):
Anas Nashifbd166f42017-09-02 12:32:08 -04002443 with open(filename, "at") as csvfile:
2444 fieldnames = ['path', 'test', 'platform', 'arch']
2445 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2446 for instance in self.instances.values():
2447 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002448 "path": os.path.dirname(instance.test.name),
2449 "test": os.path.basename(instance.test.name),
2450 "platform": instance.platform.name,
2451 "arch": instance.platform.arch
2452 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002453 cw.writerow(rowdict)
2454
Andrew Boie6acbe632015-07-17 12:03:52 -07002455 def discard_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002456
2457 try:
2458 if self.discards is None:
2459 raise SanityRuntimeError("apply_filters() hasn't been run!")
2460 except Exception as e:
2461 error(str(e))
2462 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002463
Anas Nashifbd166f42017-09-02 12:32:08 -04002464 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002465 fieldnames = ["test", "arch", "platform", "reason"]
2466 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2467 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002468 for instance, reason in sorted(self.discards.items()):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002469 rowdict = {"test": instance.test.name,
2470 "arch": instance.platform.arch,
2471 "platform": instance.platform.name,
2472 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002473 cw.writerow(rowdict)
2474
2475 def compare_metrics(self, filename):
2476 # name, datatype, lower results better
2477 interesting_metrics = [("ram_size", int, True),
2478 ("rom_size", int, True)]
2479
Anas Nashifb4bdd662018-08-15 17:12:28 -05002480 try:
2481 if self.goals is None:
2482 raise SanityRuntimeError("execute() hasn't been run!")
2483 except Exception as e:
2484 print(str(e))
2485 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002486
2487 if not os.path.exists(filename):
2488 info("Cannot compare metrics, %s not found" % filename)
2489 return []
2490
2491 results = []
2492 saved_metrics = {}
2493 with open(filename) as fp:
2494 cr = csv.DictReader(fp)
2495 for row in cr:
2496 d = {}
2497 for m, _, _ in interesting_metrics:
2498 d[m] = row[m]
2499 saved_metrics[(row["test"], row["platform"])] = d
2500
Andrew Boie08ce5a52016-02-22 13:28:10 -08002501 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002502 i = self.instances[name]
2503 mkey = (i.test.name, i.platform.name)
2504 if mkey not in saved_metrics:
2505 continue
2506 sm = saved_metrics[mkey]
2507 for metric, mtype, lower_better in interesting_metrics:
2508 if metric not in goal.metrics:
2509 continue
2510 if sm[metric] == "":
2511 continue
2512 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002513 if delta == 0:
2514 continue
2515 results.append((i, metric, goal.metrics[metric], delta,
2516 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002517 return results
2518
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002519 def testcase_target_report(self, report_file):
2520
2521 run = "Sanitycheck"
2522 eleTestsuite = None
2523 append = options.only_failed
2524
2525 errors = 0
2526 passes = 0
2527 fails = 0
2528 duration = 0
2529 skips = 0
2530
2531 for identifier, ti in self.instances.items():
2532 for k in ti.results.keys():
2533 if ti.results[k] == 'PASS':
2534 passes += 1
2535 elif ti.results[k] == 'BLOCK':
2536 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002537 elif ti.results[k] == 'SKIP':
2538 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002539 else:
2540 fails += 1
2541
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002542 eleTestsuites = ET.Element('testsuites')
2543 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2544 name=run, time="%d" % duration,
2545 tests="%d" % (errors + passes + fails),
2546 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002547 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002548
2549 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002550
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002551 # print out test results
2552 for identifier, ti in self.instances.items():
2553 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002554
2555 eleTestcase = ET.SubElement(
2556 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002557 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002558 if ti.results[k] in ['FAIL', 'BLOCK']:
2559 el = None
2560
2561 if ti.results[k] == 'FAIL':
2562 el = ET.SubElement(
2563 eleTestcase,
2564 'failure',
2565 type="failure",
2566 message="failed")
2567 elif ti.results[k] == 'BLOCK':
2568 el = ET.SubElement(
2569 eleTestcase,
2570 'error',
2571 type="failure",
2572 message="failed")
2573 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
Anas Nashif366ed112019-03-31 20:36:30 -04002574 log_file = os.path.join(p, "handler.log")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002575
Anas Nashif366ed112019-03-31 20:36:30 -04002576 if os.path.exists(log_file):
2577 with open(log_file, "rb") as f:
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002578 log = f.read().decode("utf-8")
Anas Nashif366ed112019-03-31 20:36:30 -04002579 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2580 el.text = filtered_string
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002581
Anas Nashif61e21632018-04-08 13:30:16 -05002582 elif ti.results[k] == 'SKIP':
2583 el = ET.SubElement(
2584 eleTestcase,
Anas Nashif2c7636b2018-09-02 13:11:19 -04002585 'skipped',
2586 type="skipped",
2587 message="Skipped")
Anas Nashif61e21632018-04-08 13:30:16 -05002588
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002589 result = ET.tostring(eleTestsuites)
2590 f = open(report_file, 'wb')
2591 f.write(result)
2592 f.close()
2593
2594
Anas Nashif4f028882017-12-30 11:48:43 -05002595 def testcase_xunit_report(self, filename, duration):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002596 try:
2597 if self.goals is None:
2598 raise SanityRuntimeError("execute() hasn't been run!")
2599 except Exception as e:
2600 print(str(e))
2601 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002602
2603 fails = 0
2604 passes = 0
2605 errors = 0
2606
2607 for name, goal in self.goals.items():
2608 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002609 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002610 errors += 1
2611 else:
2612 fails += 1
2613 else:
2614 passes += 1
2615
2616 run = "Sanitycheck"
2617 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002618 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002619
Anas Nashif0605fa32017-05-07 08:51:02 -04002620 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002621 tree = ET.parse(filename)
2622 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002623 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002624 else:
2625 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002626 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2627 name=run, time="%d" % duration,
2628 tests="%d" % (errors + passes + fails),
2629 failures="%d" % fails,
2630 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002631
Anas Nashifc8390f12017-11-25 17:14:12 -05002632 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002633 for name, goal in self.goals.items():
2634
2635 i = self.instances[name]
2636 if append:
2637 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002638 if tc.get('classname') == "%s:%s" % (
2639 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002640 eleTestsuite.remove(tc)
2641
Anas Nashif4d25b502017-11-25 17:37:17 -05002642 if not goal.failed and goal.handler:
2643 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002644
Anas Nashif3ba1d432017-12-05 15:28:44 -05002645 eleTestcase = ET.SubElement(
2646 eleTestsuite, 'testcase', classname="%s:%s" %
2647 (i.platform.name, i.test.name), name="%s" %
2648 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002649 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002650 failure = ET.SubElement(
2651 eleTestcase,
2652 'failure',
2653 type="failure",
2654 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002655 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002656 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002657 hl = os.path.join(p, "handler.log")
2658 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002659 if goal.reason != 'build_error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002660 if os.path.exists(hl):
2661 log_file = hl
2662 else:
2663 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002664
Anas Nashifc96c90a2019-02-05 07:38:32 -05002665 if os.path.exists(log_file):
2666 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002667 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002668 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2669 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002670 f.close()
Anas Nashifb3311ed2017-04-13 14:44:48 -04002671
2672 result = ET.tostring(eleTestsuites)
2673 f = open(filename, 'wb')
2674 f.write(result)
2675 f.close()
2676
Andrew Boie6acbe632015-07-17 12:03:52 -07002677 def testcase_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002678 try:
2679 if self.goals is None:
2680 raise SanityRuntimeError("execute() hasn't been run!")
2681 except Exception as e:
2682 print(str(e))
2683 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002684
Andrew Boie08ce5a52016-02-22 13:28:10 -08002685 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002686 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002687 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002688 "rom_size"]
2689 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2690 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002691 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07002692 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002693 rowdict = {"test": i.test.name,
2694 "arch": i.platform.arch,
2695 "platform": i.platform.name,
2696 "extra_args": " ".join(i.test.extra_args),
2697 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002698 if goal.failed:
2699 rowdict["passed"] = False
2700 rowdict["status"] = goal.reason
2701 else:
2702 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002703 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002704 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002705 rowdict["ram_size"] = goal.metrics["ram_size"]
2706 rowdict["rom_size"] = goal.metrics["rom_size"]
2707 cw.writerow(rowdict)
2708
2709
2710def parse_arguments():
2711
Anas Nashif3ba1d432017-12-05 15:28:44 -05002712 parser = argparse.ArgumentParser(
2713 description=__doc__,
2714 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002715 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002716
Marc Herbert932a33a2019-03-12 11:37:53 -07002717 case_select = parser.add_argument_group("Test case selection",
2718 """
2719Artificially long but functional example:
2720 $ ./scripts/sanitycheck -v \\
2721 --testcase-root tests/ \\
2722 --testcase-root mytests/ \\
2723 --test tests/ztest/base/testing.ztest.verbose_0 \\
2724 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
2725
2726 "kernel.fifo.poll" is one of the test section names in
2727 __/fifo_api/testcase.yaml
2728 """)
Marc Herbertedf17592019-03-08 12:39:11 -08002729
Anas Nashif07d54c02018-07-21 19:29:08 -05002730 parser.add_argument("--force-toolchain", action="store_true",
2731 help="Do not filter based on toolchain, use the set "
2732 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002733 parser.add_argument(
2734 "-p", "--platform", action="append",
2735 help="Platform filter for testing. This option may be used multiple "
2736 "times. Testcases will only be built/run on the platforms "
2737 "specified. If this option is not used, then platforms marked "
2738 "as default in the platform metadata file will be chosen "
2739 "to build and test. ")
2740 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002741 "-a", "--arch", action="append",
2742 help="Arch filter for testing. Takes precedence over --platform. "
2743 "If unspecified, test all arches. Multiple invocations "
2744 "are treated as a logical 'or' relationship")
2745 parser.add_argument(
2746 "-t", "--tag", action="append",
2747 help="Specify tags to restrict which tests to run by tag value. "
2748 "Default is to not do any tag filtering. Multiple invocations "
2749 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002750 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002751 help="Specify tags of tests that should not run. "
2752 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08002753 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002754 "-f",
2755 "--only-failed",
2756 action="store_true",
2757 help="Run only those tests that failed the previous sanity check "
2758 "invocation.")
2759 parser.add_argument(
2760 "-c", "--config", action="append",
2761 help="Specify platform configuration values filtering. This can be "
2762 "specified two ways: <config>=<value> or just <config>. The "
2763 "defconfig for all platforms will be "
2764 "checked. For the <config>=<value> case, only match defconfig "
2765 "that have that value defined. For the <config> case, match "
2766 "defconfig that have that value assigned to any value. "
2767 "Prepend a '!' to invert the match.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002768
Marc Herbert0c465bb2019-03-11 17:28:36 -07002769 test_xor_subtest = case_select.add_mutually_exclusive_group()
2770
2771 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002772 "-s", "--test", action="append",
2773 help="Run only the specified test cases. These are named by "
Marc Herbert932a33a2019-03-12 11:37:53 -07002774 "path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002775
Marc Herbert0c465bb2019-03-11 17:28:36 -07002776 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002777 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07002778 help="""Recursively find sub-test functions and run the entire
2779 test section where they were found, including all sibling test
2780 functions. Sub-tests are named by:
2781 section.name.in.testcase.yaml.function_name_without_test_prefix
2782 Example: kernel.fifo.poll.fifo_loop
2783 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002784
Anas Nashif3ba1d432017-12-05 15:28:44 -05002785 parser.add_argument(
2786 "-l", "--all", action="store_true",
2787 help="Build/test on all platforms. Any --platform arguments "
2788 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002789
Anas Nashif3ba1d432017-12-05 15:28:44 -05002790 parser.add_argument(
2791 "-o", "--testcase-report",
2792 help="Output a CSV spreadsheet containing results of the test run")
2793 parser.add_argument(
2794 "-d", "--discard-report",
2795 help="Output a CSV spreadsheet showing tests that were skipped "
2796 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002797 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002798 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002799
Anas Nashif3ba1d432017-12-05 15:28:44 -05002800 parser.add_argument(
2801 "-B", "--subset",
2802 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2803 "3/5 means run the 3rd fifth of the total. "
2804 "This option is useful when running a large number of tests on "
2805 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002806
2807 parser.add_argument(
2808 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002809 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002810
Anas Nashif3ba1d432017-12-05 15:28:44 -05002811 parser.add_argument(
2812 "-y", "--dry-run", action="store_true",
2813 help="Create the filtered list of test cases, but don't actually "
2814 "run them. Useful if you're just interested in "
2815 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002816
Anas Nashif75547e22018-02-24 08:32:14 -06002817 parser.add_argument("--list-tags", action="store_true",
2818 help="list all tags in selected tests")
2819
Anas Nashif49b22d42019-06-14 13:45:34 -04002820 parser.add_argument("--report-excluded",
2821 action="store_true",
2822 help="""List all tests that are never run based on current scope and
2823 coverage. If you are looking for accurate results, run this with
2824 --all, but this will take a while...""")
2825
Marc Herbertedf17592019-03-08 12:39:11 -08002826 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07002827 help="""List of all sub-test functions recursively found in
2828 all --testcase-root arguments. Note different sub-tests can share
2829 the same section name and come from different directories.
2830 The output is flattened and reports --sub-test names only,
2831 not their directories. For instance net.socket.getaddrinfo_ok
2832 and net.socket.fd_set belong to different directories.
2833 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002834
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002835 parser.add_argument("--export-tests", action="store",
2836 metavar="FILENAME",
2837 help="Export tests case meta-data to a file in CSV format.")
2838
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002839 parser.add_argument("--detailed-report",
2840 action="store",
2841 metavar="FILENAME",
2842 help="Generate a junit report with detailed testcase results.")
2843
Anas Nashif654ec5982019-04-11 08:38:21 -04002844 parser.add_argument("--timestamps",
2845 action="store_true",
2846 help="Print all messages with time stamps")
2847
Anas Nashif3ba1d432017-12-05 15:28:44 -05002848 parser.add_argument(
2849 "-r", "--release", action="store_true",
2850 help="Update the benchmark database with the results of this test "
2851 "run. Intended to be run by CI when tagging an official "
2852 "release. This database is used as a basis for comparison "
2853 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002854 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002855 help="Treat warning conditions as errors")
2856 parser.add_argument(
2857 "-v",
2858 "--verbose",
2859 action="count",
2860 default=0,
2861 help="Emit debugging information, call multiple times to increase "
2862 "verbosity")
2863 parser.add_argument(
2864 "-i", "--inline-logs", action="store_true",
2865 help="Upon test failure, print relevant log data to stdout "
2866 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002867 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002868 help="log also to file")
2869 parser.add_argument(
2870 "-m", "--last-metrics", action="store_true",
2871 help="Instead of comparing metrics from the last --release, "
2872 "compare with the results of the previous sanity check "
2873 "invocation")
2874 parser.add_argument(
2875 "-u",
2876 "--no-update",
2877 action="store_true",
2878 help="do not update the results of the last run of the sanity "
2879 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08002880 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002881 "-F",
2882 "--load-tests",
2883 metavar="FILENAME",
2884 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07002885 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002886
Marc Herbertedf17592019-03-08 12:39:11 -08002887 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002888 "-E",
2889 "--save-tests",
2890 metavar="FILENAME",
2891 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07002892 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002893
Andy Doancbecadd2019-02-08 10:19:10 -06002894 test_or_build = parser.add_mutually_exclusive_group()
2895 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002896 "-b", "--build-only", action="store_true",
2897 help="Only build the code, do not execute any of it in QEMU")
Andy Doancbecadd2019-02-08 10:19:10 -06002898 test_or_build.add_argument(
2899 "--test-only", action="store_true",
2900 help="""Only run device tests with current artifacts, do not build
2901 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002902 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002903 "--cmake-only", action="store_true",
2904 help="Test on device directly. Specify the serial device to "
2905 "use with the --device-serial option.")
2906
2907 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002908 "-j", "--jobs", type=int,
Oleg Zhurakivskyyf3bc9672018-08-17 18:31:38 +03002909 help="Number of jobs for building, defaults to number of CPU threads "
2910 "overcommited by factor 2")
Anas Nashif73440ea2018-02-19 10:57:03 -06002911
2912 parser.add_argument(
2913 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002914 help="Test on device directly. Specify the serial device to "
2915 "use with the --device-serial option.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002916
2917 parser.add_argument(
2918 "-X", "--fixture", action="append", default=[],
2919 help="Specify a fixture that a board might support")
Anas Nashif73440ea2018-02-19 10:57:03 -06002920 parser.add_argument(
2921 "--device-serial",
Anas Nashif333a3152018-05-24 14:35:33 -05002922 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002923 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002924 "--show-footprint", action="store_true",
2925 help="Show footprint statistics and deltas since last release."
2926 )
2927 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002928 "-H", "--footprint-threshold", type=float, default=5,
2929 help="When checking test case footprint sizes, warn the user if "
2930 "the new app size is greater then the specified percentage "
2931 "from the last release. Default is 5. 0 to warn on any "
2932 "increase on app size")
2933 parser.add_argument(
2934 "-D", "--all-deltas", action="store_true",
2935 help="Show all footprint deltas, positive or negative. Implies "
2936 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002937 parser.add_argument(
2938 "-O", "--outdir",
Anas Nashiff114a132018-11-20 11:51:34 -05002939 default="%s/sanity-out" % os.getcwd(),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002940 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05002941 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002942 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002943 parser.add_argument(
2944 "-n", "--no-clean", action="store_true",
2945 help="Do not delete the outdir before building. Will result in "
2946 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08002947 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002948 "-T", "--testcase-root", action="append", default=[],
2949 help="Base directory to recursively search for test cases. All "
2950 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07002951 "called multiple times. Defaults to the 'samples/' and "
2952 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002953
Anas Nashif3ba1d432017-12-05 15:28:44 -05002954 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2955 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002956
Anas Nashif3ba1d432017-12-05 15:28:44 -05002957 parser.add_argument(
2958 "-A", "--board-root", action="append", default=board_root_list,
2959 help="Directory to search for board configuration files. All .yaml "
2960 "files in the directory will be processed.")
2961 parser.add_argument(
2962 "-z", "--size", action="append",
2963 help="Don't run sanity checks. Instead, produce a report to "
2964 "stdout detailing RAM/ROM sizes on the specified filenames. "
2965 "All other command line arguments ignored.")
2966 parser.add_argument(
2967 "-S", "--enable-slow", action="store_true",
2968 help="Execute time-consuming test cases that have been marked "
2969 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01002970 parser.add_argument(
2971 "--disable-unrecognized-section-test", action="store_true",
2972 default=False,
2973 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07002974 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002975 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002976 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002977 parser.add_argument("--disable-asserts", action="store_false",
2978 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07002979 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05002980 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002981 help="Error on deprecation warnings.")
Andy Ross9c9162d2019-01-03 10:50:53 -08002982 parser.add_argument("--disable-size-report", action="store_true",
2983 help="Skip expensive computation of ram/rom segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002984
2985 parser.add_argument(
2986 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002987 help="""Extra CMake cache entries to define when building test cases.
2988 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01002989 prefixed with -D before being passed to CMake.
2990
2991 E.g
2992 "sanitycheck -x=USE_CCACHE=0"
2993 will translate to
2994 "cmake -DUSE_CCACHE=0"
2995
2996 which will ultimately disable ccache.
2997 """
2998 )
Andy Doan79c48842019-02-08 10:09:04 -06002999 parser.add_argument(
3000 "--west-flash", nargs='?', const=[],
3001 help="""Uses west instead of ninja or make to flash when running with
3002 --device-testing"
3003
3004 E.g
3005 sanitycheck --device-testing --device-serial /dev/ttyACM0 \
3006 --west-flash="--board-id=foobar"
3007 will translate to
3008 west flash -- --board-id=foobar
3009 """
3010 )
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003011 parser.add_argument("--gcov-tool", default="gcov",
3012 help="Path to the gcov tool. Default is gcov in the path.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003013
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003014 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003015 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003016
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003017 parser.add_argument("-C", "--coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003018 help="Generate coverage reports. Implies --enable_coverage")
Andrew Boie6acbe632015-07-17 12:03:52 -07003019
Anas Nashifdbd76492018-11-23 20:24:19 -05003020 coverage_platforms = ["native_posix", "nrf52_bsim"]
3021 parser.add_argument("--coverage-platform", action="append", default=coverage_platforms,
3022 help="Plarforms to run coverage reports on. "
3023 "This option may be used multiple times.")
3024
Andrew Boie6acbe632015-07-17 12:03:52 -07003025 return parser.parse_args()
3026
Anas Nashif3ba1d432017-12-05 15:28:44 -05003027
Andrew Boie6acbe632015-07-17 12:03:52 -07003028def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003029 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003030 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003031 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003032
3033 try:
3034 with open(filename) as fp:
3035 data = fp.read()
3036 except Exception as e:
3037 data = "Unable to read log data (%s)\n" % (str(e))
3038
3039 sys.stdout.write(data)
3040 if log_file:
3041 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003042 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003043 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003044 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003045
Anas Nashif3ba1d432017-12-05 15:28:44 -05003046
Andrew Boie6acbe632015-07-17 12:03:52 -07003047def terse_test_cb(instances, goals, goal):
3048 total_tests = len(goals)
3049 total_done = 0
3050 total_failed = 0
3051
Andrew Boie08ce5a52016-02-22 13:28:10 -08003052 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003053 if g.finished:
3054 total_done += 1
3055 if g.failed:
3056 total_failed += 1
3057
3058 if goal.failed:
3059 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05003060 info(
3061 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
3062 i.platform.name,
3063 i.test.name,
3064 COLOR_RED,
3065 COLOR_NORMAL,
Anas Nashif654ec5982019-04-11 08:38:21 -04003066 goal.reason), False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003067 log_info(goal.get_error_log())
Anas Nashif654ec5982019-04-11 08:38:21 -04003068 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003069
Anas Nashif3ba1d432017-12-05 15:28:44 -05003070 sys.stdout.write(
3071 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
3072 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
3073 int((float(total_done) / total_tests) * 100),
3074 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
3075 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07003076 sys.stdout.flush()
3077
Anas Nashif3ba1d432017-12-05 15:28:44 -05003078
Andrew Boie6acbe632015-07-17 12:03:52 -07003079def chatty_test_cb(instances, goals, goal):
3080 i = instances[goal.name]
3081
3082 if VERBOSE < 2 and not goal.finished:
3083 return
3084
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003085 total_tests = len(goals)
3086 total_tests_width = len(str(total_tests))
3087 total_done = 0
3088
3089 for k, g in goals.items():
3090 if g.finished:
3091 total_done += 1
3092
Andrew Boie6acbe632015-07-17 12:03:52 -07003093 if goal.failed:
3094 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
3095 elif goal.finished:
3096 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
3097 else:
3098 status = goal.make_state
3099
Anas Nashifd18ec532019-04-11 23:20:39 -04003100 if goal.handler:
3101 handler_type = goal.handler.type_str
Marc Herbert35dc9632019-06-20 10:00:20 -07003102 htime = goal.metrics.get("handler_time", None)
3103 if htime:
3104 handler_type += " {:.3f}s".format(htime)
Anas Nashifd18ec532019-04-11 23:20:39 -04003105 else:
3106 handler_type = "build"
3107
3108 info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003109 total_done, total_tests_width, total_tests, i.platform.name,
Anas Nashifd18ec532019-04-11 23:20:39 -04003110 i.test.name, status, handler_type))
Andrew Boie6acbe632015-07-17 12:03:52 -07003111 if goal.failed:
3112 log_info(goal.get_error_log())
3113
Andrew Boiebbd670c2015-08-17 13:16:11 -07003114
3115def size_report(sc):
3116 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07003117 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003118 for i in range(len(sc.sections)):
3119 v = sc.sections[i]
3120
Andrew Boie73b4ee62015-10-07 11:33:22 -07003121 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
3122 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3123 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003124
Andrew Boie73b4ee62015-10-07 11:33:22 -07003125 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003126 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003127 info("")
3128
Anas Nashiff29087e2019-01-25 09:37:38 -05003129def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003130 if VERBOSE:
3131 print("Working on %s" %intput_file)
3132 extracted_coverage_info = {}
3133 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003134 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003135 with open(intput_file, 'r') as fp:
3136 for line in fp.readlines():
3137 if re.search("GCOV_COVERAGE_DUMP_START", line):
3138 capture_data = True
3139 continue
3140 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003141 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003142 break
3143 # Loop until the coverage data is found.
3144 if not capture_data:
3145 continue
3146 if line.startswith("*"):
3147 sp = line.split("<")
3148 if len(sp) > 1:
3149 # Remove the leading delimiter "*"
3150 file_name = sp[0][1:]
3151 # Remove the trailing new line char
3152 hex_dump = sp[1][:-1]
3153 else:
3154 continue
3155 else:
3156 continue
3157 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003158 if not capture_data:
3159 capture_complete = True
3160 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003161
3162def create_gcda_files(extracted_coverage_info):
3163 if VERBOSE:
3164 print("Generating gcda files")
3165 for filename, hexdump_val in extracted_coverage_info.items():
3166 # if kobject_hash is given for coverage gcovr fails
3167 # hence skipping it problem only in gcovr v4.1
3168 if "kobject_hash" in filename:
3169 filename = (filename[:-4]) +"gcno"
3170 try:
3171 os.remove(filename)
3172 except:
3173 pass
3174 continue
3175
3176 with open(filename, 'wb') as fp:
3177 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003178
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003179def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003180
3181 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003182 gcov_data = retrieve_gcov_data(filename)
3183 capture_complete = gcov_data['complete']
3184 extracted_coverage_info = gcov_data['data']
3185 if capture_complete:
3186 create_gcda_files(extracted_coverage_info)
3187 verbose("Gcov data captured: {}".format(filename))
3188 else:
3189 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003190
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003191 gcov_tool = options.gcov_tool
3192
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003193 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3194 coveragefile = os.path.join(outdir, "coverage.info")
3195 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003196 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3197 "--capture", "--directory", outdir,
3198 "--rc", "lcov_branch_coverage=1",
3199 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003200 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003201 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003202 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003203 "--output-file", ztestfile,
3204 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3205
Anas Nashif3cbffef2018-11-07 23:50:54 -05003206 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003207 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003208 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3209 "--output-file", ztestfile,
3210 "--rc", "lcov_branch_coverage=1"],
3211 stdout=coveragelog)
3212 files = [coveragefile, ztestfile];
3213 else:
3214 files = [coveragefile];
3215
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003216 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003217 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003218 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3219 coveragefile, i, "--output-file",
3220 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003221 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003222
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003223 #The --ignore-errors source option is added to avoid it exiting due to
3224 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003225 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003226 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003227 "-output-directory",
3228 os.path.join(outdir, "coverage")] + files,
3229 stdout=coveragelog)
3230 if ret==0:
3231 info("HTML report generated: %s"%
3232 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05003233
Andrew Boiebbd670c2015-08-17 13:16:11 -07003234
Andrew Boie6acbe632015-07-17 12:03:52 -07003235def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003236 start_time = time.time()
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003237 global VERBOSE, INLINE_LOGS, JOBS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003238 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003239 global run_individual_tests
Anas Nashife10b6512017-12-30 13:01:45 -05003240 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003241
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003242 if options.coverage:
3243 options.enable_coverage = True
3244
Anas Nashife10b6512017-12-30 13:01:45 -05003245 if options.size:
3246 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003247 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003248 sys.exit(0)
3249
Anas Nashif73440ea2018-02-19 10:57:03 -06003250
3251 if options.device_testing:
3252 if options.device_serial is None or len(options.platform) != 1:
3253 sys.exit(1)
3254
Anas Nashife10b6512017-12-30 13:01:45 -05003255 VERBOSE += options.verbose
3256 INLINE_LOGS = options.inline_logs
3257 if options.log_file:
3258 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003259
Anas Nashife10b6512017-12-30 13:01:45 -05003260 if options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003261 JOBS = options.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07003262
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003263 # Decrease JOBS for Ninja, if jobs weren't explicitly set
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003264 if options.ninja and not options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003265 JOBS = int(JOBS * 0.75)
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003266
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003267 info("JOBS: %d" % JOBS);
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003268
Anas Nashife10b6512017-12-30 13:01:45 -05003269 if options.subset:
3270 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003271 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003272 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003273 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003274 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003275 return
3276
Anas Nashife10b6512017-12-30 13:01:45 -05003277 if os.path.exists(options.outdir) and not options.no_clean:
3278 info("Cleaning output directory " + options.outdir)
3279 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003280
Anas Nashife10b6512017-12-30 13:01:45 -05003281 if not options.testcase_root:
3282 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003283 os.path.join(ZEPHYR_BASE, "samples")]
3284
Anas Nashif37f9dc52018-02-23 08:53:46 -06003285 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04003286
Kumar Galac84235e2018-04-10 13:32:51 -05003287 if ts.load_errors:
3288 sys.exit(1)
3289
Anas Nashif75547e22018-02-24 08:32:14 -06003290 if options.list_tags:
3291 tags = set()
3292 for n,tc in ts.testcases.items():
3293 tags = tags.union(tc.tags)
3294
3295 for t in tags:
3296 print("- {}".format(t))
3297
3298 return
3299
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003300
3301 def export_tests(filename, tests):
3302 with open(filename, "wt") as csvfile:
3303 fieldnames = ['section', 'subsection', 'title', 'reference']
3304 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3305 for test in tests:
3306 data = test.split(".")
3307 subsec = " ".join(data[1].split("_")).title()
3308 rowdict = {
3309 "section": data[0].capitalize(),
3310 "subsection": subsec,
3311 "title": test,
3312 "reference": test
3313 }
3314 cw.writerow(rowdict)
3315
3316 if options.export_tests:
3317 cnt = 0
3318 unq = []
3319 for n,tc in ts.testcases.items():
3320 for c in tc.cases:
3321 unq.append(c)
3322
3323 tests = sorted(set(unq))
3324 export_tests(options.export_tests, tests)
3325 return
3326
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003327 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003328
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003329 if options.test:
3330 run_individual_tests = options.test
3331
Anas Nashif49b22d42019-06-14 13:45:34 -04003332
3333 def get_unique_tests(ts):
Anas Nashifa3abe962018-05-05 19:10:22 -05003334 unq = []
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003335 run_individual_tests = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05003336 for n,tc in ts.testcases.items():
3337 for c in tc.cases:
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003338 if options.sub_test and c in options.sub_test:
3339 if tc.name not in run_individual_tests:
3340 run_individual_tests.append(tc.name)
Anas Nashifa3abe962018-05-05 19:10:22 -05003341 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003342
Anas Nashif49b22d42019-06-14 13:45:34 -04003343 return unq
3344
3345 if options.list_tests or options.sub_test:
3346 cnt = 0
3347 unq = get_unique_tests(ts)
3348
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003349 if options.sub_test:
3350 if run_individual_tests:
3351 info("Running the following tests:")
3352 for t in run_individual_tests:
3353 print(" - {}".format(t))
3354 else:
3355 info("Tests not found")
3356 return
3357
3358 elif options.list_tests:
3359 for u in sorted(set(unq)):
3360 cnt = cnt + 1
3361 print(" - {}".format(u))
3362 print("{} total.".format(cnt))
3363 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003364
Anas Nashifbd166f42017-09-02 12:32:08 -04003365 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05003366 if options.load_tests:
3367 ts.load_from_file(options.load_tests)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05003368 elif options.only_failed:
3369 ts.get_last_failed()
Anas Nashifbd166f42017-09-02 12:32:08 -04003370 else:
Anas Nashif4f028882017-12-30 11:48:43 -05003371 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003372
Anas Nashif49b22d42019-06-14 13:45:34 -04003373
Anas Nashife10b6512017-12-30 13:01:45 -05003374 if options.discard_report:
3375 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07003376
Anas Nashif30551f42018-01-12 21:56:59 -05003377 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003378 # if we are using command line platform filter, no need to list every
3379 # other platform as excluded, we know that already.
3380 # Show only the discards that apply to the selected platforms on the
3381 # command line
3382
Andrew Boie08ce5a52016-02-22 13:28:10 -08003383 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003384 if options.platform and i.platform.name not in options.platform:
3385 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003386 debug(
3387 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3388 i.platform.name,
3389 i.test.name,
3390 COLOR_YELLOW,
3391 COLOR_NORMAL,
3392 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003393
Anas Nashif1a5bba72018-01-05 08:07:45 -05003394
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003395 def native_and_unit_first(a, b):
3396 if a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003397 return -1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003398 if b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003399 return 1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003400 if a[0].startswith('native_posix'):
3401 return -1
3402 if b[0].startswith('native_posix'):
3403 return 1
3404 if a[0].split("/",1)[0].endswith("_bsim"):
3405 return -1
3406 if b[0].split("/",1)[0].endswith("_bsim"):
3407 return 1
3408
Anas Nashif1a5bba72018-01-05 08:07:45 -05003409 return (a > b) - (a < b)
3410
3411 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003412 key=cmp_to_key(native_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04003413
Anas Nashif49b22d42019-06-14 13:45:34 -04003414
3415
3416 if options.report_excluded:
3417 all_tests = set(get_unique_tests(ts))
3418 to_be_run = set()
3419 for i,p in ts.instances.items():
3420 to_be_run.update(p.test.cases)
3421
3422 if (all_tests - to_be_run):
3423 print("Tests that never build or run:")
3424 for not_run in (all_tests - to_be_run):
3425 print("- {}".format(not_run))
3426
3427 return
3428
3429
Anas Nashife10b6512017-12-30 13:01:45 -05003430 if options.save_tests:
Marc Herbert682961a2019-03-20 16:48:49 -07003431 ts.save_tests(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04003432 return
3433
Anas Nashife10b6512017-12-30 13:01:45 -05003434 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05003435
Anas Nashife10b6512017-12-30 13:01:45 -05003436 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04003437 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003438 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003439 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003440 if subset == sets:
3441 end = total
3442 else:
3443 end = start + per_set
3444
Anas Nashif3ba1d432017-12-05 15:28:44 -05003445 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04003446 ts.instances = OrderedDict(sliced_instances)
3447
Andrew Boie6acbe632015-07-17 12:03:52 -07003448 info("%d tests selected, %d tests discarded due to filters" %
3449 (len(ts.instances), len(discards)))
3450
Anas Nashife10b6512017-12-30 13:01:45 -05003451 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07003452 return
3453
3454 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003455 goals = ts.execute(
3456 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003457 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07003458 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003459 goals = ts.execute(
3460 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003461 ts.instances)
Anas Nashif654ec5982019-04-11 08:38:21 -04003462 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003463
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003464 if options.detailed_report:
3465 ts.testcase_target_report(options.detailed_report)
3466
Daniel Leung7f850102016-04-08 11:07:32 -07003467 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05003468 if options.compare_report:
3469 report_to_use = options.compare_report
3470 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07003471 report_to_use = LAST_SANITY
3472 else:
3473 report_to_use = RELEASE_DATA
3474
3475 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07003476 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06003477 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07003478 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05003479 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07003480 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07003481 continue
3482
Andrew Boieea7928f2015-08-14 14:27:38 -07003483 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05003484 if not options.all_deltas and (percentage <
3485 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07003486 continue
3487
Daniel Leung00525c22016-04-11 10:27:56 -07003488 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07003489 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05003490 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07003491 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07003492 warnings += 1
3493
3494 if warnings:
3495 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05003496 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07003497
3498 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08003499 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003500 if goal.failed:
3501 failed += 1
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003502 elif goal.metrics.get("unrecognized") and not options.disable_unrecognized_section_test:
Andrew Boie73b4ee62015-10-07 11:33:22 -07003503 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
3504 (COLOR_RED, COLOR_NORMAL, goal.name,
3505 str(goal.metrics["unrecognized"])))
3506 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07003507
Anas Nashife10b6512017-12-30 13:01:45 -05003508 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003509 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003510 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003511
Anas Nashif0605fa32017-05-07 08:51:02 -04003512 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07003513 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003514 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
3515 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
3516 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07003517
Anas Nashife10b6512017-12-30 13:01:45 -05003518 if options.testcase_report:
3519 ts.testcase_report(options.testcase_report)
3520 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05003521 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07003522 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05003523 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07003524 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003525 if log_file:
3526 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05003527 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003528 sys.exit(1)
3529
Anas Nashif3ba1d432017-12-05 15:28:44 -05003530
Andrew Boie6acbe632015-07-17 12:03:52 -07003531if __name__ == "__main__":
3532 main()