blob: 25bf2a1a9318daac98e9efa96ba283c11f446ff8 [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
Marc Herberte5cedca2019-04-08 14:02:34 -07006Also check the "User and Developer Guides" at https://docs.zephyrproject.org/
7
Andrew Boie6acbe632015-07-17 12:03:52 -07008This script scans for the set of unit test applications in the git
9repository and attempts to execute them. By default, it tries to
10build each test case on one platform per architecture, using a precedence
Paul Sokolovskyff70add2017-06-16 01:31:54 +030011list defined in an architecture configuration file, and if possible
Anas Nashif83fc06a2019-06-22 11:04:10 -040012run the tests in any available emulators or simulators on the system.
Andrew Boie6acbe632015-07-17 12:03:52 -070013
Anas Nashifa792a3d2017-04-04 18:47:49 -040014Test cases are detected by the presence of a 'testcase.yaml' or a sample.yaml
15files in the application's project directory. This file may contain one or more
16blocks, each identifying a test scenario. The title of the block is a name for
17the test case, which only needs to be unique for the test cases specified in
18that testcase meta-data. The full canonical name for each test case is <path to
19test case>/<block>.
Andrew Boie6acbe632015-07-17 12:03:52 -070020
Anas Nashif3ba1d432017-12-05 15:28:44 -050021Each test block in the testcase meta data can define the following key/value
22pairs:
Andrew Boie6acbe632015-07-17 12:03:52 -070023
Anas Nashiffa695d22017-10-04 16:14:27 -040024 tags: <list of tags> (required)
Andrew Boie6acbe632015-07-17 12:03:52 -070025 A set of string tags for the testcase. Usually pertains to
26 functional domains but can be anything. Command line invocations
27 of this script can filter the set of tests to run based on tag.
28
Anas Nashifa792a3d2017-04-04 18:47:49 -040029 skip: <True|False> (default False)
Anas Nashif2bd99bc2015-10-12 13:10:57 -040030 skip testcase unconditionally. This can be used for broken tests.
31
Anas Nashifa792a3d2017-04-04 18:47:49 -040032 slow: <True|False> (default False)
Andy Rossdc4151f2019-01-03 14:17:43 -080033 Don't build or run this test case unless --enable-slow was passed
34 in on the command line. Intended for time-consuming test cases
35 that are only run under certain circumstances, like daily
36 builds.
Andrew Boie6bb087c2016-02-10 13:39:00 -080037
Anas Nashifa792a3d2017-04-04 18:47:49 -040038 extra_args: <list of extra arguments>
Sebastian Bøec2182612017-11-09 12:25:02 +010039 Extra cache entries to pass to CMake when building or running the
Andrew Boie6acbe632015-07-17 12:03:52 -070040 test case.
41
Anas Nashifebc329d2017-10-17 09:00:33 -040042 extra_configs: <list of extra configurations>
43 Extra configuration options to be merged with a master prj.conf
44 when building or running the test case.
45
Anas Nashifa792a3d2017-04-04 18:47:49 -040046 build_only: <True|False> (default False)
Marc Herberte5cedca2019-04-08 14:02:34 -070047 If true, don't try to run the test even if the selected platform
48 supports it.
Andrew Boie6acbe632015-07-17 12:03:52 -070049
Anas Nashifa792a3d2017-04-04 18:47:49 -040050 build_on_all: <True|False> (default False)
51 If true, attempt to build test on all available platforms.
52
53 depends_on: <list of features>
54 A board or platform can announce what features it supports, this option
55 will enable the test only those platforms that provide this feature.
56
57 min_ram: <integer>
58 minimum amount of RAM needed for this test to build and run. This is
59 compared with information provided by the board metadata.
60
61 min_flash: <integer>
62 minimum amount of ROM needed for this test to build and run. This is
63 compared with information provided by the board metadata.
64
65 timeout: <number of seconds>
Anas Nashif83fc06a2019-06-22 11:04:10 -040066 Length of time to run test in emulator before automatically killing it.
Andrew Boie6acbe632015-07-17 12:03:52 -070067 Default to 60 seconds.
68
Anas Nashifa792a3d2017-04-04 18:47:49 -040069 arch_whitelist: <list of arches, such as x86, arm, arc>
Andrew Boie6acbe632015-07-17 12:03:52 -070070 Set of architectures that this test case should only be run for.
71
Anas Nashifa792a3d2017-04-04 18:47:49 -040072 arch_exclude: <list of arches, such as x86, arm, arc>
Anas Nashif30d13872015-10-05 10:02:45 -040073 Set of architectures that this test case should not run on.
74
Anas Nashifa792a3d2017-04-04 18:47:49 -040075 platform_whitelist: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040076 Set of platforms that this test case should only be run for.
77
Anas Nashifa792a3d2017-04-04 18:47:49 -040078 platform_exclude: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040079 Set of platforms that this test case should not run on.
Andrew Boie6acbe632015-07-17 12:03:52 -070080
Anas Nashifa792a3d2017-04-04 18:47:49 -040081 extra_sections: <list of extra binary sections>
Andrew Boie52fef672016-11-29 12:21:59 -080082 When computing sizes, sanitycheck will report errors if it finds
83 extra, unexpected sections in the Zephyr binary unless they are named
84 here. They will not be included in the size calculation.
85
Anas Nashifa792a3d2017-04-04 18:47:49 -040086 filter: <expression>
Andrew Boie3ea78922016-03-24 14:46:00 -070087 Filter whether the testcase should be run by evaluating an expression
88 against an environment containing the following values:
89
90 { ARCH : <architecture>,
91 PLATFORM : <platform>,
Javier B Perez79414542016-08-08 12:24:59 -050092 <all CONFIG_* key/value pairs in the test's generated defconfig>,
Anas Nashif45a97862019-01-09 08:46:42 -050093 <all DT_* key/value pairs in the test's generated device tree file>,
94 <all CMake key/value pairs in the test's generated CMakeCache.txt file>,
Javier B Perez79414542016-08-08 12:24:59 -050095 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070096 }
97
98 The grammar for the expression language is as follows:
99
100 expression ::= expression "and" expression
101 | expression "or" expression
102 | "not" expression
103 | "(" expression ")"
104 | symbol "==" constant
105 | symbol "!=" constant
106 | symbol "<" number
107 | symbol ">" number
108 | symbol ">=" number
109 | symbol "<=" number
110 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700111 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -0700112 | symbol
113
114 list ::= "[" list_contents "]"
115
116 list_contents ::= constant
117 | list_contents "," constant
118
119 constant ::= number
120 | string
121
122
123 For the case where expression ::= symbol, it evaluates to true
124 if the symbol is defined to a non-empty string.
125
126 Operator precedence, starting from lowest to highest:
127
128 or (left associative)
129 and (left associative)
130 not (right associative)
131 all comparison operators (non-associative)
132
133 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
134 are all syntactic sugar for these expressions. For instance
135
136 arch_exclude = x86 arc
137
138 Is the same as:
139
140 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700141
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700142 The ':' operator compiles the string argument as a regular expression,
143 and then returns a true value only if the symbol's value in the environment
Anas Nashif578ae402019-07-12 07:54:35 -0700144 matches. For example, if CONFIG_SOC="stm32f107xc" then
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700145
Anas Nashif578ae402019-07-12 07:54:35 -0700146 filter = CONFIG_SOC : "stm.*"
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700147
148 Would match it.
149
Anas Nashifa792a3d2017-04-04 18:47:49 -0400150The set of test cases that actually run depends on directives in the testcase
151filed and options passed in on the command line. If there is any confusion,
152running with -v or --discard-report can help show why particular test cases
153were skipped.
Andrew Boie6acbe632015-07-17 12:03:52 -0700154
155Metrics (such as pass/fail state and binary size) for the last code
156release are stored in scripts/sanity_chk/sanity_last_release.csv.
157To update this, pass the --all --release options.
158
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500159To load arguments from a file, write '+' before the file name, e.g.,
160+file_name. File content must be one or more valid arguments separated by
161line break instead of white spaces.
162
Andrew Boie6acbe632015-07-17 12:03:52 -0700163Most everyday users will run with no arguments.
Andy Rossdc4151f2019-01-03 14:17:43 -0800164
Andrew Boie6acbe632015-07-17 12:03:52 -0700165"""
166
Sebastian Bøe56d74712019-01-21 15:48:46 +0100167import os
Sebastian Bøe56d74712019-01-21 15:48:46 +0100168if os.name == 'nt':
169 print("Running sanitycheck on Windows is not supported yet.")
170 print("https://github.com/zephyrproject-rtos/zephyr/issues/2664")
171 exit(1)
172
Anas Nashifaae71d72018-04-21 22:26:48 -0500173import contextlib
Anas Nashifa4c368e2018-10-15 09:45:59 -0400174import string
Anas Nashifaae71d72018-04-21 22:26:48 -0500175import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700176import argparse
Andrew Boie6acbe632015-07-17 12:03:52 -0700177import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700178import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700179import subprocess
180import multiprocessing
181import select
182import shutil
Marc Herbertaf1090c2019-04-30 14:11:29 -0700183import shlex
Andrew Boie6acbe632015-07-17 12:03:52 -0700184import signal
185import threading
Anas Nashif83fc06a2019-06-22 11:04:10 -0400186import concurrent.futures
187from threading import BoundedSemaphore
188import queue
Andrew Boie6acbe632015-07-17 12:03:52 -0700189import time
Anas Nashif654ec5982019-04-11 08:38:21 -0400190import datetime
Andrew Boie6acbe632015-07-17 12:03:52 -0700191import csv
Anas Nashif83fc06a2019-06-22 11:04:10 -0400192import yaml
Andrew Boie5d4eb782015-10-02 10:04:56 -0700193import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600194import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700195import concurrent
Anas Nashifb3311ed2017-04-13 14:44:48 -0400196import xml.etree.ElementTree as ET
Anas Nashif035799f2017-05-13 21:31:53 -0400197from collections import OrderedDict
198from itertools import islice
Anas Nashife24350c2018-07-11 15:09:22 -0500199from pathlib import Path
200from distutils.spawn import find_executable
Andrew Boie6acbe632015-07-17 12:03:52 -0700201
Kumar Gala7733b942019-09-12 17:08:43 -0500202ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
203if not ZEPHYR_BASE:
204 sys.exit("$ZEPHYR_BASE environment variable undefined")
205
206sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "dts"))
207import edtlib
208
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700209import logging
Anas Nashif83fc06a2019-06-22 11:04:10 -0400210
211
212hw_map_local = threading.Lock()
Anas Nashifc1ea4522019-10-11 07:32:45 -0700213report_lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400214
Anas Nashif3ba1d432017-12-05 15:28:44 -0500215
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700216log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500217logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700218
Marc Herbert1c8632c2019-04-15 17:58:45 -0700219# Use this for internal comparisons; that's what canonicalization is
220# for. Don't use it when invoking other components of the build system
221# to avoid confusing and hard to trace inconsistencies in error messages
222# and logs, generated Makefiles, etc. compared to when users invoke these
223# components directly.
224# Note "normalization" is different from canonicalization, see os.path.
225canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
226
Andrew Boie3ea78922016-03-24 14:46:00 -0700227sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
228
Anas Nashif83fc06a2019-06-22 11:04:10 -0400229from sanity_chk import scl
230from sanity_chk import expr_parser
231
Andrew Boie3ea78922016-03-24 14:46:00 -0700232
Andrew Boie6acbe632015-07-17 12:03:52 -0700233VERBOSE = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400234
Andrew Boie6acbe632015-07-17 12:03:52 -0700235RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
236 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700237
238if os.isatty(sys.stdout.fileno()):
239 TERMINAL = True
240 COLOR_NORMAL = '\033[0m'
241 COLOR_RED = '\033[91m'
242 COLOR_GREEN = '\033[92m'
243 COLOR_YELLOW = '\033[93m'
244else:
245 TERMINAL = False
246 COLOR_NORMAL = ""
247 COLOR_RED = ""
248 COLOR_GREEN = ""
249 COLOR_YELLOW = ""
250
Anas Nashif45a97862019-01-09 08:46:42 -0500251class CMakeCacheEntry:
252 '''Represents a CMake cache entry.
253
254 This class understands the type system in a CMakeCache.txt, and
255 converts the following cache types to Python types:
256
257 Cache Type Python type
258 ---------- -------------------------------------------
259 FILEPATH str
260 PATH str
261 STRING str OR list of str (if ';' is in the value)
262 BOOL bool
263 INTERNAL str OR list of str (if ';' is in the value)
264 ---------- -------------------------------------------
265 '''
266
267 # Regular expression for a cache entry.
268 #
269 # CMake variable names can include escape characters, allowing a
270 # wider set of names than is easy to match with a regular
271 # expression. To be permissive here, use a non-greedy match up to
272 # the first colon (':'). This breaks if the variable name has a
273 # colon inside, but it's good enough.
274 CACHE_ENTRY = re.compile(
275 r'''(?P<name>.*?) # name
276 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
277 =(?P<value>.*) # value
278 ''', re.X)
279
280 @classmethod
281 def _to_bool(cls, val):
282 # Convert a CMake BOOL string into a Python bool.
283 #
284 # "True if the constant is 1, ON, YES, TRUE, Y, or a
285 # non-zero number. False if the constant is 0, OFF, NO,
286 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
287 # the suffix -NOTFOUND. Named boolean constants are
288 # case-insensitive. If the argument is not one of these
289 # constants, it is treated as a variable."
290 #
291 # https://cmake.org/cmake/help/v3.0/command/if.html
292 val = val.upper()
293 if val in ('ON', 'YES', 'TRUE', 'Y'):
294 return 1
295 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
296 return 0
297 elif val.endswith('-NOTFOUND'):
298 return 0
299 else:
300 try:
301 v = int(val)
302 return v != 0
303 except ValueError as exc:
304 raise ValueError('invalid bool {}'.format(val)) from exc
305
306 @classmethod
307 def from_line(cls, line, line_no):
308 # Comments can only occur at the beginning of a line.
309 # (The value of an entry could contain a comment character).
310 if line.startswith('//') or line.startswith('#'):
311 return None
312
313 # Whitespace-only lines do not contain cache entries.
314 if not line.strip():
315 return None
316
317 m = cls.CACHE_ENTRY.match(line)
318 if not m:
319 return None
320
321 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
322 if type_ == 'BOOL':
323 try:
324 value = cls._to_bool(value)
325 except ValueError as exc:
326 args = exc.args + ('on line {}: {}'.format(line_no, line),)
327 raise ValueError(args) from exc
Anas Nashif83fc06a2019-06-22 11:04:10 -0400328 elif type_ in ['STRING','INTERNAL']:
Anas Nashif45a97862019-01-09 08:46:42 -0500329 # If the value is a CMake list (i.e. is a string which
330 # contains a ';'), convert to a Python list.
331 if ';' in value:
332 value = value.split(';')
333
334 return CMakeCacheEntry(name, value)
335
336 def __init__(self, name, value):
337 self.name = name
338 self.value = value
339
340 def __str__(self):
341 fmt = 'CMakeCacheEntry(name={}, value={})'
342 return fmt.format(self.name, self.value)
343
344
345class CMakeCache:
346 '''Parses and represents a CMake cache file.'''
347
348 @staticmethod
349 def from_file(cache_file):
350 return CMakeCache(cache_file)
351
352 def __init__(self, cache_file):
353 self.cache_file = cache_file
354 self.load(cache_file)
355
356 def load(self, cache_file):
357 entries = []
358 with open(cache_file, 'r') as cache:
359 for line_no, line in enumerate(cache):
360 entry = CMakeCacheEntry.from_line(line, line_no)
361 if entry:
362 entries.append(entry)
363 self._entries = OrderedDict((e.name, e) for e in entries)
364
365 def get(self, name, default=None):
366 entry = self._entries.get(name)
367 if entry is not None:
368 return entry.value
369 else:
370 return default
371
372 def get_list(self, name, default=None):
373 if default is None:
374 default = []
375 entry = self._entries.get(name)
376 if entry is not None:
377 value = entry.value
378 if isinstance(value, list):
379 return value
380 elif isinstance(value, str):
381 return [value] if value else []
382 else:
383 msg = 'invalid value {} type {}'
384 raise RuntimeError(msg.format(value, type(value)))
385 else:
386 return default
387
388 def __contains__(self, name):
389 return name in self._entries
390
391 def __getitem__(self, name):
392 return self._entries[name].value
393
394 def __setitem__(self, name, entry):
395 if not isinstance(entry, CMakeCacheEntry):
396 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
397 raise TypeError(msg.format(type(entry), entry))
398 self._entries[name] = entry
399
400 def __delitem__(self, name):
401 del self._entries[name]
402
403 def __iter__(self):
404 return iter(self._entries.values())
405
Andrew Boie6acbe632015-07-17 12:03:52 -0700406class SanityCheckException(Exception):
407 pass
408
Anas Nashif3ba1d432017-12-05 15:28:44 -0500409
Andrew Boie6acbe632015-07-17 12:03:52 -0700410class SanityRuntimeError(SanityCheckException):
411 pass
412
413class ConfigurationError(SanityCheckException):
414 def __init__(self, cfile, message):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400415 SanityCheckException.__init__(self, cfile + ": " + message)
Andrew Boie6acbe632015-07-17 12:03:52 -0700416
Anas Nashif83fc06a2019-06-22 11:04:10 -0400417class BuildError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700418 pass
419
Anas Nashif3ba1d432017-12-05 15:28:44 -0500420
Anas Nashif83fc06a2019-06-22 11:04:10 -0400421class ExecutionError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700422 pass
423
Anas Nashif3ba1d432017-12-05 15:28:44 -0500424
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800425log_file = None
426
Andrew Boie6acbe632015-07-17 12:03:52 -0700427# Debug Functions
Anas Nashif654ec5982019-04-11 08:38:21 -0400428def info(what, show_time=True):
429 if options.timestamps and show_time:
430 date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
431 what = "{}: {}".format(date, what)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800432 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300433 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800434 if log_file:
435 log_file.write(what + "\n")
436 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700437
Anas Nashif3ba1d432017-12-05 15:28:44 -0500438
Andrew Boie6acbe632015-07-17 12:03:52 -0700439def error(what):
Anas Nashif654ec5982019-04-11 08:38:21 -0400440 if options.timestamps:
441 date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
442 what = "{}: {}".format(date, what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700443 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800444 if log_file:
445 log_file(what + "\n")
446 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700447
Anas Nashif3ba1d432017-12-05 15:28:44 -0500448
Andrew Boie08ce5a52016-02-22 13:28:10 -0800449def debug(what):
450 if VERBOSE >= 1:
451 info(what)
452
Anas Nashif3ba1d432017-12-05 15:28:44 -0500453
Andrew Boie6acbe632015-07-17 12:03:52 -0700454def verbose(what):
455 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800456 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700457
Anas Nashif576be982017-12-23 20:20:27 -0500458class HarnessImporter:
459
460 def __init__(self, name):
461 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
462 module = __import__("harness")
463 if name:
464 my_class = getattr(module, name)
465 else:
466 my_class = getattr(module, "Test")
467
468 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500469
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300470class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400471 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300472 """Constructor
473
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300474 """
475 self.lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400476
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300477 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500478 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -0400479 self.duration = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400480 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300481
Anas Nashifdf7ee612018-07-07 06:09:01 -0500482 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100483 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500484 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500485
Anas Nashifd3384fb2018-02-22 06:44:16 -0600486 self.name = instance.name
487 self.instance = instance
Anas Nashif83fc06a2019-06-22 11:04:10 -0400488 self.timeout = instance.testcase.timeout
489 self.sourcedir = instance.testcase.source_dir
490 self.build_dir = instance.build_dir
491 self.log = os.path.join(self.build_dir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600492 self.returncode = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400493 self.set_state("running", self.duration)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600494
Anas Nashif83fc06a2019-06-22 11:04:10 -0400495 self.args = []
496
497 def set_state(self, state, duration):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300498 self.lock.acquire()
499 self.state = state
Anas Nashif83fc06a2019-06-22 11:04:10 -0400500 self.duration = duration
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300501 self.lock.release()
502
503 def get_state(self):
504 self.lock.acquire()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400505 ret = (self.state, self.duration)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300506 self.lock.release()
507 return ret
508
Anas Nashif83fc06a2019-06-22 11:04:10 -0400509 def record(self, harness):
510 if harness.recording:
511 filename = os.path.join(options.outdir,
512 self.instance.platform.name,
513 self.instance.testcase.name, "recording.csv")
514 with open(filename, "at") as csvfile:
515 cw = csv.writer(csvfile, harness.fieldnames, lineterminator=os.linesep)
516 cw.writerow(harness.fieldnames)
517 for instance in harness.recording:
518 cw.writerow(instance)
519
Anas Nashifdf7ee612018-07-07 06:09:01 -0500520class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400521 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500522 """Constructor
523
524 @param instance Test Instance
525 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400526 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500527
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100528 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500529
Jan Kowalewski265895b2019-01-07 16:40:24 +0100530 def try_kill_process_by_pid(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400531 if self.pid_fn:
Jan Kowalewski265895b2019-01-07 16:40:24 +0100532 pid = int(open(self.pid_fn).read())
533 os.unlink(self.pid_fn)
534 self.pid_fn = None # clear so we don't try to kill the binary twice
535 try:
536 os.kill(pid, signal.SIGTERM)
537 except ProcessLookupError:
538 pass
539
Anas Nashifdf7ee612018-07-07 06:09:01 -0500540 def _output_reader(self, proc, harness):
541 log_out_fp = open(self.log, "wt")
542 for line in iter(proc.stdout.readline, b''):
543 verbose("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
544 log_out_fp.write(line.decode('utf-8'))
545 log_out_fp.flush()
546 harness.handle(line.decode('utf-8').rstrip())
547 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100548 try:
549 #POSIX arch based ztests end on their own,
550 #so let's give it up to 100ms to do so
551 proc.wait(0.1)
552 except subprocess.TimeoutExpired:
553 proc.terminate()
554 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500555 break
556
557 log_out_fp.close()
558
559 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500560
Anas Nashif83fc06a2019-06-22 11:04:10 -0400561 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500562 harness_import = HarnessImporter(harness_name)
563 harness = harness_import.instance
564 harness.configure(self.instance)
565
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500566 if self.call_make_run:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400567 command = [get_generator()[0], "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500568 else:
569 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500570
Anas Nashifc1ea4522019-10-11 07:32:45 -0700571 run_valgrind = False
572 if options.enable_valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500573 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100574 "--leak-check=full",
575 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
Anas Nashif83fc06a2019-06-22 11:04:10 -0400576 "--log-file="+self.build_dir+"/valgrind.log"
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100577 ] + command
Anas Nashifc1ea4522019-10-11 07:32:45 -0700578 run_valgrind = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500579
Marc Herbertaf1090c2019-04-30 14:11:29 -0700580 verbose("Spawning process: " +
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100581 " ".join(shlex.quote(word) for word in command) + os.linesep +
Anas Nashif83fc06a2019-06-22 11:04:10 -0400582 "Spawning process in directory: " + self.build_dir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200583
Anas Nashif83fc06a2019-06-22 11:04:10 -0400584 start_time = time.time()
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200585
Anas Nashif89c83042019-11-05 05:55:39 -0800586 env = os.environ.copy()
Jan Van Winkel21212f32019-09-12 00:03:35 +0200587 if options.enable_asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200588 env["ASAN_OPTIONS"] = "log_path=stdout:" + \
589 env.get("ASAN_OPTIONS", "")
590 if not options.enable_lsan:
591 env["ASAN_OPTIONS"] += "detect_leaks=0"
592 with subprocess.Popen(command, stdout=subprocess.PIPE,
593 stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc:
Marc Herbertaf1090c2019-04-30 14:11:29 -0700594 verbose("Spawning BinaryHandler Thread for %s" % self.name)
Flavio Ceolin063ab902019-10-15 16:10:49 -0700595 t = threading.Thread(target=self._output_reader, args=(proc, harness, ), daemon=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500596 t.start()
597 t.join(self.timeout)
598 if t.is_alive():
Jan Kowalewski265895b2019-01-07 16:40:24 +0100599 self.try_kill_process_by_pid()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500600 proc.terminate()
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100601 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500602 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500603 proc.wait()
604 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500605
Anas Nashif83fc06a2019-06-22 11:04:10 -0400606 handler_time = time.time() - start_time
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200607
Anas Nashifdf7ee612018-07-07 06:09:01 -0500608 if options.enable_coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400609 subprocess.call(["GCOV_PREFIX=" + self.build_dir,
610 "gcov", self.sourcedir, "-b", "-s", self.build_dir], shell=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500611
Jan Kowalewski265895b2019-01-07 16:40:24 +0100612 self.try_kill_process_by_pid()
613
Anas Nashif83fc06a2019-06-22 11:04:10 -0400614 # FIXME: This is needed when killing the simulator, the console is
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500615 # garbled and needs to be reset. Did not find a better way to do that.
616
617 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500618 self.instance.results = harness.tests
Anas Nashifc1ea4522019-10-11 07:32:45 -0700619
Anas Nashif83fc06a2019-06-22 11:04:10 -0400620 if not self.terminated and self.returncode != 0:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100621 #When a process is killed, the default handler returns 128 + SIGTERM
622 #so in that case the return code itself is not meaningful
Anas Nashiff72d1902019-10-18 17:20:44 -0400623 self.set_state("failed", handler_time)
Anas Nashifc1ea4522019-10-11 07:32:45 -0700624 self.instance.reason = "Handler Error"
625 elif run_valgrind and self.returncode == 2:
626 self.set_state("failed", handler_time)
627 self.instance.reason = "Valgrind error"
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100628 elif harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400629 self.set_state(harness.state, handler_time)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500630 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400631 self.set_state("timeout", handler_time)
632 self.instance.reason = "Handler timeout"
633
Anas Nashifc1ea4522019-10-11 07:32:45 -0700634
Anas Nashif83fc06a2019-06-22 11:04:10 -0400635 self.record(harness)
Anas Nashif73440ea2018-02-19 10:57:03 -0600636
637class DeviceHandler(Handler):
638
Anas Nashifd18ec532019-04-11 23:20:39 -0400639 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600640 """Constructor
641
642 @param instance Test Instance
643 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400644 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600645
Anas Nashif83fc06a2019-06-22 11:04:10 -0400646 self.suite = None
647
Marti Bolivar5591ca22019-02-07 15:53:39 -0700648 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500649 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600650
Marti Bolivar5591ca22019-02-07 15:53:39 -0700651 ser_fileno = ser.fileno()
652 readlist = [halt_fileno, ser_fileno]
653
Anas Nashif73440ea2018-02-19 10:57:03 -0600654 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700655 readable, _, _ = select.select(readlist, [], [], self.timeout)
656
657 if halt_fileno in readable:
658 verbose('halted')
659 ser.close()
660 break
661 if ser_fileno not in readable:
662 continue # Timeout.
663
664 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500665 try:
666 serial_line = ser.readline()
667 except TypeError:
668 pass
Anas Nashif83fc06a2019-06-22 11:04:10 -0400669 except serial.SerialException:
Anas Nashif59237602019-03-03 10:36:35 -0500670 ser.close()
671 break
Anas Nashif61e21632018-04-08 13:30:16 -0500672
Marti Bolivar5591ca22019-02-07 15:53:39 -0700673 # Just because ser_fileno has data doesn't mean an entire line
674 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600675 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600676 sl = serial_line.decode('utf-8', 'ignore')
677 verbose("DEVICE: {0}".format(sl.rstrip()))
678
679 log_out_fp.write(sl)
680 log_out_fp.flush()
681 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700682
Anas Nashif73440ea2018-02-19 10:57:03 -0600683 if harness.state:
684 ser.close()
685 break
686
687 log_out_fp.close()
688
Anas Nashif83fc06a2019-06-22 11:04:10 -0400689 def device_is_available(self, device):
690 for i in self.suite.connected_hardware:
691 if i['platform'] == device and i['available'] and i['connected']:
692 return True
693
694 return False
695
696 def get_available_device(self, device):
697 for i in self.suite.connected_hardware:
698 if i['platform'] == device and i['available']:
699 i['available'] = False
700 i['counter'] += 1
701 return i
702
703 return None
704
705 def make_device_available(self, serial):
706 with hw_map_local:
707 for i in self.suite.connected_hardware:
708 if i['serial'] == serial:
709 i['available'] = True
710
Anas Nashif73440ea2018-02-19 10:57:03 -0600711 def handle(self):
712 out_state = "failed"
713
Anas Nashif83fc06a2019-06-22 11:04:10 -0400714 if options.west_flash:
715 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
716 if options.west_runner:
Michael Scott421ce462019-06-18 09:37:46 -0700717 command.append("--runner")
718 command.append(options.west_runner)
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200719 # There are three ways this option is used.
Andy Doan79c48842019-02-08 10:09:04 -0600720 # 1) bare: --west-flash
721 # This results in options.west_flash == []
722 # 2) with a value: --west-flash="--board-id=42"
723 # This results in options.west_flash == "--board-id=42"
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200724 # 3) Multiple values: --west-flash="--board-id=42,--erase"
725 # This results in options.west_flash == "--board-id=42 --erase"
Andy Doan79c48842019-02-08 10:09:04 -0600726 if options.west_flash != []:
727 command.append('--')
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200728 command.extend(options.west_flash.split(','))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600729 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400730 command = [get_generator()[0], "-C", self.build_dir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600731
Anas Nashifd3384fb2018-02-22 06:44:16 -0600732
Anas Nashif83fc06a2019-06-22 11:04:10 -0400733 while not self.device_is_available(self.instance.platform.name):
734 time.sleep(1)
735
736 hardware = self.get_available_device(self.instance.platform.name)
737
738 runner = hardware.get('runner', None)
739 if runner:
740 board_id = hardware.get("id", None)
741 product = hardware.get("product", None)
742 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
743 command.append("--runner")
744 command.append(hardware.get('runner', None))
745 if runner == "pyocd":
746 command.append("--board-id")
747 command.append(board_id)
748 elif runner == "nrfjprog":
749 command.append('--')
750 command.append("--snr")
751 command.append(board_id)
752 elif runner == "openocd" and product == "STM32 STLink":
753 command.append('--')
754 command.append("--cmd-pre-init")
755 command.append("hla_serial %s" %(board_id))
756 elif runner == "openocd" and product == "EDBG CMSIS-DAP":
757 command.append('--')
758 command.append("--cmd-pre-init")
759 command.append("cmsis_dap_serial %s" %(board_id))
760 elif runner == "jlink":
761 command.append("--tool-opt=-SelectEmuBySN %s" %(board_id))
762
763 serial_device = hardware['serial']
764
765 try:
766 ser = serial.Serial(
767 serial_device,
768 baudrate=115200,
769 parity=serial.PARITY_NONE,
770 stopbits=serial.STOPBITS_ONE,
771 bytesize=serial.EIGHTBITS,
772 timeout=self.timeout
773 )
774 except serial.SerialException as e:
775 self.set_state("failed", 0)
776 error("Serial device err: %s" %(str(e)))
777 self.make_device_available(serial_device)
778 return
Anas Nashif73440ea2018-02-19 10:57:03 -0600779
780 ser.flush()
781
Anas Nashif83fc06a2019-06-22 11:04:10 -0400782 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashif73440ea2018-02-19 10:57:03 -0600783 harness_import = HarnessImporter(harness_name)
784 harness = harness_import.instance
785 harness.configure(self.instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400786 read_pipe, write_pipe = os.pipe()
787 start_time = time.time()
Anas Nashif73440ea2018-02-19 10:57:03 -0600788
Marti Bolivar5591ca22019-02-07 15:53:39 -0700789 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400790 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600791 t.start()
792
Andy Doan79c48842019-02-08 10:09:04 -0600793 logging.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500794 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400795 if VERBOSE and not runner:
Marti Bolivar303b5222019-02-07 15:50:55 -0700796 subprocess.check_call(command)
797 else:
798 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400799
Anas Nashif61e21632018-04-08 13:30:16 -0500800 except subprocess.CalledProcessError:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400801 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600802
803 t.join(self.timeout)
804 if t.is_alive():
805 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600806
807 if ser.isOpen():
808 ser.close()
809
Anas Nashifd3384fb2018-02-22 06:44:16 -0600810 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400811 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600812 if c not in harness.tests:
813 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500814
Anas Nashif83fc06a2019-06-22 11:04:10 -0400815 handler_time = time.time() - start_time
816
Anas Nashif61e21632018-04-08 13:30:16 -0500817 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600818 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400819 self.set_state(harness.state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600820 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400821 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600822
Anas Nashif83fc06a2019-06-22 11:04:10 -0400823 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500824
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300825class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700826 """Spawns a thread to monitor QEMU output from pipes
827
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400828 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700829 We need to do this as once qemu starts, it runs forever until killed.
830 Test cases emit special messages to the console as they run, we check
831 for these to collect whether the test passed or failed.
832 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700833
Anas Nashif83fc06a2019-06-22 11:04:10 -0400834
835 def __init__(self, instance, type_str):
836 """Constructor
837
838 @param instance Test instance
839 """
840
841 super().__init__(instance, type_str)
842 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
843
844 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
845
846
Andrew Boie6acbe632015-07-17 12:03:52 -0700847 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500848 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700849 fifo_in = fifo_fn + ".in"
850 fifo_out = fifo_fn + ".out"
851
852 # These in/out nodes are named from QEMU's perspective, not ours
853 if os.path.exists(fifo_in):
854 os.unlink(fifo_in)
855 os.mkfifo(fifo_in)
856 if os.path.exists(fifo_out):
857 os.unlink(fifo_out)
858 os.mkfifo(fifo_out)
859
860 # We don't do anything with out_fp but we need to open it for
861 # writing so that QEMU doesn't block, due to the way pipes work
862 out_fp = open(fifo_in, "wb")
863 # Disable internal buffering, we don't
864 # want read() or poll() to ever block if there is data in there
865 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800866 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700867
868 start_time = time.time()
869 timeout_time = start_time + timeout
870 p = select.poll()
871 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400872 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700873
Andrew Boie6acbe632015-07-17 12:03:52 -0700874 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500875 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700876 while True:
877 this_timeout = int((timeout_time - time.time()) * 1000)
878 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400879 if not out_state:
880 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700881 break
882
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500883 try:
884 c = in_fp.read(1).decode("utf-8")
885 except UnicodeDecodeError:
886 # Test is writing something weird, fail
887 out_state = "unexpected byte"
888 break
889
Andrew Boie6acbe632015-07-17 12:03:52 -0700890 if c == "":
891 # EOF, this shouldn't happen unless QEMU crashes
892 out_state = "unexpected eof"
893 break
894 line = line + c
895 if c != "\n":
896 continue
897
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300898 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700899 log_out_fp.write(line)
900 log_out_fp.flush()
901 line = line.strip()
902 verbose("QEMU: %s" % line)
903
Anas Nashif576be982017-12-23 20:20:27 -0500904 harness.handle(line)
905 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400906 # if we have registered a fail make sure the state is not
907 # overridden by a false success message coming from the
908 # testsuite
909 if out_state != 'failed':
910 out_state = harness.state
911
912 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700913 # the timeout and wait for 2 more seconds to catch anything
914 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700915 # coverage is enabled since dumping this information can
916 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500917 if not timeout_extended or harness.capture_coverage:
918 timeout_extended= True
919 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700920 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500921 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500922 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700923 line = ""
924
Anas Nashif83fc06a2019-06-22 11:04:10 -0400925 handler.record(harness)
926
927 handler_time = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700928 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashif83fc06a2019-06-22 11:04:10 -0400929 (out_state, handler_time))
930 handler.set_state(out_state, handler_time)
Andrew Boie6acbe632015-07-17 12:03:52 -0700931
932 log_out_fp.close()
933 out_fp.close()
934 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400935 if os.path.exists(pid_fn):
936 pid = int(open(pid_fn).read())
937 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700938
Anas Nashifd6476ee2019-04-11 11:40:09 -0400939 try:
940 if pid:
941 os.kill(pid, signal.SIGTERM)
942 except ProcessLookupError:
943 # Oh well, as long as it's dead! User probably sent Ctrl-C
944 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800945
Andrew Boie6acbe632015-07-17 12:03:52 -0700946 os.unlink(fifo_in)
947 os.unlink(fifo_out)
948
Anas Nashif83fc06a2019-06-22 11:04:10 -0400949 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700950 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500951 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700952
953 # We pass this to QEMU which looks for fifos with .in and .out
954 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400955 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700956
Anas Nashif83fc06a2019-06-22 11:04:10 -0400957 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700958 if os.path.exists(self.pid_fn):
959 os.unlink(self.pid_fn)
960
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500961 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500962
Anas Nashif83fc06a2019-06-22 11:04:10 -0400963 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500964 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600965 harness.configure(self.instance)
966 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400967 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300968 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500969 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600970
971 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700972 self.thread.daemon = True
Anas Nashif83fc06a2019-06-22 11:04:10 -0400973 verbose("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700974 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400975 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700976
Andrew Boie6acbe632015-07-17 12:03:52 -0700977 def get_fifo(self):
978 return self.fifo_fn
979
Andrew Boie6acbe632015-07-17 12:03:52 -0700980class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700981
Anas Nashif83fc06a2019-06-22 11:04:10 -0400982 alloc_sections = [
983 "bss",
984 "noinit",
985 "app_bss",
986 "app_noinit",
987 "ccm_bss",
988 "ccm_noinit"
989 ]
990
991 rw_sections = [
992 "datas",
993 "initlevel",
994 "exceptions",
995 "initshell",
996 "_static_thread_area",
997 "_k_timer_area",
998 "_k_mem_slab_area",
999 "_k_mem_pool_area",
1000 "sw_isr_table",
1001 "_k_sem_area",
1002 "_k_mutex_area",
1003 "app_shmem_regions",
1004 "_k_fifo_area",
1005 "_k_lifo_area",
1006 "_k_stack_area",
1007 "_k_msgq_area",
1008 "_k_mbox_area",
1009 "_k_pipe_area",
1010 "net_if",
1011 "net_if_dev",
1012 "net_stack",
1013 "net_l2_data",
1014 "_k_queue_area",
1015 "_net_buf_pool_area",
1016 "app_datas",
1017 "kobject_data",
1018 "mmu_tables",
1019 "app_pad",
1020 "priv_stacks",
1021 "ccm_data",
1022 "usb_descriptor",
1023 "usb_data", "usb_bos_desc",
1024 'log_backends_sections',
1025 'log_dynamic_sections',
1026 'log_const_sections',
1027 "app_smem",
1028 'shell_root_cmds_sections',
1029 'log_const_sections',
1030 "font_entry_sections",
1031 "priv_stacks_noinit",
1032 "_TEXT_SECTION_NAME_2",
1033 "_GCOV_BSS_SECTION_NAME",
1034 "gcov",
1035 "nocache"
1036 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001037
Andrew Boie73b4ee62015-10-07 11:33:22 -07001038 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001039 ro_sections = [
1040 "text",
1041 "ctors",
1042 "init_array",
1043 "reset",
1044 "object_access",
1045 "rodata",
1046 "devconfig",
1047 "net_l2",
1048 "vector",
1049 "sw_isr_table",
1050 "_settings_handlers_area",
1051 "_bt_channels_area",
1052 "_bt_br_channels_area",
1053 "_bt_services_area",
1054 "vectors",
1055 "net_socket_register",
1056 "net_ppp_proto"
1057 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001058
Andrew Boie52fef672016-11-29 12:21:59 -08001059 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001060 """Constructor
1061
Andrew Boiebbd670c2015-08-17 13:16:11 -07001062 @param filename Path to the output binary
1063 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001064 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001065 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001066 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001067 magic = f.read(4)
1068
Anas Nashifb4bdd662018-08-15 17:12:28 -05001069 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001070 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001071 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1072 except Exception as e:
1073 print(str(e))
1074 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001075
1076 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001077 # GREP can not be used as it returns an error if the symbol is not
1078 # found.
1079 is_xip_command = "nm " + filename + \
1080 " | awk '/CONFIG_XIP/ { print $3 }'"
1081 is_xip_output = subprocess.check_output(
1082 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1083 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001084 try:
1085 if is_xip_output.endswith("no symbols"):
1086 raise SanityRuntimeError("%s has no symbol information" % filename)
1087 except Exception as e:
1088 print(str(e))
1089 sys.exit(2)
1090
Andrew Boie6acbe632015-07-17 12:03:52 -07001091 self.is_xip = (len(is_xip_output) != 0)
1092
Andrew Boiebbd670c2015-08-17 13:16:11 -07001093 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001094 self.sections = []
1095 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001096 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001097 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001098
1099 self._calculate_sizes()
1100
1101 def get_ram_size(self):
1102 """Get the amount of RAM the application will use up on the device
1103
1104 @return amount of RAM, in bytes
1105 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001106 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001107
1108 def get_rom_size(self):
1109 """Get the size of the data that this application uses on device's flash
1110
1111 @return amount of ROM, in bytes
1112 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001113 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001114
1115 def unrecognized_sections(self):
1116 """Get a list of sections inside the binary that weren't recognized
1117
David B. Kinder29963c32017-06-16 12:32:42 -07001118 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001119 """
1120 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001121 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001122 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001123 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001124 return slist
1125
1126 def _calculate_sizes(self):
1127 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001128 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001129 objdump_output = subprocess.check_output(
1130 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001131
1132 for line in objdump_output:
1133 words = line.split()
1134
Anas Nashif83fc06a2019-06-22 11:04:10 -04001135 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001136 continue
1137
1138 index = words[0]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001139 if not index[0].isdigit(): # Skip lines that do not start
Andrew Boie6acbe632015-07-17 12:03:52 -07001140 continue # with a digit
1141
1142 name = words[1] # Skip lines with section names
Anas Nashif83fc06a2019-06-22 11:04:10 -04001143 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001144 continue
1145
Andrew Boie73b4ee62015-10-07 11:33:22 -07001146 # TODO this doesn't actually reflect the size in flash or RAM as
1147 # it doesn't include linker-imposed padding between sections.
1148 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001149 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001150 if size == 0:
1151 continue
1152
Andrew Boie73b4ee62015-10-07 11:33:22 -07001153 load_addr = int(words[4], 16)
1154 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001155
1156 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001157 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001158 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001159 if name in SizeCalculator.alloc_sections:
1160 self.ram_size += size
1161 stype = "alloc"
1162 elif name in SizeCalculator.rw_sections:
1163 self.ram_size += size
1164 self.rom_size += size
1165 stype = "rw"
1166 elif name in SizeCalculator.ro_sections:
1167 self.rom_size += size
1168 if not self.is_xip:
1169 self.ram_size += size
1170 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001171 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001172 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001173 if name not in self.extra_sections:
1174 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001175
Anas Nashif3ba1d432017-12-05 15:28:44 -05001176 self.sections.append({"name": name, "load_addr": load_addr,
1177 "size": size, "virt_addr": virt_addr,
1178 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001179
1180
Andrew Boie6acbe632015-07-17 12:03:52 -07001181# "list" - List of strings
1182# "list:<type>" - List of <type>
1183# "set" - Set of unordered, unique strings
1184# "set:<type>" - Set of <type>
1185# "float" - Floating point
1186# "int" - Integer
1187# "bool" - Boolean
1188# "str" - String
1189
1190# XXX Be sure to update __doc__ if you change any of this!!
1191
Anas Nashif83fc06a2019-06-22 11:04:10 -04001192platform_valid_keys = {
Anas Nashif924a4e72018-10-18 12:25:55 -04001193 "supported_toolchains": {"type": "list", "default": []},
1194 "env": {"type": "list", "default": []}
1195 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001196
Anas Nashif3ba1d432017-12-05 15:28:44 -05001197testcase_valid_keys = {"tags": {"type": "set", "required": False},
1198 "type": {"type": "str", "default": "integration"},
1199 "extra_args": {"type": "list"},
1200 "extra_configs": {"type": "list"},
1201 "build_only": {"type": "bool", "default": False},
1202 "build_on_all": {"type": "bool", "default": False},
1203 "skip": {"type": "bool", "default": False},
1204 "slow": {"type": "bool", "default": False},
1205 "timeout": {"type": "int", "default": 60},
1206 "min_ram": {"type": "int", "default": 8},
1207 "depends_on": {"type": "set"},
1208 "min_flash": {"type": "int", "default": 32},
1209 "arch_whitelist": {"type": "set"},
1210 "arch_exclude": {"type": "set"},
1211 "extra_sections": {"type": "list", "default": []},
1212 "platform_exclude": {"type": "set"},
1213 "platform_whitelist": {"type": "set"},
1214 "toolchain_exclude": {"type": "set"},
1215 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001216 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001217 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301218 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001219 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001220
Andrew Boie6acbe632015-07-17 12:03:52 -07001221class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001222 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001223 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001224
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001225 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001226 """Instantiate a new SanityConfigParser object
1227
Anas Nashifa792a3d2017-04-04 18:47:49 -04001228 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001229 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001230 self.data = {}
1231 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001232 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001233 self.tests = {}
1234 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001235
1236 def load(self):
1237 self.data = scl.yaml_load_verify(self.filename, self.schema)
1238
Anas Nashif255625b2017-12-05 15:08:26 -05001239 if 'tests' in self.data:
1240 self.tests = self.data['tests']
1241 if 'common' in self.data:
1242 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001243
Anas Nashif83fc06a2019-06-22 11:04:10 -04001244
Andrew Boie6acbe632015-07-17 12:03:52 -07001245 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001246 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001247 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001248 if typestr == "str":
1249 return v
1250
1251 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001252 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001253
1254 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001255 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001256
1257 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001258 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001259
Anas Nashif3ba1d432017-12-05 15:28:44 -05001260 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001261 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001262 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 vs = v.split()
1264 if len(typestr) > 4 and typestr[4] == ":":
1265 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1266 else:
1267 return vs
1268
1269 elif typestr.startswith("set"):
1270 vs = v.split()
1271 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001272 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 else:
1274 return set(vs)
1275
Anas Nashif576be982017-12-23 20:20:27 -05001276 elif typestr.startswith("map"):
1277 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001278 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001279 raise ConfigurationError(
1280 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001281
Anas Nashifb4754ed2017-12-05 17:27:58 -05001282 def get_test(self, name, valid_keys):
1283 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001284
Anas Nashifb4754ed2017-12-05 17:27:58 -05001285 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001286 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001287 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001288 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001289 here, it will generate an error. Each value in this dictionary
1290 is another dictionary containing metadata:
1291
1292 "default" - Default value if not given
1293 "type" - Data type to convert the text value to. Simple types
1294 supported are "str", "float", "int", "bool" which will get
1295 converted to respective Python data types. "set" and "list"
1296 may also be specified which will split the value by
1297 whitespace (but keep the elements as strings). finally,
1298 "list:<type>" and "set:<type>" may be given which will
1299 perform a type conversion after splitting the value up.
1300 "required" - If true, raise an error if not defined. If false
1301 and "default" isn't specified, a type conversion will be
1302 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001303 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001304 type conversion and default values filled in per valid_keys
1305 """
1306
1307 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001308 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001309 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001310
Anas Nashifb4754ed2017-12-05 17:27:58 -05001311 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001312 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001313 raise ConfigurationError(
1314 self.filename,
1315 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001316 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001317
Anas Nashiffa695d22017-10-04 16:14:27 -04001318 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001319 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001320 # By default, we just concatenate string values of keys
1321 # which appear both in "common" and per-test sections,
1322 # but some keys are handled in adhoc way based on their
1323 # semantics.
1324 if k == "filter":
1325 d[k] = "(%s) and (%s)" % (d[k], v)
1326 else:
1327 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001328 else:
1329 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001330
Andrew Boie08ce5a52016-02-22 13:28:10 -08001331 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001332 if k not in d:
1333 if "required" in kinfo:
1334 required = kinfo["required"]
1335 else:
1336 required = False
1337
1338 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001339 raise ConfigurationError(
1340 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001341 "missing required value for '%s' in test '%s'" %
1342 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001343 else:
1344 if "default" in kinfo:
1345 default = kinfo["default"]
1346 else:
1347 default = self._cast_value("", kinfo["type"])
1348 d[k] = default
1349 else:
1350 try:
1351 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001352 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001353 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001354 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1355 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001356
1357 return d
1358
1359
1360class Platform:
1361 """Class representing metadata for a particular platform
1362
Anas Nashifc7406082015-12-13 15:00:31 -05001363 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001364
Anas Nashif83fc06a2019-06-22 11:04:10 -04001365 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
1366 "scripts","sanity_chk","platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001367
Anas Nashif83fc06a2019-06-22 11:04:10 -04001368 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001369 """Constructor.
1370
Andrew Boie6acbe632015-07-17 12:03:52 -07001371 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001372
1373 self.name = ""
1374 self.sanitycheck = True
1375 # if no RAM size is specified by the board, take a default of 128K
1376 self.ram = 128
1377
1378 self.ignore_tags = []
1379 self.default = False
1380 # if no flash size is specified by the board, take a default of 512K
1381 self.flash = 512
1382 self.supported = set()
1383
1384 self.arch = ""
1385 self.type = "na"
1386 self.simulation = "na"
1387 self.supported_toolchains = []
1388 self.env = []
1389 self.env_satisfied = True
1390 self.filter_data = dict()
1391
1392 def load(self, platform_file):
1393 scp = SanityConfigParser(platform_file, self.platform_schema)
1394 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001395 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001396
Anas Nashif255625b2017-12-05 15:08:26 -05001397 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001398 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001399 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001400 self.ram = data.get("ram", 128)
1401 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001402 self.ignore_tags = testing.get("ignore_tags", [])
1403 self.default = testing.get("default", False)
1404 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001405 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001406 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001407 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001408 for item in supp_feature.split(":"):
1409 self.supported.add(item)
1410
Anas Nashif255625b2017-12-05 15:08:26 -05001411 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001412 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001413 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001414 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001415 self.env = data.get("env", [])
1416 self.env_satisfied = True
1417 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001418 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001419 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001420
Andrew Boie6acbe632015-07-17 12:03:52 -07001421
Andrew Boie6acbe632015-07-17 12:03:52 -07001422 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001423 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001424
Anas Nashif83fc06a2019-06-22 11:04:10 -04001425class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001426 """Class representing a test application
1427 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001428
Anas Nashif83fc06a2019-06-22 11:04:10 -04001429
1430 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001431 """TestCase constructor.
1432
Anas Nashif877d3ca2017-12-05 17:39:29 -05001433 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001434 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001435 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001436
Andrew Boie6acbe632015-07-17 12:03:52 -07001437 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001438 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001439 the test case is <workdir>/<name>.
1440
Marc Herbert1c8632c2019-04-15 17:58:45 -07001441 @param testcase_root os.path.abspath() of one of the --testcase-root
1442 @param workdir Sub-directory of testcase_root where the
1443 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001444 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001445 in the test case configuration file. For many test cases that just
1446 define one test, can be anything and is usually "test". This is
1447 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001448 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001449 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001450 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001451 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001452
Anas Nashif83fc06a2019-06-22 11:04:10 -04001453 self.id = ""
1454 self.source_dir = ""
1455 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001456 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001457 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001458
Anas Nashif83fc06a2019-06-22 11:04:10 -04001459 self.type = None
1460 self.tags = None
1461 self.extra_args = None
1462 self.extra_configs = None
1463 self.arch_whitelist = None
1464 self.arch_exclude = None
1465 self.skip = None
1466 self.platform_exclude = None
1467 self.platform_whitelist = None
1468 self.toolchain_exclude = None
1469 self.toolchain_whitelist = None
1470 self.tc_filter = None
1471 self.timeout = 60
1472 self.harness = ""
1473 self.harness_config = {}
1474 self.build_only = True
1475 self.build_on_all = False
1476 self.slow = False
1477 self.min_ram = None
1478 self.depends_on = None
1479 self.min_flash = None
1480 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001481
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001482
Anas Nashif83fc06a2019-06-22 11:04:10 -04001483 @staticmethod
1484 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001485
Marc Herbert1c8632c2019-04-15 17:58:45 -07001486 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001487 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001488 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001489 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001490 relative_tc_root = os.path.relpath(canonical_testcase_root,
1491 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001492 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001493 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001494
Marc Herbert1c8632c2019-04-15 17:58:45 -07001495 # workdir can be "."
1496 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001497 return unique
1498
Anas Nashif83fc06a2019-06-22 11:04:10 -04001499 @staticmethod
1500 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001501 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001502 # do not match until end-of-line, otherwise we won't allow
1503 # stc_regex below to catch the ones that are declared in the same
1504 # line--as we only search starting the end of this match
1505 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001506 re.MULTILINE)
1507 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001508 br"^\s*" # empy space at the beginning is ok
1509 # catch the case where it is declared in the same sentence, e.g:
1510 #
1511 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1512 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1513 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1514 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1515 # Consume the argument that becomes the extra testcse
1516 br"\(\s*"
1517 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1518 # _setup_teardown() variant has two extra arguments that we ignore
1519 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1520 br"\s*\)",
1521 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001522 re.MULTILINE)
1523 suite_run_regex = re.compile(
1524 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1525 re.MULTILINE)
1526 achtung_regex = re.compile(
1527 br"(#ifdef|#endif)",
1528 re.MULTILINE)
1529 warnings = None
1530
1531 with open(inf_name) as inf:
1532 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1533 mmap.PROT_READ, 0)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001534 # contextlib makes pylint think main_c isn't subscriptable
1535 # pylint: disable=unsubscriptable-object
1536
Anas Nashifaae71d72018-04-21 22:26:48 -05001537 suite_regex_match = suite_regex.search(main_c)
1538 if not suite_regex_match:
1539 # can't find ztest_test_suite, maybe a client, because
1540 # it includes ztest.h
1541 return None, None
1542
1543 suite_run_match = suite_run_regex.search(main_c)
1544 if not suite_run_match:
1545 raise ValueError("can't find ztest_run_test_suite")
1546
1547 achtung_matches = re.findall(
1548 achtung_regex,
1549 main_c[suite_regex_match.end():suite_run_match.start()])
1550 if achtung_matches:
1551 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001552 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001553 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001554 stc_regex,
1555 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001556 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001557 return matches, warnings
1558
1559 def scan_path(self, path):
1560 subcases = []
1561 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1562 try:
1563 _subcases, warnings = self.scan_file(filename)
1564 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001565 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001566 if _subcases:
1567 subcases += _subcases
1568 except ValueError as e:
Flavio Ceolinbf878ce2019-04-19 22:24:09 -07001569 error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001570 return subcases
1571
Anas Nashif83fc06a2019-06-22 11:04:10 -04001572 def parse_subcases(self, test_path):
1573 results = self.scan_path(os.path.dirname(test_path))
Anas Nashifaae71d72018-04-21 22:26:48 -05001574 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001575 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001576 self.cases.append(name)
1577
Anas Nashiff16e92c2019-03-31 16:58:12 -04001578 if not results:
1579 self.cases.append(self.id)
1580
Anas Nashifaae71d72018-04-21 22:26:48 -05001581
Anas Nashif75547e22018-02-24 08:32:14 -06001582 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001583 return self.name
1584
1585
Andrew Boie6acbe632015-07-17 12:03:52 -07001586class TestInstance:
1587 """Class representing the execution of a particular TestCase on a platform
1588
1589 @param test The TestCase object we want to build/execute
1590 @param platform Platform object that we want to build and run against
1591 @param base_outdir Base directory for all test results. The actual
1592 out directory used is <outdir>/<platform>/<test case name>
1593 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001594
Anas Nashif83fc06a2019-06-22 11:04:10 -04001595 def __init__(self, testcase, platform, base_outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001596
Anas Nashif83fc06a2019-06-22 11:04:10 -04001597 self.testcase = testcase
1598 self.platform = platform
1599
1600 self.status = None
1601 self.reason = "N/A"
1602 self.metrics = dict()
1603 self.handler = None
1604
1605
1606 self.name = os.path.join(platform.name, testcase.name)
1607 self.build_dir = os.path.join(base_outdir, platform.name, testcase.name)
1608
1609 self.build_only = self.check_build_or_run()
1610 self.run = not self.build_only
1611
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001612 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001613
Marc Herbert0f7255c2019-04-05 14:14:21 -07001614 def __lt__(self, other):
1615 return self.name < other.name
1616
Anas Nashif83fc06a2019-06-22 11:04:10 -04001617 def check_build_or_run(self):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001618
Anas Nashif83fc06a2019-06-22 11:04:10 -04001619 build_only = True
1620
1621 # we asked for build-only on the command line
1622 if options.build_only:
1623 return True
1624
1625 # The testcase is designed to be build only.
1626 if self.testcase.build_only:
1627 return True
1628
1629 # Do not run slow tests:
1630 skip_slow = self.testcase.slow and not options.enable_slow
1631 if skip_slow:
1632 return True
1633
1634 runnable =bool(self.testcase.type == "unit" or \
1635 self.platform.type == "native" or \
1636 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1637 options.device_testing)
1638
1639 if self.platform.simulation == "nsim":
1640 if not find_executable("nsimdrv"):
1641 runnable = False
1642
1643 if self.platform.simulation == "renode":
1644 if not find_executable("renode"):
1645 runnable = False
1646
1647 # console harness allows us to run the test and capture data.
1648 if self.testcase.harness == 'console':
1649
1650 # if we have a fixture that is also being supplied on the
1651 # command-line, then we need to run the test, not just build it.
1652 if "fixture" in self.testcase.harness_config:
1653 fixture = self.testcase.harness_config['fixture']
1654 if fixture in options.fixture:
1655 build_only = False
1656 else:
1657 build_only = True
1658 else:
1659 build_only = False
1660 elif self.testcase.harness:
1661 build_only = True
1662 else:
1663 build_only = False
1664
1665 return not (not build_only and runnable)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001666
Anas Nashifdbd76492018-11-23 20:24:19 -05001667 def create_overlay(self, platform):
Marc Herbertc7633de2019-07-06 15:52:31 -07001668 # Create this in a "sanitycheck/" subdirectory otherwise this
1669 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1670 # will silently give that second time precedence over any
1671 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001672 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001673 os.makedirs(subdir, exist_ok=True)
1674 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001675 with open(file, "w") as f:
1676 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001677
Anas Nashif83fc06a2019-06-22 11:04:10 -04001678 if self.testcase.extra_configs:
1679 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001680
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001681 if options.enable_coverage:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001682 if platform.name in options.coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001683 content = content + "\nCONFIG_COVERAGE=y"
1684
Jan Van Winkel21212f32019-09-12 00:03:35 +02001685 if options.enable_asan:
1686 if platform.type == "native":
1687 content = content + "\nCONFIG_ASAN=y"
1688
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001689 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001690
Andrew Boie6acbe632015-07-17 12:03:52 -07001691 def calculate_sizes(self):
1692 """Get the RAM/ROM sizes of a test case.
1693
1694 This can only be run after the instance has been executed by
1695 MakeGenerator, otherwise there won't be any binaries to measure.
1696
1697 @return A SizeCalculator object
1698 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001699 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1700 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001701 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001702 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001703 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001704
1705 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001706
1707 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001708 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001709
1710
Anas Nashif83fc06a2019-06-22 11:04:10 -04001711class CMake():
Andrew Boie4ef16c52015-08-28 12:36:03 -07001712
Anas Nashif83fc06a2019-06-22 11:04:10 -04001713 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1714 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1715
1716 def __init__(self, testcase, platform, source_dir, build_dir):
1717
1718 self.cwd = None
1719 self.capture_output = True
1720
1721 self.defconfig = {}
1722 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001723
1724 self.instance = None
1725 self.testcase = testcase
1726 self.platform = platform
1727 self.source_dir = source_dir
1728 self.build_dir = build_dir
1729 self.log = "build.log"
1730
1731 def parse_generated(self):
1732 self.defconfig = {}
1733 return {}
1734
1735 def run_build(self, args=[]):
1736
1737 verbose("Building %s for %s" % (self.source_dir, self.platform.name))
1738
1739 cmake_args = []
1740 cmake_args.extend(args)
1741 cmake = shutil.which('cmake')
1742 cmd = [cmake] + cmake_args
1743 kwargs = dict()
1744
1745 if self.capture_output:
1746 kwargs['stdout'] = subprocess.PIPE
1747 # CMake sends the output of message() to stderr unless it's STATUS
1748 kwargs['stderr'] = subprocess.STDOUT
1749
1750 if self.cwd:
1751 kwargs['cwd'] = self.cwd
1752
1753 p = subprocess.Popen(cmd, **kwargs)
1754 out, _ = p.communicate()
1755
1756 results = {}
1757 if p.returncode == 0:
1758 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1759
1760 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001761 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1762
1763 if out:
1764 log_msg = out.decode(sys.getdefaultencoding())
1765 with open(os.path.join(self.build_dir, self.log), "a") as log:
1766 log.write(log_msg)
1767
1768 else:
1769 return None
1770 else:
1771 # A real error occurred, raise an exception
1772 if out:
1773 log_msg = out.decode(sys.getdefaultencoding())
1774 with open(os.path.join(self.build_dir, self.log), "a") as log:
1775 log.write(log_msg)
1776
1777 overflow_flash = "region `FLASH' overflowed by"
1778 overflow_ram = "region `RAM' overflowed by"
1779
1780 if log_msg:
1781 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
1782 verbose("RAM/ROM Overflow")
1783 self.instance.status = "skipped"
1784 self.instance.reason = "overflow"
1785 else:
1786 self.instance.status = "failed"
1787 self.instance.reason = "Build failure"
1788
1789 results = {
1790 "returncode": p.returncode,
1791 "instance": self.instance,
1792 }
1793
1794 return results
1795
1796 def run_cmake(self, args=[]):
1797
1798 verbose("Running cmake on %s for %s" %(self.source_dir, self.platform.name))
1799
Anas Nashifa5984ab2019-10-22 07:36:24 -07001800 ldflags="-Wl,--fatal-warnings"
1801
1802 #fixme: add additional cflags based on options
1803 cmake_args = [
1804 '-B{}'.format(self.build_dir),
1805 '-S{}'.format(self.source_dir),
1806 '-DEXTRA_CFLAGS="-Werror ',
1807 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1808 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1809 '-G{}'.format(get_generator()[1])
1810 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001811
1812 args = ["-D{}".format(a.replace('"', '')) for a in args]
1813 cmake_args.extend(args)
1814
1815 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1816 cmake_args.extend(cmake_opts)
1817
1818 cmake = shutil.which('cmake')
1819 cmd = [cmake] + cmake_args
1820 kwargs = dict()
1821
1822 if self.capture_output:
1823 kwargs['stdout'] = subprocess.PIPE
1824 # CMake sends the output of message() to stderr unless it's STATUS
1825 kwargs['stderr'] = subprocess.STDOUT
1826
1827 if self.cwd:
1828 kwargs['cwd'] = self.cwd
1829
1830 p = subprocess.Popen(cmd, **kwargs)
1831 out, _ = p.communicate()
1832
1833 if p.returncode == 0:
1834 filter_results = self.parse_generated()
1835 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1836
1837 results = {'msg': msg, 'filter': filter_results}
1838
1839 else:
1840 self.instance.status = "failed"
1841 self.instance.reason = "Cmake build failure"
1842 results = {"returncode": p.returncode}
1843
1844
1845 if out:
1846 with open(os.path.join(self.build_dir, self.log), "a") as log:
1847 log_msg = out.decode(sys.getdefaultencoding())
1848 log.write(log_msg)
1849
1850 return results
1851
1852
1853class FilterBuilder(CMake):
1854
1855 def __init__(self, testcase, platform, source_dir, build_dir):
1856 super().__init__(testcase, platform, source_dir, build_dir)
1857
1858 self.log = "config-sanitycheck.log"
1859
1860 def parse_generated(self):
1861
1862 if self.platform.name == "unit_testing":
1863 return {}
1864
Anas Nashif83fc06a2019-06-22 11:04:10 -04001865 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001866 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1867
1868 with open(defconfig_path, "r") as fp:
1869 defconfig = {}
1870 for line in fp.readlines():
1871 m = self.config_re.match(line)
1872 if not m:
1873 if line.strip() and not line.startswith("#"):
1874 sys.stderr.write("Unrecognized line %s\n" % line)
1875 continue
1876 defconfig[m.group(1)] = m.group(2).strip()
1877
1878 self.defconfig = defconfig
1879
1880 cmake_conf = {}
1881 try:
1882 cache = CMakeCache.from_file(cmake_cache_path)
1883 except FileNotFoundError:
1884 cache = {}
1885
1886 for k in iter(cache):
1887 cmake_conf[k.name] = k.value
1888
1889 self.cmake_cache = cmake_conf
1890
Anas Nashif83fc06a2019-06-22 11:04:10 -04001891 filter_data = {
1892 "ARCH": self.platform.arch,
1893 "PLATFORM": self.platform.name
1894 }
1895 filter_data.update(os.environ)
1896 filter_data.update(self.defconfig)
1897 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001898
Anas Nashif556f3cb2019-11-05 15:36:15 -08001899 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001900 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001901 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001902 if os.path.exists(dts_path):
1903 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1904 else:
1905 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001906 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1907
Anas Nashif83fc06a2019-06-22 11:04:10 -04001908 except (ValueError, SyntaxError) as se:
1909 sys.stderr.write(
1910 "Failed processing %s\n" % self.testcase.yamlfile)
1911 raise se
1912
1913 if not res:
1914 return {os.path.join(self.platform.name, self.testcase.name): True}
1915 else:
1916 return {os.path.join(self.platform.name, self.testcase.name): False}
1917 else:
1918 self.platform.filter_data = filter_data
1919 return filter_data
1920
1921
1922class ProjectBuilder(FilterBuilder):
1923
1924 def __init__(self, suite, instance):
1925 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1926
1927 self.log = "build.log"
1928 self.instance = instance
1929 self.suite = suite
1930
1931 def setup_handler(self):
1932
1933 instance = self.instance
1934 args = []
1935
1936 # FIXME: Needs simplification
1937 if instance.platform.simulation == "qemu":
1938 instance.handler = QEMUHandler(instance, "qemu")
1939 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
1940 instance.handler.call_make_run = True
1941 elif instance.testcase.type == "unit":
1942 instance.handler = BinaryHandler(instance, "unit")
1943 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
1944 elif instance.platform.type == "native":
1945 instance.handler = BinaryHandler(instance, "native")
1946 instance.handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
1947 elif instance.platform.simulation == "nsim":
1948 if find_executable("nsimdrv"):
1949 instance.handler = BinaryHandler(instance, "nsim")
1950 instance.handler.call_make_run = True
1951 elif instance.platform.simulation == "renode":
1952 if find_executable("renode"):
1953 instance.handler = BinaryHandler(instance, "renode")
1954 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
1955 instance.handler.call_make_run = True
1956 elif options.device_testing:
1957 instance.handler = DeviceHandler(instance, "device")
1958
1959 if instance.handler:
1960 instance.handler.args = args
1961
1962 def process(self, message):
1963 op = message.get('op')
1964
1965 if not self.instance.handler:
1966 self.setup_handler()
1967
1968 # The build process, call cmake and build with configured generator
1969 if op == "cmake":
1970 results = self.cmake()
1971 if self.instance.status == "failed":
1972 pipeline.put({"op": "report", "test": self.instance})
1973 elif options.cmake_only:
1974 pipeline.put({"op": "report", "test": self.instance})
1975 else:
1976 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
1977 verbose("filtering %s" % self.instance.name)
1978 self.instance.status = "skipped"
1979 self.instance.reason = "filter"
1980 pipeline.put({"op": "report", "test": self.instance})
1981 else:
1982 pipeline.put({"op": "build", "test": self.instance})
1983
1984
1985 elif op == "build":
1986 verbose("build test: %s" %self.instance.name)
1987 results = self.build()
1988
1989 if results.get('returncode', 1) > 0:
1990 pipeline.put({"op": "report", "test": self.instance})
1991 else:
1992 if self.instance.run:
1993 pipeline.put({"op": "run", "test": self.instance})
1994 else:
1995 pipeline.put({"op": "report", "test": self.instance})
1996 # Run the generated binary using one of the supported handlers
1997 elif op == "run":
1998 verbose("run test: %s" %self.instance.name)
1999 self.run()
2000 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002001 pipeline.put({
2002 "op": "report",
2003 "test": self.instance,
2004 "state": "executed",
2005 "status": self.instance.status,
2006 "reason": self.instance.status}
2007 )
2008
2009 # Report results and output progress to screen
2010 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002011 with report_lock:
2012 self.report_out()
2013
Anas Nashif83fc06a2019-06-22 11:04:10 -04002014 def report_out(self):
2015 total_tests_width = len(str(self.suite.total_tests))
2016 self.suite.total_done += 1
2017 instance = self.instance
2018
2019 if instance.status in ["failed", "timeout"]:
2020 self.suite.total_failed += 1
2021 if VERBOSE or not TERMINAL:
2022 status = COLOR_RED + "FAILED " + COLOR_NORMAL + instance.reason
2023 else:
2024 info(
Anas Nashifc1ea4522019-10-11 07:32:45 -07002025 "\n{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002026 instance.platform.name,
2027 instance.testcase.name,
2028 COLOR_RED,
2029 COLOR_NORMAL,
2030 instance.reason), False)
Anas Nashifc1ea4522019-10-11 07:32:45 -07002031 if not VERBOSE:
Ulf Magnussone73d2862019-10-29 11:54:02 +01002032 log_info_file(instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002033 elif instance.status == "skipped":
2034 self.suite.total_skipped += 1
2035 status = COLOR_YELLOW + "SKIPPED" + COLOR_NORMAL
Anas Nashif83fc06a2019-06-22 11:04:10 -04002036 else:
2037 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2038
2039 if VERBOSE or not TERMINAL:
2040 if options.cmake_only:
2041 more_info = "cmake"
2042 elif instance.status == "skipped":
2043 more_info = instance.reason
2044 else:
2045 if instance.handler and instance.run:
2046 more_info = instance.handler.type_str
2047 htime = instance.handler.duration
2048 if htime:
2049 more_info += " {:.3f}s".format(htime)
2050 else:
2051 more_info = "build"
2052
2053 info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
2054 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2055 instance.testcase.name, status, more_info))
2056
2057 if instance.status in ["failed", "timeout"]:
Ulf Magnussone73d2862019-10-29 11:54:02 +01002058 log_info_file(instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002059 else:
2060 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% skipped: %s%4d%s, failed: %s%4d%s" % (
2061 COLOR_GREEN,
2062 self.suite.total_done,
2063 self.suite.total_tests,
2064 COLOR_NORMAL,
2065 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
2066 COLOR_YELLOW if self.suite.total_skipped > 0 else COLOR_NORMAL,
2067 self.suite.total_skipped,
2068 COLOR_NORMAL,
2069 COLOR_RED if self.suite.total_failed > 0 else COLOR_NORMAL,
2070 self.suite.total_failed,
2071 COLOR_NORMAL
2072 )
2073 )
2074 sys.stdout.flush()
2075
2076 def cmake(self):
2077
2078 instance = self.instance
2079 args = self.testcase.extra_args[:]
2080
2081 if options.extra_args:
2082 args += options.extra_args
2083
2084 if instance.handler:
2085 args += instance.handler.args
2086
2087 # merge overlay files into one variable
2088 overlays = ""
2089 idx = 0
2090 for arg in args:
2091 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2092 if match:
2093 overlays += match.group(1)
2094 del args[idx]
2095 idx += 1
2096
Jan Van Winkel21212f32019-09-12 00:03:35 +02002097 if (self.testcase.extra_configs or options.coverage or
2098 options.enable_asan):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002099 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
2100 os.path.join(instance.build_dir,
2101 "sanitycheck", "testcase_extra.conf")))
2102
2103 results = self.run_cmake(args)
2104 return results
2105
2106 def build(self):
2107 results = self.run_build(['--build', self.build_dir])
2108 return results
2109
2110 def run(self):
2111
2112 instance = self.instance
2113
2114 if instance.handler.type_str == "device":
2115 instance.handler.suite = self.suite
2116
2117 instance.handler.handle()
2118
2119 if instance.handler.type_str == "qemu":
2120 verbose("Running %s (%s)" %(instance.name, instance.handler.type_str))
2121 command = [get_generator()[0]]
2122 command += ["-C", self.build_dir, "run"]
2123
2124 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
2125 verbose("Spawning QEMUHandler Thread for %s" % instance.name)
2126 proc.wait()
2127 self.returncode = proc.returncode
2128
2129 sys.stdout.flush()
2130
2131
2132pipeline = queue.LifoQueue()
2133
2134class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2135 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2136 calls to submit() once the limit given as "bound" work items are queued for
2137 execution.
2138 :param bound: Integer - the maximum number of items in the work queue
2139 :param max_workers: Integer - the size of the thread pool
2140 """
2141 def __init__(self, bound, max_workers, **kwargs):
2142 super().__init__(max_workers)
2143 #self.executor = ThreadPoolExecutor(max_workers=max_workers)
2144 self.semaphore = BoundedSemaphore(bound + max_workers)
2145
2146 def submit(self, fn, *args, **kwargs):
2147 self.semaphore.acquire()
2148 try:
2149 future = super().submit(fn, *args, **kwargs)
2150 except:
2151 self.semaphore.release()
2152 raise
2153 else:
2154 future.add_done_callback(lambda x: self.semaphore.release())
2155 return future
2156
Andrew Boie6acbe632015-07-17 12:03:52 -07002157
2158class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002159 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002160 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002161
Anas Nashif83fc06a2019-06-22 11:04:10 -04002162 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002163 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002164 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002165
Anas Nashif37f9dc52018-02-23 08:53:46 -06002166 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002167
2168 self.roots = testcase_roots
2169 if not isinstance(board_root_list, list):
2170 self.board_roots= [board_root_list]
2171 else:
2172 self.board_roots = board_root_list
2173
Andrew Boie6acbe632015-07-17 12:03:52 -07002174 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002175 self.testcases = {}
2176 self.platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002177 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002178 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002179 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002180 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002181 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002182
Anas Nashif83fc06a2019-06-22 11:04:10 -04002183 self.total_tests = 0 # number of test instances
2184 self.total_cases = 0 # number of test cases
2185 self.total_done = 0 # tests completed
2186 self.total_failed = 0
2187 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002188
Anas Nashif83fc06a2019-06-22 11:04:10 -04002189 self.total_platforms = 0
2190 self.start_time = 0
2191 self.duration = 0
2192 self.warnings = 0
2193 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002194
Anas Nashif83fc06a2019-06-22 11:04:10 -04002195 # hardcoded for now
2196 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002197
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002198
Anas Nashif83fc06a2019-06-22 11:04:10 -04002199 if options.jobs:
2200 self.jobs = options.jobs
2201 elif options.build_only:
2202 self.jobs = multiprocessing.cpu_count() * 2
Andy Ross9c9162d2019-01-03 10:50:53 -08002203 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002204 self.jobs = multiprocessing.cpu_count()
Daniel Leung6b170072016-04-07 12:10:25 -07002205
Anas Nashif83fc06a2019-06-22 11:04:10 -04002206 info("JOBS: %d" % self.jobs)
Andrew Boie6acbe632015-07-17 12:03:52 -07002207
Anas Nashif83fc06a2019-06-22 11:04:10 -04002208 def update(self):
2209 self.total_tests = len(self.instances)
2210 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002211
Andrew Boie6acbe632015-07-17 12:03:52 -07002212
2213 def compare_metrics(self, filename):
2214 # name, datatype, lower results better
2215 interesting_metrics = [("ram_size", int, True),
2216 ("rom_size", int, True)]
2217
Andrew Boie6acbe632015-07-17 12:03:52 -07002218
2219 if not os.path.exists(filename):
2220 info("Cannot compare metrics, %s not found" % filename)
2221 return []
2222
2223 results = []
2224 saved_metrics = {}
2225 with open(filename) as fp:
2226 cr = csv.DictReader(fp)
2227 for row in cr:
2228 d = {}
2229 for m, _, _ in interesting_metrics:
2230 d[m] = row[m]
2231 saved_metrics[(row["test"], row["platform"])] = d
2232
Anas Nashif83fc06a2019-06-22 11:04:10 -04002233 for instance in self.instances.values():
2234 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002235 if mkey not in saved_metrics:
2236 continue
2237 sm = saved_metrics[mkey]
2238 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002239 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002240 continue
2241 if sm[metric] == "":
2242 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002243 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002244 if delta == 0:
2245 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002246 results.append((instance, metric, instance.metrics.get(metric, 0 ), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002247 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002248 return results
2249
Anas Nashif83fc06a2019-06-22 11:04:10 -04002250 def misc_reports(self, report, show_footprint, all_deltas,
2251 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002252
Anas Nashif83fc06a2019-06-22 11:04:10 -04002253 if not report:
2254 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002255
Anas Nashif83fc06a2019-06-22 11:04:10 -04002256 deltas = self.compare_metrics(report)
2257 warnings = 0
2258 if deltas and show_footprint:
2259 for i, metric, value, delta, lower_better in deltas:
2260 if not all_deltas and ((delta < 0 and lower_better) or
2261 (delta > 0 and not lower_better)):
2262 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002263
Anas Nashif83fc06a2019-06-22 11:04:10 -04002264 percentage = (float(delta) / float(value - delta))
2265 if not all_deltas and (percentage <
2266 (footprint_threshold / 100.0)):
2267 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002268
Anas Nashif83fc06a2019-06-22 11:04:10 -04002269 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
2270 i.platform.name, i.testcase.name, COLOR_YELLOW,
2271 "INFO" if all_deltas else "WARNING", COLOR_NORMAL,
2272 metric, delta, value, percentage))
2273 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002274
Anas Nashif83fc06a2019-06-22 11:04:10 -04002275 if warnings:
2276 info("Deltas based on metrics from last %s" %
2277 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002278
Anas Nashif83fc06a2019-06-22 11:04:10 -04002279 def summary(self, unrecognized_sections):
2280 failed = 0
2281 for instance in self.instances.values():
2282 if instance.status == "failed":
2283 failed += 1
2284 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
2285 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2286 (COLOR_RED, COLOR_NORMAL, instance.name,
2287 str(instance.metrics.get("unrecognized", []))))
2288 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002289
Anas Nashif83fc06a2019-06-22 11:04:10 -04002290 if self.total_tests and self.total_tests != self.total_skipped:
2291 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped)/ float(self.total_tests - self.total_skipped))
2292 else:
2293 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002294
Anas Nashif83fc06a2019-06-22 11:04:10 -04002295 info("{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
2296 COLOR_RED if failed else COLOR_GREEN,
2297 self.total_tests - self.total_failed - self.total_skipped,
2298 self.total_tests,
2299 COLOR_NORMAL,
2300 pass_rate,
2301 COLOR_RED if self.total_failed else COLOR_NORMAL,
2302 self.total_failed,
2303 COLOR_NORMAL,
2304 self.total_skipped,
2305 COLOR_YELLOW if self.warnings else COLOR_NORMAL,
2306 self.warnings,
2307 COLOR_NORMAL,
2308 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002309
Anas Nashif83fc06a2019-06-22 11:04:10 -04002310 platforms = set(p.platform for p in self.instances.values())
2311 self.total_platforms = len(self.platforms)
2312 if self.platforms:
2313 info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
2314 self.total_cases,
2315 len(platforms),
2316 self.total_platforms,
2317 (100 * len(platforms) / len(self.platforms))
2318 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002319
Anas Nashif83fc06a2019-06-22 11:04:10 -04002320 def save_reports(self):
2321 if not self.instances:
2322 return
Anas Nashif61e21632018-04-08 13:30:16 -05002323
Anas Nashif83fc06a2019-06-22 11:04:10 -04002324 report_name = "sanitycheck"
2325 if options.report_name:
2326 report_name = options.report_name
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002327
Anas Nashif83fc06a2019-06-22 11:04:10 -04002328 if options.report_dir:
Paul Sokolovsky3ea18692019-10-15 17:18:39 +03002329 os.makedirs(options.report_dir, exist_ok=True)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002330 filename = os.path.join(options.report_dir, report_name)
2331 outdir = options.report_dir
2332 else:
2333 filename = os.path.join(options.outdir, report_name)
2334 outdir = options.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002335
Anas Nashif83fc06a2019-06-22 11:04:10 -04002336 if not options.no_update:
2337 self.xunit_report(filename + ".xml")
2338 self.csv_report(filename + ".csv")
2339 self.target_report(outdir)
2340 if self.discards:
2341 self.discard_report(filename + "_discard.csv")
2342
2343 if options.release:
2344 self.csv_report(RELEASE_DATA)
2345
2346 if log_file:
2347 log_file.close()
2348
2349 def load_hardware_map_from_cmdline(self, serial, platform):
2350 device = {
2351 "serial": serial,
2352 "platform": platform,
2353 "counter": 0,
2354 "available": True,
2355 "connected": True
2356 }
2357 self.connected_hardware = [device]
2358
2359 def load_hardware_map(self, map_file):
2360 with open(map_file, 'r') as stream:
2361 try:
2362 self.connected_hardware = yaml.safe_load(stream)
2363 except yaml.YAMLError as exc:
2364 print(exc)
2365 for i in self.connected_hardware:
2366 i['counter'] = 0
2367
2368 def add_configurations(self):
2369
2370 for board_root in self.board_roots:
2371 board_root = os.path.abspath(board_root)
2372
2373 debug("Reading platform configuration files under %s..." %
2374 board_root)
2375
2376 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
2377 verbose("Found plaform configuration " + file)
2378 try:
2379 platform = Platform()
2380 platform.load(file)
2381 if platform.sanitycheck:
2382 self.platforms.append(platform)
2383 if platform.default:
2384 self.default_platforms.append(platform.name)
2385
2386 except RuntimeError as e:
2387 error("E: %s: can't load: %s" % (file, e))
2388 self.load_errors += 1
2389
2390 @staticmethod
2391 def get_toolchain():
2392 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2393 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2394
2395 if toolchain == "gccarmemb":
2396 # Remove this translation when gccarmemb is no longer supported.
2397 toolchain = "gnuarmemb"
2398
Anas Nashifb4bdd662018-08-15 17:12:28 -05002399 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002400 if not toolchain:
2401 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002402 except Exception as e:
2403 print(str(e))
2404 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002405
Anas Nashif83fc06a2019-06-22 11:04:10 -04002406 return toolchain
2407
2408
2409 def add_testcases(self):
2410 for root in self.roots:
2411 root = os.path.abspath(root)
2412
2413 debug("Reading test case configuration files under %s..." %root)
2414
2415 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
2416 verbose("scanning %s" % dirpath)
2417 if 'sample.yaml' in filenames:
2418 filename = 'sample.yaml'
2419 elif 'testcase.yaml' in filenames:
2420 filename = 'testcase.yaml'
2421 else:
2422 continue
2423
2424 verbose("Found possible test case in " + dirpath)
2425
2426 dirnames[:] = []
2427 tc_path = os.path.join(dirpath, filename)
2428 self.add_testcase(tc_path, root)
2429
2430 def add_testcase(self, tc_data_file, root):
2431 try:
2432 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2433 parsed_data.load()
2434
2435 tc_path = os.path.dirname(tc_data_file)
2436 workdir = os.path.relpath(tc_path, root)
2437
2438 for name in parsed_data.tests.keys():
2439 tc = TestCase()
2440 tc.name = tc.get_unique(root, workdir, name)
2441
2442 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2443
2444 tc.source_dir = tc_path
2445 tc.yamlfile = tc_data_file
2446
2447 tc.id = name
2448 tc.type = tc_dict["type"]
2449 tc.tags = tc_dict["tags"]
2450 tc.extra_args = tc_dict["extra_args"]
2451 tc.extra_configs = tc_dict["extra_configs"]
2452 tc.arch_whitelist = tc_dict["arch_whitelist"]
2453 tc.arch_exclude = tc_dict["arch_exclude"]
2454 tc.skip = tc_dict["skip"]
2455 tc.platform_exclude = tc_dict["platform_exclude"]
2456 tc.platform_whitelist = tc_dict["platform_whitelist"]
2457 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2458 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2459 tc.tc_filter = tc_dict["filter"]
2460 tc.timeout = tc_dict["timeout"]
2461 tc.harness = tc_dict["harness"]
2462 tc.harness_config = tc_dict["harness_config"]
2463 tc.build_only = tc_dict["build_only"]
2464 tc.build_on_all = tc_dict["build_on_all"]
2465 tc.slow = tc_dict["slow"]
2466 tc.min_ram = tc_dict["min_ram"]
2467 tc.depends_on = tc_dict["depends_on"]
2468 tc.min_flash = tc_dict["min_flash"]
2469 tc.extra_sections = tc_dict["extra_sections"]
2470
2471 tc.parse_subcases(tc_path)
2472
2473 if tc.name:
2474 self.testcases[tc.name] = tc
2475
2476 except Exception as e:
2477 error("E: %s: can't load (skipping): %s" % (tc_data_file, e))
2478 self.load_errors += 1
2479 return False
2480
2481 return True
2482
2483
2484 def get_platform(self, name):
2485 selected_platform = None
2486 for platform in self.platforms:
2487 if platform.name == name:
2488 selected_platform = platform
2489 break
2490 return selected_platform
2491
2492 def get_last_failed(self):
2493 last_run = os.path.join(options.outdir, "sanitycheck.csv")
2494 try:
2495 if not os.path.exists(last_run):
2496 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" %last_run)
2497 except Exception as e:
2498 print(str(e))
2499 sys.exit(2)
2500
2501 total_tests = 0
2502 with open(last_run, "r") as fp:
2503 cr = csv.DictReader(fp)
2504 instance_list = []
2505 for row in cr:
2506 total_tests += 1
2507 if row["passed"] == "True":
2508 continue
2509 test = row["test"]
2510 platform = self.get_platform(row["platform"])
2511 instance = TestInstance(self.testcases[test], platform, self.outdir)
Jan Van Winkel21212f32019-09-12 00:03:35 +02002512 instance.create_overlay(platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002513 instance_list.append(instance)
2514 self.add_instances(instance_list)
2515
2516 tests_to_run = len(self.instances)
2517 info("%d tests passed already, retyring %d tests" %(total_tests - tests_to_run, tests_to_run))
2518
2519 def load_from_file(self, file):
2520 try:
2521 if not os.path.exists(file):
2522 raise SanityRuntimeError(
2523 "Couldn't find input file with list of tests.")
2524 except Exception as e:
2525 print(str(e))
2526 sys.exit(2)
2527
2528 with open(file, "r") as fp:
2529 cr = csv.DictReader(fp)
2530 instance_list = []
2531 for row in cr:
2532 if row["arch"] == "arch":
2533 continue
2534 test = row["test"]
2535 platform = self.get_platform(row["platform"])
2536 instance = TestInstance(self.testcases[test], platform, self.outdir)
Jan Van Winkel21212f32019-09-12 00:03:35 +02002537 instance.create_overlay(platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002538 instance_list.append(instance)
2539 self.add_instances(instance_list)
2540
2541
2542 def apply_filters(self):
2543
2544 toolchain = self.get_toolchain()
2545
2546 discards = {}
2547 platform_filter = options.platform
2548 testcase_filter = run_individual_tests
2549 arch_filter = options.arch
2550 tag_filter = options.tag
2551 exclude_tag = options.exclude_tag
2552
2553 verbose("platform filter: " + str(platform_filter))
2554 verbose(" arch_filter: " + str(arch_filter))
2555 verbose(" tag_filter: " + str(tag_filter))
2556 verbose(" exclude_tag: " + str(exclude_tag))
2557
2558 default_platforms = False
2559
2560 if platform_filter:
2561 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2562 else:
2563 platforms = self.platforms
2564
2565 if options.all:
2566 info("Selecting all possible platforms per test case")
2567 # When --all used, any --platform arguments ignored
2568 platform_filter = []
2569 elif not platform_filter:
2570 info("Selecting default platforms per test case")
2571 default_platforms = True
2572
2573 info("Building initial testcase list...")
2574
2575 for tc_name, tc in self.testcases.items():
2576 # list of instances per testcase, aka configurations.
2577 instance_list = []
2578 for plat in platforms:
2579 instance = TestInstance(tc, plat, self.outdir)
2580
2581 if (plat.arch == "unit") != (tc.type == "unit"):
2582 # Discard silently
2583 continue
2584
2585 if options.device_testing and instance.build_only:
2586 discards[instance] = "Not runnable on device"
2587 continue
2588
2589 if tc.skip:
2590 discards[instance] = "Skip filter"
2591 continue
2592
2593 if tc.build_on_all and not platform_filter:
2594 platform_filter = []
2595
2596 if tag_filter and not tc.tags.intersection(tag_filter):
2597 discards[instance] = "Command line testcase tag filter"
2598 continue
2599
2600 if exclude_tag and tc.tags.intersection(exclude_tag):
2601 discards[instance] = "Command line testcase exclude filter"
2602 continue
2603
2604 if testcase_filter and tc_name not in testcase_filter:
2605 discards[instance] = "Testcase name filter"
2606 continue
2607
2608 if arch_filter and plat.arch not in arch_filter:
2609 discards[instance] = "Command line testcase arch filter"
2610 continue
2611
2612 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2613 discards[instance] = "Not in test case arch whitelist"
2614 continue
2615
2616 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2617 discards[instance] = "In test case arch exclude"
2618 continue
2619
2620 if tc.platform_exclude and plat.name in tc.platform_exclude:
2621 discards[instance] = "In test case platform exclude"
2622 continue
2623
2624 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2625 discards[instance] = "In test case toolchain exclude"
2626 continue
2627
2628 if platform_filter and plat.name not in platform_filter:
2629 discards[instance] = "Command line platform filter"
2630 continue
2631
2632 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2633 discards[instance] = "Not in testcase platform whitelist"
2634 continue
2635
2636 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2637 discards[instance] = "Not in testcase toolchain whitelist"
2638 continue
2639
2640 if not plat.env_satisfied:
2641 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2642 continue
2643
2644 if not options.force_toolchain \
2645 and toolchain and (toolchain not in plat.supported_toolchains) \
2646 and tc.type != 'unit':
2647 discards[instance] = "Not supported by the toolchain"
2648 continue
2649
2650 if plat.ram < tc.min_ram:
2651 discards[instance] = "Not enough RAM"
2652 continue
2653
2654 if tc.depends_on:
2655 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2656 if dep_intersection != set(tc.depends_on):
2657 discards[instance] = "No hardware support"
2658 continue
2659
2660 if plat.flash < tc.min_flash:
2661 discards[instance] = "Not enough FLASH"
2662 continue
2663
2664 if set(plat.ignore_tags) & tc.tags:
2665 discards[instance] = "Excluded tags per platform"
2666 continue
2667
2668 # if nothing stopped us until now, it means this configuration
2669 # needs to be added.
2670 instance_list.append(instance)
2671
2672 # no configurations, so jump to next testcase
2673 if not instance_list:
2674 continue
2675
2676 # if sanitycheck was launched with no platform options at all, we
2677 # take all default platforms
2678 if default_platforms and not tc.build_on_all:
2679 if tc.platform_whitelist:
2680 a = set(self.default_platforms)
2681 b = set(tc.platform_whitelist)
2682 c = a.intersection(b)
2683 if c:
2684 aa = list( filter( lambda tc: tc.platform.name in c, instance_list))
2685 self.add_instances(aa)
2686 else:
2687 self.add_instances(instance_list[:1])
2688 else:
2689 instances = list( filter( lambda tc: tc.platform.default, instance_list))
2690 self.add_instances(instances)
2691
2692 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2693 discards[instance] = "Not a default test platform"
2694
2695 else:
2696 self.add_instances(instance_list)
2697
2698 for _, case in self.instances.items():
Jan Van Winkel21212f32019-09-12 00:03:35 +02002699 case.create_overlay(case.platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002700
2701 self.discards = discards
2702
2703 return discards
2704
2705 def add_instances(self, instance_list):
2706 for instance in instance_list:
2707 self.instances[instance.name] = instance
2708
2709 def add_tasks_to_queue(self):
2710 for instance in self.instances.values():
2711 if options.test_only:
2712 if instance.run:
2713 pipeline.put({"op": "run", "test": instance, "status": "built"})
2714 else:
2715 if instance.status not in ['passed', 'skipped']:
2716 instance.status = None
2717 pipeline.put({"op": "cmake", "test": instance})
2718
2719 return "DONE FEEDING"
2720
2721 def execute(self):
2722 def calc_one_elf_size(instance):
2723 if instance.status not in ["failed", "skipped"]:
2724 if instance.platform.type != "native":
2725 size_calc = instance.calculate_sizes()
2726 instance.metrics["ram_size"] = size_calc.get_ram_size()
2727 instance.metrics["rom_size"] = size_calc.get_rom_size()
2728 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2729 else:
2730 instance.metrics["ram_size"] = 0
2731 instance.metrics["rom_size"] = 0
2732 instance.metrics["unrecognized"] = []
2733
2734 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2735
2736 info("Adding tasks to the queue...")
2737 # We can use a with statement to ensure threads are cleaned up promptly
2738 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2739
2740 # start a future for a thread which sends work in through the queue
2741 future_to_test = {
2742 executor.submit(self.add_tasks_to_queue): 'FEEDER DONE'}
2743
2744 while future_to_test:
2745 # check for status of the futures which are currently working
2746 done, _ = concurrent.futures.wait(
2747 future_to_test, timeout=0.25,
2748 return_when=concurrent.futures.FIRST_COMPLETED)
2749
2750 # if there is incoming work, start a new future
2751 while not pipeline.empty():
2752 # fetch a url from the queue
2753 message = pipeline.get()
2754 test = message['test']
2755
2756 # Start the load operation and mark the future with its URL
2757 pb = ProjectBuilder(self, test)
2758 future_to_test[executor.submit(pb.process, message)] = test.name
2759
2760 # process any completed futures
2761 for future in done:
2762 test = future_to_test[future]
2763 try:
2764 data = future.result()
2765 except Exception as exc:
Anas Nashif4f043862019-11-05 06:01:49 -08002766 sys.exit('%r generated an exception: %s' % (test, exc))
2767
Anas Nashif83fc06a2019-06-22 11:04:10 -04002768 else:
2769 if data:
2770 verbose(data)
2771
2772 # remove the now completed future
2773 del future_to_test[future]
2774
2775 if options.enable_size_report and not options.cmake_only:
2776 # Parallelize size calculation
2777 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2778 futures = [executor.submit(calc_one_elf_size, instance)
2779 for instance in self.instances.values()]
2780 concurrent.futures.wait(futures)
2781 else:
2782 for instance in self.instances.values():
2783 instance.metrics["ram_size"] = 0
2784 instance.metrics["rom_size"] = 0
2785 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2786 instance.metrics["unrecognized"] = []
2787
2788
2789 def discard_report(self, filename):
2790
2791 try:
2792 if self.discards is None:
2793 raise SanityRuntimeError("apply_filters() hasn't been run!")
2794 except Exception as e:
2795 error(str(e))
2796 sys.exit(2)
2797
2798 with open(filename, "wt") as csvfile:
2799 fieldnames = ["test", "arch", "platform", "reason"]
2800 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2801 cw.writeheader()
2802 for instance, reason in sorted(self.discards.items()):
2803 rowdict = {"test": instance.testcase.name,
2804 "arch": instance.platform.arch,
2805 "platform": instance.platform.name,
2806 "reason": reason}
2807 cw.writerow(rowdict)
2808
2809
2810 def target_report(self, outdir):
2811 run = "Sanitycheck"
2812 eleTestsuite = None
2813
2814 platforms = {inst.platform.name for _,inst in self.instances.items()}
2815 for platform in platforms:
2816 errors = 0
2817 passes = 0
2818 fails = 0
2819 duration = 0
2820 skips = 0
2821 for _, instance in self.instances.items():
2822 if instance.platform.name != platform:
2823 continue
2824
2825 handler_time = instance.metrics.get('handler_time', 0)
2826 duration += handler_time
2827 for k in instance.results.keys():
2828 if instance.results[k] == 'PASS':
2829 passes += 1
2830 elif instance.results[k] == 'BLOCK':
2831 errors += 1
2832 elif instance.results[k] == 'SKIP':
2833 skips += 1
2834 else:
2835 fails += 1
2836
2837 eleTestsuites = ET.Element('testsuites')
2838 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2839 name=run, time="%f" % duration,
2840 tests="%d" % (errors + passes + fails),
2841 failures="%d" % fails,
2842 errors="%d" % errors, skipped="%d" %skips)
2843
2844 handler_time = 0
2845
2846 # print out test results
2847 for _, instance in self.instances.items():
2848 if instance.platform.name != platform:
2849 continue
2850 handler_time = instance.metrics.get('handler_time', 0)
2851 for k in instance.results.keys():
2852 eleTestcase = ET.SubElement(
2853 eleTestsuite, 'testcase', classname="%s:%s" %(instance.platform.name, os.path.basename(instance.testcase.name)),
2854 name="%s" % (k), time="%f" %handler_time)
2855 if instance.results[k] in ['FAIL', 'BLOCK']:
2856 el = None
2857
2858 if instance.results[k] == 'FAIL':
2859 el = ET.SubElement(
2860 eleTestcase,
2861 'failure',
2862 type="failure",
2863 message="failed")
2864 elif instance.results[k] == 'BLOCK':
2865 el = ET.SubElement(
2866 eleTestcase,
2867 'error',
2868 type="failure",
2869 message="failed")
2870 p = os.path.join(options.outdir, instance.platform.name, instance.testcase.name)
2871 log_file = os.path.join(p, "handler.log")
2872
2873 if os.path.exists(log_file):
2874 with open(log_file, "rb") as f:
2875 log = f.read().decode("utf-8")
2876 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2877 el.text = filtered_string
2878
2879 elif instance.results[k] == 'SKIP':
2880 el = ET.SubElement(
2881 eleTestcase,
2882 'skipped',
2883 type="skipped",
2884 message="Skipped")
2885
2886
2887 result = ET.tostring(eleTestsuites)
2888 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
2889 f.write(result)
2890
2891
2892 def xunit_report(self, filename):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002893 fails = 0
2894 passes = 0
2895 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002896 skips = 0
2897 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04002898
Anas Nashif83fc06a2019-06-22 11:04:10 -04002899 for instance in self.instances.values():
2900 handler_time = instance.metrics.get('handler_time', 0)
2901 duration += handler_time
2902 if instance.status == "failed":
2903 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002904 errors += 1
2905 else:
2906 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04002907 elif instance.status == 'skipped':
2908 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04002909 else:
2910 passes += 1
2911
2912 run = "Sanitycheck"
2913 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002914 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002915
Anas Nashif83fc06a2019-06-22 11:04:10 -04002916 # When we re-run the tests, we re-use the results and update only with
2917 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04002918 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002919 tree = ET.parse(filename)
2920 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002921 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002922 else:
2923 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002924 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04002925 name=run, time="%f" % duration,
2926 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05002927 failures="%d" % fails,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002928 errors="%d" %(errors), skip="%s" %(skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002929
Anas Nashif83fc06a2019-06-22 11:04:10 -04002930 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04002931
Anas Nashif83fc06a2019-06-22 11:04:10 -04002932 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04002933 if append:
2934 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002935 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04002936 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002937 eleTestsuite.remove(tc)
2938
Anas Nashif83fc06a2019-06-22 11:04:10 -04002939 handler_time = 0
2940 if instance.status != "failed" and instance.handler:
2941 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002942
Anas Nashif3ba1d432017-12-05 15:28:44 -05002943 eleTestcase = ET.SubElement(
2944 eleTestsuite, 'testcase', classname="%s:%s" %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002945 (instance.platform.name, instance.testcase.name), name="%s" %
2946 (instance.testcase.name), time="%f" %handler_time)
2947
2948 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05002949 failure = ET.SubElement(
2950 eleTestcase,
2951 'failure',
2952 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002953 message=instance.reason)
2954 p = ("%s/%s/%s" % (options.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002955 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002956 hl = os.path.join(p, "handler.log")
2957 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04002958 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002959 if os.path.exists(hl):
2960 log_file = hl
2961 else:
2962 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002963
Anas Nashifc96c90a2019-02-05 07:38:32 -05002964 if os.path.exists(log_file):
2965 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002966 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002967 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2968 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002969 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002970 elif instance.status == "skipped":
2971 ET.SubElement( eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002972
2973 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002974 with open(filename, 'wb') as report:
2975 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002976
Andrew Boie6acbe632015-07-17 12:03:52 -07002977
Anas Nashif83fc06a2019-06-22 11:04:10 -04002978 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08002979 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002980 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002981 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002982 "rom_size"]
2983 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2984 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002985 for instance in sorted(self.instances.values()):
2986 rowdict = {"test": instance.testcase.name,
2987 "arch": instance.platform.arch,
2988 "platform": instance.platform.name,
2989 "extra_args": " ".join(instance.testcase.extra_args),
2990 "handler": instance.platform.simulation}
2991
2992 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07002993 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04002994 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07002995 else:
2996 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04002997 if instance.handler:
2998 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
2999 ram_size = instance.metrics.get("ram_size", 0)
3000 rom_size = instance.metrics.get("rom_size", 0)
3001 rowdict["ram_size"] = ram_size
3002 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003003 cw.writerow(rowdict)
3004
3005
3006def parse_arguments():
3007
Anas Nashif3ba1d432017-12-05 15:28:44 -05003008 parser = argparse.ArgumentParser(
3009 description=__doc__,
3010 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003011 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003012
Marc Herbert932a33a2019-03-12 11:37:53 -07003013 case_select = parser.add_argument_group("Test case selection",
3014 """
3015Artificially long but functional example:
3016 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003017 --testcase-root tests/ztest/base \\
3018 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003019 --test tests/ztest/base/testing.ztest.verbose_0 \\
3020 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3021
3022 "kernel.fifo.poll" is one of the test section names in
3023 __/fifo_api/testcase.yaml
3024 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003025
Anas Nashif07d54c02018-07-21 19:29:08 -05003026 parser.add_argument("--force-toolchain", action="store_true",
3027 help="Do not filter based on toolchain, use the set "
3028 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003029 parser.add_argument(
3030 "-p", "--platform", action="append",
3031 help="Platform filter for testing. This option may be used multiple "
3032 "times. Testcases will only be built/run on the platforms "
3033 "specified. If this option is not used, then platforms marked "
3034 "as default in the platform metadata file will be chosen "
3035 "to build and test. ")
3036 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003037 "-a", "--arch", action="append",
3038 help="Arch filter for testing. Takes precedence over --platform. "
3039 "If unspecified, test all arches. Multiple invocations "
3040 "are treated as a logical 'or' relationship")
3041 parser.add_argument(
3042 "-t", "--tag", action="append",
3043 help="Specify tags to restrict which tests to run by tag value. "
3044 "Default is to not do any tag filtering. Multiple invocations "
3045 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003046 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003047 help="Specify tags of tests that should not run. "
3048 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003049 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003050 "-f",
3051 "--only-failed",
3052 action="store_true",
3053 help="Run only those tests that failed the previous sanity check "
3054 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003055
Anas Nashif3ba1d432017-12-05 15:28:44 -05003056 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003057 "--retry-failed", type=int, default=0,
3058 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003059
Marc Herbert0c465bb2019-03-11 17:28:36 -07003060 test_xor_subtest = case_select.add_mutually_exclusive_group()
3061
3062 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003063 "-s", "--test", action="append",
3064 help="Run only the specified test cases. These are named by "
Marc Herberte5cedca2019-04-08 14:02:34 -07003065 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003066
Marc Herbert0c465bb2019-03-11 17:28:36 -07003067 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003068 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003069 help="""Recursively find sub-test functions and run the entire
3070 test section where they were found, including all sibling test
3071 functions. Sub-tests are named by:
3072 section.name.in.testcase.yaml.function_name_without_test_prefix
3073 Example: kernel.fifo.poll.fifo_loop
3074 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003075
Anas Nashif3ba1d432017-12-05 15:28:44 -05003076 parser.add_argument(
3077 "-l", "--all", action="store_true",
3078 help="Build/test on all platforms. Any --platform arguments "
3079 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003080
Anas Nashif3ba1d432017-12-05 15:28:44 -05003081 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003082 "-o", "--report-dir",
3083 help="""Output reports containing results of the test run into the
3084 specified directory.
3085 The output will be both in CSV and JUNIT format
3086 (sanitycheck.csv and sanitycheck.xml).
3087 """)
3088
Anas Nashif3ba1d432017-12-05 15:28:44 -05003089 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003090 "--report-name",
3091 help="""Create a report with a custom name.
3092 """)
3093
3094 parser.add_argument("--detailed-report",
3095 action="store",
3096 metavar="FILENAME",
3097 help="""Generate a junit report with detailed testcase results.
3098 Unlike the CSV file produced by --testcase-report, this XML
3099 report includes only tests which have run and none which were
3100 merely built. If an image with multiple tests crashes early then
3101 later tests are not accounted for either.""")
3102
3103 parser.add_argument("--report-excluded",
3104 action="store_true",
3105 help="""List all tests that are never run based on current scope and
3106 coverage. If you are looking for accurate results, run this with
3107 --all, but this will take a while...""")
3108
Daniel Leung7f850102016-04-08 11:07:32 -07003109 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003110 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003111
Anas Nashif3ba1d432017-12-05 15:28:44 -05003112 parser.add_argument(
3113 "-B", "--subset",
3114 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
3115 "3/5 means run the 3rd fifth of the total. "
3116 "This option is useful when running a large number of tests on "
3117 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003118
3119 parser.add_argument(
3120 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003121 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003122
Anas Nashif3ba1d432017-12-05 15:28:44 -05003123 parser.add_argument(
3124 "-y", "--dry-run", action="store_true",
3125 help="Create the filtered list of test cases, but don't actually "
3126 "run them. Useful if you're just interested in "
3127 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07003128
Anas Nashif75547e22018-02-24 08:32:14 -06003129 parser.add_argument("--list-tags", action="store_true",
3130 help="list all tags in selected tests")
3131
Marc Herbertedf17592019-03-08 12:39:11 -08003132 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07003133 help="""List of all sub-test functions recursively found in
3134 all --testcase-root arguments. Note different sub-tests can share
3135 the same section name and come from different directories.
3136 The output is flattened and reports --sub-test names only,
3137 not their directories. For instance net.socket.getaddrinfo_ok
3138 and net.socket.fd_set belong to different directories.
3139 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003140
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003141 parser.add_argument("--export-tests", action="store",
3142 metavar="FILENAME",
3143 help="Export tests case meta-data to a file in CSV format.")
3144
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003145
Anas Nashif654ec5982019-04-11 08:38:21 -04003146 parser.add_argument("--timestamps",
3147 action="store_true",
3148 help="Print all messages with time stamps")
3149
Anas Nashif3ba1d432017-12-05 15:28:44 -05003150 parser.add_argument(
3151 "-r", "--release", action="store_true",
3152 help="Update the benchmark database with the results of this test "
3153 "run. Intended to be run by CI when tagging an official "
3154 "release. This database is used as a basis for comparison "
3155 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003156 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003157 help="Treat warning conditions as errors")
3158 parser.add_argument(
3159 "-v",
3160 "--verbose",
3161 action="count",
3162 default=0,
3163 help="Emit debugging information, call multiple times to increase "
3164 "verbosity")
3165 parser.add_argument(
3166 "-i", "--inline-logs", action="store_true",
3167 help="Upon test failure, print relevant log data to stdout "
3168 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003169 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003170 help="log also to file")
3171 parser.add_argument(
3172 "-m", "--last-metrics", action="store_true",
3173 help="Instead of comparing metrics from the last --release, "
3174 "compare with the results of the previous sanity check "
3175 "invocation")
3176 parser.add_argument(
3177 "-u",
3178 "--no-update",
3179 action="store_true",
3180 help="do not update the results of the last run of the sanity "
3181 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003182 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003183 "-F",
3184 "--load-tests",
3185 metavar="FILENAME",
3186 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003187 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003188
Marc Herbertedf17592019-03-08 12:39:11 -08003189 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003190 "-E",
3191 "--save-tests",
3192 metavar="FILENAME",
3193 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003194 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003195
Andy Doancbecadd2019-02-08 10:19:10 -06003196 test_or_build = parser.add_mutually_exclusive_group()
3197 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003198 "-b", "--build-only", action="store_true",
3199 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003200
Andy Doancbecadd2019-02-08 10:19:10 -06003201 test_or_build.add_argument(
3202 "--test-only", action="store_true",
3203 help="""Only run device tests with current artifacts, do not build
3204 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003205 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003206 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003207 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003208
3209 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003210 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003211 help="Number of jobs for building, defaults to number of CPU threads, "
3212 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003213
3214 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06003215 "--show-footprint", action="store_true",
3216 help="Show footprint statistics and deltas since last release."
3217 )
3218 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003219 "-H", "--footprint-threshold", type=float, default=5,
3220 help="When checking test case footprint sizes, warn the user if "
3221 "the new app size is greater then the specified percentage "
3222 "from the last release. Default is 5. 0 to warn on any "
3223 "increase on app size")
3224 parser.add_argument(
3225 "-D", "--all-deltas", action="store_true",
3226 help="Show all footprint deltas, positive or negative. Implies "
3227 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003228 parser.add_argument(
3229 "-O", "--outdir",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003230 default=os.path.join(os.getcwd(),"sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003231 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05003232 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003233 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003234 parser.add_argument(
3235 "-n", "--no-clean", action="store_true",
3236 help="Do not delete the outdir before building. Will result in "
3237 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08003238 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003239 "-T", "--testcase-root", action="append", default=[],
3240 help="Base directory to recursively search for test cases. All "
3241 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07003242 "called multiple times. Defaults to the 'samples/' and "
3243 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003244
Anas Nashif3ba1d432017-12-05 15:28:44 -05003245 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3246 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003247
Anas Nashif3ba1d432017-12-05 15:28:44 -05003248 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003249 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003250 help="""Directory to search for board configuration files. All .yaml
3251files in the directory will be processed. The directory should have the same
3252structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3253
Anas Nashif3ba1d432017-12-05 15:28:44 -05003254 parser.add_argument(
3255 "-z", "--size", action="append",
3256 help="Don't run sanity checks. Instead, produce a report to "
3257 "stdout detailing RAM/ROM sizes on the specified filenames. "
3258 "All other command line arguments ignored.")
3259 parser.add_argument(
3260 "-S", "--enable-slow", action="store_true",
3261 help="Execute time-consuming test cases that have been marked "
3262 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003263 parser.add_argument(
3264 "--disable-unrecognized-section-test", action="store_true",
3265 default=False,
3266 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003267 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003268 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003269 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003270 parser.add_argument("--disable-asserts", action="store_false",
3271 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003272 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003273 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003274 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003275 parser.add_argument("--enable-size-report", action="store_true",
3276 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003277
3278 parser.add_argument(
3279 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003280 help="""Extra CMake cache entries to define when building test cases.
3281 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003282 prefixed with -D before being passed to CMake.
3283
3284 E.g
3285 "sanitycheck -x=USE_CCACHE=0"
3286 will translate to
3287 "cmake -DUSE_CCACHE=0"
3288
3289 which will ultimately disable ccache.
3290 """
3291 )
Michael Scott421ce462019-06-18 09:37:46 -07003292
Andy Doan79c48842019-02-08 10:09:04 -06003293 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003294 "--device-testing", action="store_true",
3295 help="Test on device directly. Specify the serial device to "
3296 "use with the --device-serial option.")
3297
3298 parser.add_argument(
3299 "-X", "--fixture", action="append", default=[],
3300 help="Specify a fixture that a board might support")
3301 parser.add_argument(
3302 "--device-serial",
3303 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3304
3305 parser.add_argument("--generate-hardware-map",
3306 help="""Probe serial devices connected to this platform
3307 and create a hardware map file to be used with
3308 --device-testing
3309 """)
3310
3311 parser.add_argument("--hardware-map",
3312 help="""Load hardware map from a file. This will be used
3313 for testing on hardware that is listed in the file.
3314 """)
3315
3316 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003317 "--west-flash", nargs='?', const=[],
3318 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003319 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003320
Michael Scott4ca54392019-07-09 14:21:30 -07003321 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003322 --west-flash="--board-id=foobar,--erase"
3323 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003324
3325 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003326 """
3327 )
Michael Scott421ce462019-06-18 09:37:46 -07003328 parser.add_argument(
3329 "--west-runner",
3330 help="""Uses the specified west runner instead of default when running
3331 with --west-flash.
3332
3333 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3334 --west-flash --west-runner=pyocd"
3335 will translate to "west flash --runner pyocd"
3336
3337 NOTE: west-flash must be enabled to use this option.
3338 """
3339 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003340
3341 valgrind_asan_group = parser.add_mutually_exclusive_group()
3342
3343 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003344 "--enable-valgrind", action="store_true",
3345 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003346 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003347 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003348 configuration and is mutual exclusive with --enable-asan.
3349 """)
3350
3351 valgrind_asan_group.add_argument(
3352 "--enable-asan", action="store_true",
3353 help="""Enable address sanitizer to check for several memory access
3354 errors. Libasan needs to be installed on the host. This option only
3355 works with host binaries such as those generated for the native_posix
3356 configuration and is mutual exclusive with --enable-valgrind.
3357 """)
3358
3359 parser.add_argument(
3360 "--enable-lsan", action="store_true",
3361 help="""Enable leak sanitizer to check for heap memory leaks.
3362 Libasan needs to be installed on the host. This option only
3363 works with host binaries such as those generated for the native_posix
3364 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003365 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003366
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003367 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003368 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003369
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003370 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003371 help="Generate coverage reports. Implies "
3372 "--enable_coverage and --enable-slow")
Andrew Boie6acbe632015-07-17 12:03:52 -07003373
Andrew Boie8047a6f2019-07-02 15:43:29 -07003374 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003375 help="Plarforms to run coverage reports on. "
Andrew Boie8047a6f2019-07-02 15:43:29 -07003376 "This option may be used multiple times. "
3377 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003378
Anas Nashif83fc06a2019-06-22 11:04:10 -04003379 parser.add_argument("--gcov-tool", default=None,
3380 help="Path to the gcov tool to use for code coverage "
3381 "reports")
3382
Andrew Boie6acbe632015-07-17 12:03:52 -07003383 return parser.parse_args()
3384
Anas Nashif3ba1d432017-12-05 15:28:44 -05003385
Andrew Boie6acbe632015-07-17 12:03:52 -07003386def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003387 filename = os.path.relpath(os.path.realpath(filename))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003388 if options.inline_logs:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003389 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003390
3391 try:
3392 with open(filename) as fp:
3393 data = fp.read()
3394 except Exception as e:
3395 data = "Unable to read log data (%s)\n" % (str(e))
3396
3397 sys.stdout.write(data)
3398 if log_file:
3399 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003400 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003401 else:
Anas Nashifc1ea4522019-10-11 07:32:45 -07003402 info("\n\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003403
Ulf Magnussone73d2862019-10-29 11:54:02 +01003404
3405def log_info_file(instance):
3406 build_dir = instance.build_dir
3407 h_log = "{}/handler.log".format(build_dir)
3408 b_log = "{}/build.log".format(build_dir)
3409 v_log = "{}/valgrind.log".format(build_dir)
3410
3411 if os.path.exists(v_log) and "Valgrind" in instance.reason:
3412 log_info("{}".format(v_log))
3413 elif os.path.exists(h_log):
3414 log_info("{}".format(h_log))
3415 else:
3416 log_info("{}".format(b_log))
3417
3418
Andrew Boiebbd670c2015-08-17 13:16:11 -07003419def size_report(sc):
3420 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07003421 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003422 for i in range(len(sc.sections)):
3423 v = sc.sections[i]
3424
Andrew Boie73b4ee62015-10-07 11:33:22 -07003425 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
3426 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3427 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003428
Andrew Boie73b4ee62015-10-07 11:33:22 -07003429 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003430 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003431 info("")
3432
Anas Nashiff29087e2019-01-25 09:37:38 -05003433def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003434 if VERBOSE:
3435 print("Working on %s" %intput_file)
3436 extracted_coverage_info = {}
3437 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003438 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003439 with open(intput_file, 'r') as fp:
3440 for line in fp.readlines():
3441 if re.search("GCOV_COVERAGE_DUMP_START", line):
3442 capture_data = True
3443 continue
3444 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003445 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003446 break
3447 # Loop until the coverage data is found.
3448 if not capture_data:
3449 continue
3450 if line.startswith("*"):
3451 sp = line.split("<")
3452 if len(sp) > 1:
3453 # Remove the leading delimiter "*"
3454 file_name = sp[0][1:]
3455 # Remove the trailing new line char
3456 hex_dump = sp[1][:-1]
3457 else:
3458 continue
3459 else:
3460 continue
3461 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003462 if not capture_data:
3463 capture_complete = True
3464 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003465
3466def create_gcda_files(extracted_coverage_info):
3467 if VERBOSE:
3468 print("Generating gcda files")
3469 for filename, hexdump_val in extracted_coverage_info.items():
3470 # if kobject_hash is given for coverage gcovr fails
3471 # hence skipping it problem only in gcovr v4.1
3472 if "kobject_hash" in filename:
3473 filename = (filename[:-4]) +"gcno"
3474 try:
3475 os.remove(filename)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003476 except Exception:
Anas Nashifdb9592a2018-10-08 10:19:41 -04003477 pass
3478 continue
3479
3480 with open(filename, 'wb') as fp:
3481 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003482
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003483def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003484
3485 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003486 gcov_data = retrieve_gcov_data(filename)
3487 capture_complete = gcov_data['complete']
3488 extracted_coverage_info = gcov_data['data']
3489 if capture_complete:
3490 create_gcda_files(extracted_coverage_info)
3491 verbose("Gcov data captured: {}".format(filename))
3492 else:
3493 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003494
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003495 gcov_tool = options.gcov_tool
3496
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003497 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3498 coveragefile = os.path.join(outdir, "coverage.info")
3499 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003500 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3501 "--capture", "--directory", outdir,
3502 "--rc", "lcov_branch_coverage=1",
3503 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003504 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003505 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003506 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003507 "--output-file", ztestfile,
3508 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3509
Anas Nashif3cbffef2018-11-07 23:50:54 -05003510 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003511 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003512 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3513 "--output-file", ztestfile,
3514 "--rc", "lcov_branch_coverage=1"],
3515 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003516 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003517 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003518 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003519
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003520 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003521 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003522 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3523 coveragefile, i, "--output-file",
3524 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003525 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003526
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003527 #The --ignore-errors source option is added to avoid it exiting due to
3528 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003529 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003530 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003531 "-output-directory",
3532 os.path.join(outdir, "coverage")] + files,
3533 stdout=coveragelog)
3534 if ret==0:
3535 info("HTML report generated: %s"%
Anas Nashif83fc06a2019-06-22 11:04:10 -04003536 os.path.join(outdir, "coverage","index.html"))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003537
Anas Nashif83fc06a2019-06-22 11:04:10 -04003538def get_generator():
3539 if options.ninja:
3540 generator_cmd = "ninja"
3541 generator = "Ninja"
3542 else:
3543 generator_cmd = "make"
3544 generator = "Unix Makefiles"
3545 return generator_cmd, generator
3546
3547
3548def export_tests(filename, tests):
3549 with open(filename, "wt") as csvfile:
3550 fieldnames = ['section', 'subsection', 'title', 'reference']
3551 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3552 for test in tests:
3553 data = test.split(".")
3554 subsec = " ".join(data[1].split("_")).title()
3555 rowdict = {
3556 "section": data[0].capitalize(),
3557 "subsection": subsec,
3558 "title": test,
3559 "reference": test
3560 }
3561 cw.writerow(rowdict)
3562
3563def get_unique_tests(suite):
3564 unq = []
3565 run_individual_tests = []
3566 for _, tc in suite.testcases.items():
3567 for c in tc.cases:
3568 if options.sub_test and c in options.sub_test:
3569 if tc.name not in run_individual_tests:
3570 run_individual_tests.append(tc.name)
3571 unq.append(c)
3572
3573 return unq
3574
3575
3576
3577def native_and_unit_first(a, b):
3578 if a[0].startswith('unit_testing'):
3579 return -1
3580 if b[0].startswith('unit_testing'):
3581 return 1
3582 if a[0].startswith('native_posix'):
3583 return -1
3584 if b[0].startswith('native_posix'):
3585 return 1
3586 if a[0].split("/",1)[0].endswith("_bsim"):
3587 return -1
3588 if b[0].split("/",1)[0].endswith("_bsim"):
3589 return 1
3590
3591 return (a > b) - (a < b)
3592
3593
3594run_individual_tests = None
3595options = None
Andrew Boiebbd670c2015-08-17 13:16:11 -07003596
Andrew Boie6acbe632015-07-17 12:03:52 -07003597def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003598 start_time = time.time()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003599 global VERBOSE, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003600 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003601 global run_individual_tests
Andrew Boie1578ef72019-07-03 10:19:29 -07003602
Anas Nashife10b6512017-12-30 13:01:45 -05003603 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003604
Anas Nashif83fc06a2019-06-22 11:04:10 -04003605
3606 if options.generate_hardware_map:
3607 from serial.tools import list_ports
3608 serial_devices = list_ports.comports()
3609 filtered = []
3610 for d in serial_devices:
3611 if d.manufacturer in ['ARM', 'SEGGER', 'MBED', 'STMicroelectronics', 'Atmel Corp.']:
3612 s_dev = {}
3613 s_dev['platform'] = "unknown"
3614 s_dev['id'] = d.serial_number
3615 s_dev['serial'] = d.device
3616 s_dev['product'] = d.product
3617 if s_dev['product'] in ['DAPLink CMSIS-DAP', 'MBED CMSIS-DAP']:
3618 s_dev['runner'] = "pyocd"
3619 else:
3620 s_dev['runner'] = "unknown"
3621 s_dev['available'] = True
3622 s_dev['connected'] = True
3623 filtered.append(s_dev)
3624 else:
3625 print("Unsupported device (%s): %s" %(d.manufacturer, d))
3626
3627 if os.path.exists(options.generate_hardware_map):
3628 # use existing map
3629
3630 with open(options.generate_hardware_map, 'r') as yaml_file:
3631 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3632 # disconnect everything
3633 for h in hwm:
3634 h['connected'] = False
3635
3636 for d in filtered:
3637 for h in hwm:
3638 if d['id'] == h['id'] and d['product'] == h['product']:
3639 print("Already in map: %s (%s)" %(d['product'], d['id']))
3640 h['connected'] = True
3641 h['serial'] = d['serial']
3642 d['match'] = True
3643
3644 new = list(filter(lambda n: not n.get('match', False), filtered))
3645 hwm = hwm + new
3646
3647 #import pprint
3648 #pprint.pprint(hwm)
3649 with open(options.generate_hardware_map, 'w') as yaml_file:
3650 yaml.dump(hwm, yaml_file, default_flow_style=False)
3651
3652
3653 else:
3654 # create new file
3655 with open(options.generate_hardware_map, 'w') as yaml_file:
3656 yaml.dump(filtered, yaml_file, default_flow_style=False)
3657
3658 return
3659
3660
Michael Scott421ce462019-06-18 09:37:46 -07003661 if options.west_runner and not options.west_flash:
3662 error("west-runner requires west-flash to be enabled")
3663 sys.exit(1)
3664
Michael Scott4ca54392019-07-09 14:21:30 -07003665 if options.west_flash and not options.device_testing:
3666 error("west-flash requires device-testing to be enabled")
3667 sys.exit(1)
3668
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003669 if options.coverage:
3670 options.enable_coverage = True
Andrew Boie8047a6f2019-07-02 15:43:29 -07003671 options.enable_slow = True
3672 if not options.coverage_platform:
3673 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003674
Anas Nashife10b6512017-12-30 13:01:45 -05003675 if options.size:
3676 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003677 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003678 sys.exit(0)
3679
Anas Nashife10b6512017-12-30 13:01:45 -05003680 VERBOSE += options.verbose
Anas Nashif83fc06a2019-06-22 11:04:10 -04003681
Anas Nashife10b6512017-12-30 13:01:45 -05003682 if options.log_file:
3683 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003684
Anas Nashife10b6512017-12-30 13:01:45 -05003685 if options.subset:
3686 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003687 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003688 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003689 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003690 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003691 return
3692
Anas Nashif83fc06a2019-06-22 11:04:10 -04003693 # Cleanup
3694
3695 if options.no_clean or options.only_failed or options.test_only:
3696 if os.path.exists(options.outdir):
3697 info("Keeping artifacts untouched")
3698 elif os.path.exists(options.outdir):
3699 for i in range(1,100):
3700 new_out = options.outdir + ".{}".format(i)
3701 if not os.path.exists(new_out):
3702 info("Renaming output directory to {}".format(new_out))
3703 shutil.move(options.outdir, new_out)
3704 break
3705 #shutil.rmtree("%s.old" %options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003706
Anas Nashife10b6512017-12-30 13:01:45 -05003707 if not options.testcase_root:
3708 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003709 os.path.join(ZEPHYR_BASE, "samples")]
3710
Anas Nashif83fc06a2019-06-22 11:04:10 -04003711 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
3712 suite.add_testcases()
3713 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04003714
Anas Nashif83fc06a2019-06-22 11:04:10 -04003715 if options.device_testing:
3716 if options.hardware_map:
3717 suite.load_hardware_map(options.hardware_map)
3718 if not options.platform:
3719 options.platform = []
3720 for platform in suite.connected_hardware:
3721 if platform['connected']:
3722 options.platform.append(platform['platform'])
3723
3724 elif options.device_serial: #back-ward compatibility
3725 if options.platform and len(options.platform) == 1:
3726 suite.load_hardware_map_from_cmdline(options.device_serial,
3727 options.platform[0])
3728 else:
3729 error("""When --device-testing is used with --device-serial, only one
3730 platform is allowed""")
3731
3732
3733
3734 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05003735 sys.exit(1)
3736
Anas Nashif75547e22018-02-24 08:32:14 -06003737 if options.list_tags:
3738 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003739 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06003740 tags = tags.union(tc.tags)
3741
3742 for t in tags:
3743 print("- {}".format(t))
3744
3745 return
3746
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003747 if options.export_tests:
3748 cnt = 0
3749 unq = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04003750 for _, tc in suite.testcases.items():
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003751 for c in tc.cases:
3752 unq.append(c)
3753
3754 tests = sorted(set(unq))
3755 export_tests(options.export_tests, tests)
3756 return
3757
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003758 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003759
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003760 if options.test:
3761 run_individual_tests = options.test
3762
Anas Nashif49b22d42019-06-14 13:45:34 -04003763 if options.list_tests or options.sub_test:
3764 cnt = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04003765 unq = get_unique_tests(suite)
Anas Nashif49b22d42019-06-14 13:45:34 -04003766
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003767 if options.sub_test:
3768 if run_individual_tests:
3769 info("Running the following tests:")
3770 for t in run_individual_tests:
3771 print(" - {}".format(t))
3772 else:
3773 info("Tests not found")
3774 return
3775
3776 elif options.list_tests:
3777 for u in sorted(set(unq)):
3778 cnt = cnt + 1
3779 print(" - {}".format(u))
3780 print("{} total.".format(cnt))
3781 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003782
Anas Nashifbd166f42017-09-02 12:32:08 -04003783 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04003784
3785 if options.only_failed:
3786 suite.get_last_failed()
3787 elif options.load_tests:
3788 suite.load_from_file(options.load_tests)
3789 elif options.test_only:
3790 last_run = os.path.join(options.outdir, "sanitycheck.csv")
3791 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04003792 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003793 discards = suite.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003794
Anas Nashif30551f42018-01-12 21:56:59 -05003795 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003796 # if we are using command line platform filter, no need to list every
3797 # other platform as excluded, we know that already.
3798 # Show only the discards that apply to the selected platforms on the
3799 # command line
3800
Andrew Boie08ce5a52016-02-22 13:28:10 -08003801 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003802 if options.platform and i.platform.name not in options.platform:
3803 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003804 debug(
3805 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3806 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003807 i.testcase.name,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003808 COLOR_YELLOW,
3809 COLOR_NORMAL,
3810 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003811
Anas Nashif49b22d42019-06-14 13:45:34 -04003812 if options.report_excluded:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003813 all_tests = set(get_unique_tests(suite))
Anas Nashif49b22d42019-06-14 13:45:34 -04003814 to_be_run = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003815 for i,p in suite.instances.items():
3816 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04003817
Anas Nashif83fc06a2019-06-22 11:04:10 -04003818 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04003819 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003820 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04003821 print("- {}".format(not_run))
3822
3823 return
3824
Anas Nashife10b6512017-12-30 13:01:45 -05003825 if options.subset:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003826 #suite.instances = OrderedDict(sorted(suite.instances.items(),
3827 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05003828 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003829 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003830 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003831 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003832 if subset == sets:
3833 end = total
3834 else:
3835 end = start + per_set
3836
Anas Nashif83fc06a2019-06-22 11:04:10 -04003837 sliced_instances = islice(suite.instances.items(), start, end)
3838 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003839
Andrew Boie6acbe632015-07-17 12:03:52 -07003840
Anas Nashif83fc06a2019-06-22 11:04:10 -04003841 if options.save_tests:
3842 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07003843 return
3844
Anas Nashif83fc06a2019-06-22 11:04:10 -04003845 info("%d test configurations selected, %d configurations discarded due to filters." %
3846 (len(suite.instances), len(discards)))
3847
3848 if options.dry_run:
3849 duration = time.time() - start_time
3850 info("Completed in %d seconds" % (duration))
3851 return
3852
3853 retries = options.retry_failed + 1
3854 completed = 0
3855
3856 suite.update()
3857 suite.start_time = start_time
3858
3859 while True:
3860 completed += 1
3861
3862 if completed > 1:
3863 info("%d Iteration:" %(completed ))
3864 time.sleep(60) # waiting for the system to settle down
3865 suite.total_done = suite.total_tests - suite.total_failed
3866 suite.total_failed = 0
3867
3868 suite.execute()
Anas Nashif654ec5982019-04-11 08:38:21 -04003869 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003870
Anas Nashif83fc06a2019-06-22 11:04:10 -04003871 retries = retries - 1
3872 if retries == 0 or suite.total_failed == 0:
3873 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003874
Anas Nashif83fc06a2019-06-22 11:04:10 -04003875 suite.misc_reports(options.compare_report, options.show_footprint,
3876 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07003877
Anas Nashife10b6512017-12-30 13:01:45 -05003878 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003879 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003880 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07003881
3882 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003883 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003884 if ts_plat and (ts_plat.type in {"native", "unit"}):
3885 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07003886
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003887 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07003888 options.gcov_tool = "gcov"
3889 else:
3890 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
3891 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
3892
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003893 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003894 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003895
Anas Nashif83fc06a2019-06-22 11:04:10 -04003896 suite.duration = time.time() - start_time
3897 suite.summary(options.disable_unrecognized_section_test)
Andrew Boie6acbe632015-07-17 12:03:52 -07003898
Anas Nashif83fc06a2019-06-22 11:04:10 -04003899 if options.device_testing:
3900 print("\nHardware distribution summary:\n")
3901 for p in suite.connected_hardware:
3902 if p['connected']:
3903 print("%s (%s): %d" %(p['platform'], p.get('id', None), p['counter']))
3904
Flavio Ceolinca1feea2019-10-22 14:03:48 -07003905 suite.save_reports()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003906 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003907 sys.exit(1)
3908
3909if __name__ == "__main__":
3910 main()