blob: e83c346f421743216bdb4601c7895eb300ce9259 [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
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700202import logging
Anas Nashif83fc06a2019-06-22 11:04:10 -0400203
204
205hw_map_local = threading.Lock()
206
Anas Nashif3ba1d432017-12-05 15:28:44 -0500207
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700208log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500209logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700210
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +0300211ZEPHYR_BASE = os.environ.get("ZEPHYR_BASE")
212if not ZEPHYR_BASE:
Anas Nashif427cdd32015-08-06 07:25:42 -0400213 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700214 exit(1)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700215
Marc Herbert1c8632c2019-04-15 17:58:45 -0700216# Use this for internal comparisons; that's what canonicalization is
217# for. Don't use it when invoking other components of the build system
218# to avoid confusing and hard to trace inconsistencies in error messages
219# and logs, generated Makefiles, etc. compared to when users invoke these
220# components directly.
221# Note "normalization" is different from canonicalization, see os.path.
222canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
223
Andrew Boie3ea78922016-03-24 14:46:00 -0700224sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
225
Anas Nashif83fc06a2019-06-22 11:04:10 -0400226from sanity_chk import scl
227from sanity_chk import expr_parser
228
Andrew Boie3ea78922016-03-24 14:46:00 -0700229
Andrew Boie6acbe632015-07-17 12:03:52 -0700230VERBOSE = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400231
Andrew Boie6acbe632015-07-17 12:03:52 -0700232RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
233 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700234
235if os.isatty(sys.stdout.fileno()):
236 TERMINAL = True
237 COLOR_NORMAL = '\033[0m'
238 COLOR_RED = '\033[91m'
239 COLOR_GREEN = '\033[92m'
240 COLOR_YELLOW = '\033[93m'
241else:
242 TERMINAL = False
243 COLOR_NORMAL = ""
244 COLOR_RED = ""
245 COLOR_GREEN = ""
246 COLOR_YELLOW = ""
247
Anas Nashif45a97862019-01-09 08:46:42 -0500248class CMakeCacheEntry:
249 '''Represents a CMake cache entry.
250
251 This class understands the type system in a CMakeCache.txt, and
252 converts the following cache types to Python types:
253
254 Cache Type Python type
255 ---------- -------------------------------------------
256 FILEPATH str
257 PATH str
258 STRING str OR list of str (if ';' is in the value)
259 BOOL bool
260 INTERNAL str OR list of str (if ';' is in the value)
261 ---------- -------------------------------------------
262 '''
263
264 # Regular expression for a cache entry.
265 #
266 # CMake variable names can include escape characters, allowing a
267 # wider set of names than is easy to match with a regular
268 # expression. To be permissive here, use a non-greedy match up to
269 # the first colon (':'). This breaks if the variable name has a
270 # colon inside, but it's good enough.
271 CACHE_ENTRY = re.compile(
272 r'''(?P<name>.*?) # name
273 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
274 =(?P<value>.*) # value
275 ''', re.X)
276
277 @classmethod
278 def _to_bool(cls, val):
279 # Convert a CMake BOOL string into a Python bool.
280 #
281 # "True if the constant is 1, ON, YES, TRUE, Y, or a
282 # non-zero number. False if the constant is 0, OFF, NO,
283 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
284 # the suffix -NOTFOUND. Named boolean constants are
285 # case-insensitive. If the argument is not one of these
286 # constants, it is treated as a variable."
287 #
288 # https://cmake.org/cmake/help/v3.0/command/if.html
289 val = val.upper()
290 if val in ('ON', 'YES', 'TRUE', 'Y'):
291 return 1
292 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
293 return 0
294 elif val.endswith('-NOTFOUND'):
295 return 0
296 else:
297 try:
298 v = int(val)
299 return v != 0
300 except ValueError as exc:
301 raise ValueError('invalid bool {}'.format(val)) from exc
302
303 @classmethod
304 def from_line(cls, line, line_no):
305 # Comments can only occur at the beginning of a line.
306 # (The value of an entry could contain a comment character).
307 if line.startswith('//') or line.startswith('#'):
308 return None
309
310 # Whitespace-only lines do not contain cache entries.
311 if not line.strip():
312 return None
313
314 m = cls.CACHE_ENTRY.match(line)
315 if not m:
316 return None
317
318 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
319 if type_ == 'BOOL':
320 try:
321 value = cls._to_bool(value)
322 except ValueError as exc:
323 args = exc.args + ('on line {}: {}'.format(line_no, line),)
324 raise ValueError(args) from exc
Anas Nashif83fc06a2019-06-22 11:04:10 -0400325 elif type_ in ['STRING','INTERNAL']:
Anas Nashif45a97862019-01-09 08:46:42 -0500326 # If the value is a CMake list (i.e. is a string which
327 # contains a ';'), convert to a Python list.
328 if ';' in value:
329 value = value.split(';')
330
331 return CMakeCacheEntry(name, value)
332
333 def __init__(self, name, value):
334 self.name = name
335 self.value = value
336
337 def __str__(self):
338 fmt = 'CMakeCacheEntry(name={}, value={})'
339 return fmt.format(self.name, self.value)
340
341
342class CMakeCache:
343 '''Parses and represents a CMake cache file.'''
344
345 @staticmethod
346 def from_file(cache_file):
347 return CMakeCache(cache_file)
348
349 def __init__(self, cache_file):
350 self.cache_file = cache_file
351 self.load(cache_file)
352
353 def load(self, cache_file):
354 entries = []
355 with open(cache_file, 'r') as cache:
356 for line_no, line in enumerate(cache):
357 entry = CMakeCacheEntry.from_line(line, line_no)
358 if entry:
359 entries.append(entry)
360 self._entries = OrderedDict((e.name, e) for e in entries)
361
362 def get(self, name, default=None):
363 entry = self._entries.get(name)
364 if entry is not None:
365 return entry.value
366 else:
367 return default
368
369 def get_list(self, name, default=None):
370 if default is None:
371 default = []
372 entry = self._entries.get(name)
373 if entry is not None:
374 value = entry.value
375 if isinstance(value, list):
376 return value
377 elif isinstance(value, str):
378 return [value] if value else []
379 else:
380 msg = 'invalid value {} type {}'
381 raise RuntimeError(msg.format(value, type(value)))
382 else:
383 return default
384
385 def __contains__(self, name):
386 return name in self._entries
387
388 def __getitem__(self, name):
389 return self._entries[name].value
390
391 def __setitem__(self, name, entry):
392 if not isinstance(entry, CMakeCacheEntry):
393 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
394 raise TypeError(msg.format(type(entry), entry))
395 self._entries[name] = entry
396
397 def __delitem__(self, name):
398 del self._entries[name]
399
400 def __iter__(self):
401 return iter(self._entries.values())
402
Andrew Boie6acbe632015-07-17 12:03:52 -0700403class SanityCheckException(Exception):
404 pass
405
Anas Nashif3ba1d432017-12-05 15:28:44 -0500406
Andrew Boie6acbe632015-07-17 12:03:52 -0700407class SanityRuntimeError(SanityCheckException):
408 pass
409
Andrew Boie6acbe632015-07-17 12:03:52 -0700410class ConfigurationError(SanityCheckException):
411 def __init__(self, cfile, message):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400412 SanityCheckException.__init__(self, cfile + ": " + message)
Andrew Boie6acbe632015-07-17 12:03:52 -0700413
Anas Nashif83fc06a2019-06-22 11:04:10 -0400414class BuildError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700415 pass
416
Anas Nashif3ba1d432017-12-05 15:28:44 -0500417
Anas Nashif83fc06a2019-06-22 11:04:10 -0400418class ExecutionError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700419 pass
420
Anas Nashif3ba1d432017-12-05 15:28:44 -0500421
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800422log_file = None
423
Andrew Boie6acbe632015-07-17 12:03:52 -0700424# Debug Functions
Anas Nashif654ec5982019-04-11 08:38:21 -0400425def info(what, show_time=True):
426 if options.timestamps and show_time:
427 date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
428 what = "{}: {}".format(date, what)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800429 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300430 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800431 if log_file:
432 log_file.write(what + "\n")
433 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700434
Anas Nashif3ba1d432017-12-05 15:28:44 -0500435
Andrew Boie6acbe632015-07-17 12:03:52 -0700436def error(what):
Anas Nashif654ec5982019-04-11 08:38:21 -0400437 if options.timestamps:
438 date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
439 what = "{}: {}".format(date, what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700440 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800441 if log_file:
442 log_file(what + "\n")
443 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700444
Anas Nashif3ba1d432017-12-05 15:28:44 -0500445
Andrew Boie08ce5a52016-02-22 13:28:10 -0800446def debug(what):
447 if VERBOSE >= 1:
448 info(what)
449
Anas Nashif3ba1d432017-12-05 15:28:44 -0500450
Andrew Boie6acbe632015-07-17 12:03:52 -0700451def verbose(what):
452 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800453 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700454
Anas Nashif576be982017-12-23 20:20:27 -0500455class HarnessImporter:
456
457 def __init__(self, name):
458 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
459 module = __import__("harness")
460 if name:
461 my_class = getattr(module, name)
462 else:
463 my_class = getattr(module, "Test")
464
465 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500466
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300467class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400468 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300469 """Constructor
470
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300471 """
472 self.lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400473
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300474 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500475 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -0400476 self.duration = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400477 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300478
Anas Nashifdf7ee612018-07-07 06:09:01 -0500479 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100480 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500481 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500482
Anas Nashifd3384fb2018-02-22 06:44:16 -0600483 self.name = instance.name
484 self.instance = instance
Anas Nashif83fc06a2019-06-22 11:04:10 -0400485 self.timeout = instance.testcase.timeout
486 self.sourcedir = instance.testcase.source_dir
487 self.build_dir = instance.build_dir
488 self.log = os.path.join(self.build_dir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600489 self.returncode = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400490 self.set_state("running", self.duration)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600491
Anas Nashif83fc06a2019-06-22 11:04:10 -0400492 self.args = []
493
494 def set_state(self, state, duration):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300495 self.lock.acquire()
496 self.state = state
Anas Nashif83fc06a2019-06-22 11:04:10 -0400497 self.duration = duration
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300498 self.lock.release()
499
500 def get_state(self):
501 self.lock.acquire()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400502 ret = (self.state, self.duration)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300503 self.lock.release()
504 return ret
505
Anas Nashif83fc06a2019-06-22 11:04:10 -0400506 def record(self, harness):
507 if harness.recording:
508 filename = os.path.join(options.outdir,
509 self.instance.platform.name,
510 self.instance.testcase.name, "recording.csv")
511 with open(filename, "at") as csvfile:
512 cw = csv.writer(csvfile, harness.fieldnames, lineterminator=os.linesep)
513 cw.writerow(harness.fieldnames)
514 for instance in harness.recording:
515 cw.writerow(instance)
516
Anas Nashifdf7ee612018-07-07 06:09:01 -0500517class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400518 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500519 """Constructor
520
521 @param instance Test Instance
522 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400523 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500524
525 self.valgrind = False
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100526 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500527
Jan Kowalewski265895b2019-01-07 16:40:24 +0100528 def try_kill_process_by_pid(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400529 if self.pid_fn:
Jan Kowalewski265895b2019-01-07 16:40:24 +0100530 pid = int(open(self.pid_fn).read())
531 os.unlink(self.pid_fn)
532 self.pid_fn = None # clear so we don't try to kill the binary twice
533 try:
534 os.kill(pid, signal.SIGTERM)
535 except ProcessLookupError:
536 pass
537
Anas Nashifdf7ee612018-07-07 06:09:01 -0500538 def _output_reader(self, proc, harness):
539 log_out_fp = open(self.log, "wt")
540 for line in iter(proc.stdout.readline, b''):
541 verbose("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
542 log_out_fp.write(line.decode('utf-8'))
543 log_out_fp.flush()
544 harness.handle(line.decode('utf-8').rstrip())
545 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100546 try:
547 #POSIX arch based ztests end on their own,
548 #so let's give it up to 100ms to do so
549 proc.wait(0.1)
550 except subprocess.TimeoutExpired:
551 proc.terminate()
552 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500553 break
554
555 log_out_fp.close()
556
557 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500558
Anas Nashif83fc06a2019-06-22 11:04:10 -0400559 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500560 harness_import = HarnessImporter(harness_name)
561 harness = harness_import.instance
562 harness.configure(self.instance)
563
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500564 if self.call_make_run:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400565 command = [get_generator()[0], "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500566 else:
567 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500568
Alberto Escolar Piedrasb91f3742019-07-02 10:06:22 +0200569 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500570 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100571 "--leak-check=full",
572 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
Anas Nashif83fc06a2019-06-22 11:04:10 -0400573 "--log-file="+self.build_dir+"/valgrind.log"
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100574 ] + command
Anas Nashifdf7ee612018-07-07 06:09:01 -0500575
Marc Herbertaf1090c2019-04-30 14:11:29 -0700576 verbose("Spawning process: " +
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100577 " ".join(shlex.quote(word) for word in command) + os.linesep +
Anas Nashif83fc06a2019-06-22 11:04:10 -0400578 "Spawning process in directory: " + self.build_dir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200579
Anas Nashif83fc06a2019-06-22 11:04:10 -0400580 start_time = time.time()
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200581
Anas Nashif83fc06a2019-06-22 11:04:10 -0400582 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Marc Herbertaf1090c2019-04-30 14:11:29 -0700583 verbose("Spawning BinaryHandler Thread for %s" % self.name)
Flavio Ceolin063ab902019-10-15 16:10:49 -0700584 t = threading.Thread(target=self._output_reader, args=(proc, harness, ), daemon=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500585 t.start()
586 t.join(self.timeout)
587 if t.is_alive():
Jan Kowalewski265895b2019-01-07 16:40:24 +0100588 self.try_kill_process_by_pid()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500589 proc.terminate()
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100590 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500591 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500592 proc.wait()
593 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500594
Anas Nashif83fc06a2019-06-22 11:04:10 -0400595 handler_time = time.time() - start_time
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200596
Anas Nashifdf7ee612018-07-07 06:09:01 -0500597 if options.enable_coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400598 subprocess.call(["GCOV_PREFIX=" + self.build_dir,
599 "gcov", self.sourcedir, "-b", "-s", self.build_dir], shell=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500600
Jan Kowalewski265895b2019-01-07 16:40:24 +0100601 self.try_kill_process_by_pid()
602
Anas Nashif83fc06a2019-06-22 11:04:10 -0400603 # FIXME: This is needed when killing the simulator, the console is
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500604 # garbled and needs to be reset. Did not find a better way to do that.
605
606 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500607 self.instance.results = harness.tests
Anas Nashif83fc06a2019-06-22 11:04:10 -0400608 if not self.terminated and self.returncode != 0:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100609 #When a process is killed, the default handler returns 128 + SIGTERM
610 #so in that case the return code itself is not meaningful
Anas Nashif83fc06a2019-06-22 11:04:10 -0400611 self.set_state("error", handler_time)
612 self.instance.reason = "Handler error"
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100613 elif harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400614 self.set_state(harness.state, handler_time)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500615 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400616 self.set_state("timeout", handler_time)
617 self.instance.reason = "Handler timeout"
618
619 self.record(harness)
Anas Nashif73440ea2018-02-19 10:57:03 -0600620
621class DeviceHandler(Handler):
622
Anas Nashifd18ec532019-04-11 23:20:39 -0400623 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600624 """Constructor
625
626 @param instance Test Instance
627 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400628 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600629
Anas Nashif83fc06a2019-06-22 11:04:10 -0400630 self.suite = None
631
Marti Bolivar5591ca22019-02-07 15:53:39 -0700632 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500633 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600634
Marti Bolivar5591ca22019-02-07 15:53:39 -0700635 ser_fileno = ser.fileno()
636 readlist = [halt_fileno, ser_fileno]
637
Anas Nashif73440ea2018-02-19 10:57:03 -0600638 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700639 readable, _, _ = select.select(readlist, [], [], self.timeout)
640
641 if halt_fileno in readable:
642 verbose('halted')
643 ser.close()
644 break
645 if ser_fileno not in readable:
646 continue # Timeout.
647
648 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500649 try:
650 serial_line = ser.readline()
651 except TypeError:
652 pass
Anas Nashif83fc06a2019-06-22 11:04:10 -0400653 except serial.SerialException:
Anas Nashif59237602019-03-03 10:36:35 -0500654 ser.close()
655 break
Anas Nashif61e21632018-04-08 13:30:16 -0500656
Marti Bolivar5591ca22019-02-07 15:53:39 -0700657 # Just because ser_fileno has data doesn't mean an entire line
658 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600659 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600660 sl = serial_line.decode('utf-8', 'ignore')
661 verbose("DEVICE: {0}".format(sl.rstrip()))
662
663 log_out_fp.write(sl)
664 log_out_fp.flush()
665 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700666
Anas Nashif73440ea2018-02-19 10:57:03 -0600667 if harness.state:
668 ser.close()
669 break
670
671 log_out_fp.close()
672
Anas Nashif83fc06a2019-06-22 11:04:10 -0400673 def device_is_available(self, device):
674 for i in self.suite.connected_hardware:
675 if i['platform'] == device and i['available'] and i['connected']:
676 return True
677
678 return False
679
680 def get_available_device(self, device):
681 for i in self.suite.connected_hardware:
682 if i['platform'] == device and i['available']:
683 i['available'] = False
684 i['counter'] += 1
685 return i
686
687 return None
688
689 def make_device_available(self, serial):
690 with hw_map_local:
691 for i in self.suite.connected_hardware:
692 if i['serial'] == serial:
693 i['available'] = True
694
Anas Nashif73440ea2018-02-19 10:57:03 -0600695 def handle(self):
696 out_state = "failed"
697
Anas Nashif83fc06a2019-06-22 11:04:10 -0400698 if options.west_flash:
699 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
700 if options.west_runner:
Michael Scott421ce462019-06-18 09:37:46 -0700701 command.append("--runner")
702 command.append(options.west_runner)
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200703 # There are three ways this option is used.
Andy Doan79c48842019-02-08 10:09:04 -0600704 # 1) bare: --west-flash
705 # This results in options.west_flash == []
706 # 2) with a value: --west-flash="--board-id=42"
707 # This results in options.west_flash == "--board-id=42"
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200708 # 3) Multiple values: --west-flash="--board-id=42,--erase"
709 # This results in options.west_flash == "--board-id=42 --erase"
Andy Doan79c48842019-02-08 10:09:04 -0600710 if options.west_flash != []:
711 command.append('--')
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200712 command.extend(options.west_flash.split(','))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600713 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400714 command = [get_generator()[0], "-C", self.build_dir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600715
Anas Nashifd3384fb2018-02-22 06:44:16 -0600716
Anas Nashif83fc06a2019-06-22 11:04:10 -0400717 while not self.device_is_available(self.instance.platform.name):
718 time.sleep(1)
719
720 hardware = self.get_available_device(self.instance.platform.name)
721
722 runner = hardware.get('runner', None)
723 if runner:
724 board_id = hardware.get("id", None)
725 product = hardware.get("product", None)
726 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
727 command.append("--runner")
728 command.append(hardware.get('runner', None))
729 if runner == "pyocd":
730 command.append("--board-id")
731 command.append(board_id)
732 elif runner == "nrfjprog":
733 command.append('--')
734 command.append("--snr")
735 command.append(board_id)
736 elif runner == "openocd" and product == "STM32 STLink":
737 command.append('--')
738 command.append("--cmd-pre-init")
739 command.append("hla_serial %s" %(board_id))
740 elif runner == "openocd" and product == "EDBG CMSIS-DAP":
741 command.append('--')
742 command.append("--cmd-pre-init")
743 command.append("cmsis_dap_serial %s" %(board_id))
744 elif runner == "jlink":
745 command.append("--tool-opt=-SelectEmuBySN %s" %(board_id))
746
747 serial_device = hardware['serial']
748
749 try:
750 ser = serial.Serial(
751 serial_device,
752 baudrate=115200,
753 parity=serial.PARITY_NONE,
754 stopbits=serial.STOPBITS_ONE,
755 bytesize=serial.EIGHTBITS,
756 timeout=self.timeout
757 )
758 except serial.SerialException as e:
759 self.set_state("failed", 0)
760 error("Serial device err: %s" %(str(e)))
761 self.make_device_available(serial_device)
762 return
Anas Nashif73440ea2018-02-19 10:57:03 -0600763
764 ser.flush()
765
Anas Nashif83fc06a2019-06-22 11:04:10 -0400766 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashif73440ea2018-02-19 10:57:03 -0600767 harness_import = HarnessImporter(harness_name)
768 harness = harness_import.instance
769 harness.configure(self.instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400770 read_pipe, write_pipe = os.pipe()
771 start_time = time.time()
Anas Nashif73440ea2018-02-19 10:57:03 -0600772
Marti Bolivar5591ca22019-02-07 15:53:39 -0700773 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400774 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600775 t.start()
776
Andy Doan79c48842019-02-08 10:09:04 -0600777 logging.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500778 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400779 if VERBOSE and not runner:
Marti Bolivar303b5222019-02-07 15:50:55 -0700780 subprocess.check_call(command)
781 else:
782 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400783
Anas Nashif61e21632018-04-08 13:30:16 -0500784 except subprocess.CalledProcessError:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400785 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600786
787 t.join(self.timeout)
788 if t.is_alive():
789 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600790
791 if ser.isOpen():
792 ser.close()
793
Anas Nashifd3384fb2018-02-22 06:44:16 -0600794 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400795 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600796 if c not in harness.tests:
797 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500798
Anas Nashif83fc06a2019-06-22 11:04:10 -0400799
800 handler_time = time.time() - start_time
801
Anas Nashif61e21632018-04-08 13:30:16 -0500802 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600803 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400804 self.set_state(harness.state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600805 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400806 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600807
Anas Nashif83fc06a2019-06-22 11:04:10 -0400808 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500809
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300810class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700811 """Spawns a thread to monitor QEMU output from pipes
812
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400813 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700814 We need to do this as once qemu starts, it runs forever until killed.
815 Test cases emit special messages to the console as they run, we check
816 for these to collect whether the test passed or failed.
817 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700818
Anas Nashif83fc06a2019-06-22 11:04:10 -0400819
820 def __init__(self, instance, type_str):
821 """Constructor
822
823 @param instance Test instance
824 """
825
826 super().__init__(instance, type_str)
827 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
828
829 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
830
831
Andrew Boie6acbe632015-07-17 12:03:52 -0700832 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500833 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700834 fifo_in = fifo_fn + ".in"
835 fifo_out = fifo_fn + ".out"
836
837 # These in/out nodes are named from QEMU's perspective, not ours
838 if os.path.exists(fifo_in):
839 os.unlink(fifo_in)
840 os.mkfifo(fifo_in)
841 if os.path.exists(fifo_out):
842 os.unlink(fifo_out)
843 os.mkfifo(fifo_out)
844
845 # We don't do anything with out_fp but we need to open it for
846 # writing so that QEMU doesn't block, due to the way pipes work
847 out_fp = open(fifo_in, "wb")
848 # Disable internal buffering, we don't
849 # want read() or poll() to ever block if there is data in there
850 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800851 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700852
853 start_time = time.time()
854 timeout_time = start_time + timeout
855 p = select.poll()
856 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400857 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700858
Andrew Boie6acbe632015-07-17 12:03:52 -0700859 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500860 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700861 while True:
862 this_timeout = int((timeout_time - time.time()) * 1000)
863 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400864 if not out_state:
865 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700866 break
867
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500868 try:
869 c = in_fp.read(1).decode("utf-8")
870 except UnicodeDecodeError:
871 # Test is writing something weird, fail
872 out_state = "unexpected byte"
873 break
874
Andrew Boie6acbe632015-07-17 12:03:52 -0700875 if c == "":
876 # EOF, this shouldn't happen unless QEMU crashes
877 out_state = "unexpected eof"
878 break
879 line = line + c
880 if c != "\n":
881 continue
882
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300883 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700884 log_out_fp.write(line)
885 log_out_fp.flush()
886 line = line.strip()
887 verbose("QEMU: %s" % line)
888
Anas Nashif576be982017-12-23 20:20:27 -0500889 harness.handle(line)
890 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400891 # if we have registered a fail make sure the state is not
892 # overridden by a false success message coming from the
893 # testsuite
894 if out_state != 'failed':
895 out_state = harness.state
896
897 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700898 # the timeout and wait for 2 more seconds to catch anything
899 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700900 # coverage is enabled since dumping this information can
901 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500902 if not timeout_extended or harness.capture_coverage:
903 timeout_extended= True
904 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700905 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500906 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500907 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700908 line = ""
909
Anas Nashif83fc06a2019-06-22 11:04:10 -0400910 handler.record(harness)
911
912 handler_time = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700913 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashif83fc06a2019-06-22 11:04:10 -0400914 (out_state, handler_time))
915 handler.set_state(out_state, handler_time)
Andrew Boie6acbe632015-07-17 12:03:52 -0700916
917 log_out_fp.close()
918 out_fp.close()
919 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400920 if os.path.exists(pid_fn):
921 pid = int(open(pid_fn).read())
922 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700923
Anas Nashifd6476ee2019-04-11 11:40:09 -0400924 try:
925 if pid:
926 os.kill(pid, signal.SIGTERM)
927 except ProcessLookupError:
928 # Oh well, as long as it's dead! User probably sent Ctrl-C
929 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800930
Andrew Boie6acbe632015-07-17 12:03:52 -0700931 os.unlink(fifo_in)
932 os.unlink(fifo_out)
933
Anas Nashif83fc06a2019-06-22 11:04:10 -0400934 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700935 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500936 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700937
938 # We pass this to QEMU which looks for fifos with .in and .out
939 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400940 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700941
Anas Nashif83fc06a2019-06-22 11:04:10 -0400942 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700943 if os.path.exists(self.pid_fn):
944 os.unlink(self.pid_fn)
945
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500946 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500947
Anas Nashif83fc06a2019-06-22 11:04:10 -0400948 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500949 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600950 harness.configure(self.instance)
951 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400952 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300953 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500954 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600955
956 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700957 self.thread.daemon = True
Anas Nashif83fc06a2019-06-22 11:04:10 -0400958 verbose("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700959 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400960 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700961
Andrew Boie6acbe632015-07-17 12:03:52 -0700962 def get_fifo(self):
963 return self.fifo_fn
964
Andrew Boie6acbe632015-07-17 12:03:52 -0700965class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700966
Anas Nashif83fc06a2019-06-22 11:04:10 -0400967 alloc_sections = [
968 "bss",
969 "noinit",
970 "app_bss",
971 "app_noinit",
972 "ccm_bss",
973 "ccm_noinit"
974 ]
975
976 rw_sections = [
977 "datas",
978 "initlevel",
979 "exceptions",
980 "initshell",
981 "_static_thread_area",
982 "_k_timer_area",
983 "_k_mem_slab_area",
984 "_k_mem_pool_area",
985 "sw_isr_table",
986 "_k_sem_area",
987 "_k_mutex_area",
988 "app_shmem_regions",
989 "_k_fifo_area",
990 "_k_lifo_area",
991 "_k_stack_area",
992 "_k_msgq_area",
993 "_k_mbox_area",
994 "_k_pipe_area",
995 "net_if",
996 "net_if_dev",
997 "net_stack",
998 "net_l2_data",
999 "_k_queue_area",
1000 "_net_buf_pool_area",
1001 "app_datas",
1002 "kobject_data",
1003 "mmu_tables",
1004 "app_pad",
1005 "priv_stacks",
1006 "ccm_data",
1007 "usb_descriptor",
1008 "usb_data", "usb_bos_desc",
1009 'log_backends_sections',
1010 'log_dynamic_sections',
1011 'log_const_sections',
1012 "app_smem",
1013 'shell_root_cmds_sections',
1014 'log_const_sections',
1015 "font_entry_sections",
1016 "priv_stacks_noinit",
1017 "_TEXT_SECTION_NAME_2",
1018 "_GCOV_BSS_SECTION_NAME",
1019 "gcov",
1020 "nocache"
1021 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001022
Andrew Boie73b4ee62015-10-07 11:33:22 -07001023 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001024 ro_sections = [
1025 "text",
1026 "ctors",
1027 "init_array",
1028 "reset",
1029 "object_access",
1030 "rodata",
1031 "devconfig",
1032 "net_l2",
1033 "vector",
1034 "sw_isr_table",
1035 "_settings_handlers_area",
1036 "_bt_channels_area",
1037 "_bt_br_channels_area",
1038 "_bt_services_area",
1039 "vectors",
1040 "net_socket_register",
1041 "net_ppp_proto"
1042 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001043
Andrew Boie52fef672016-11-29 12:21:59 -08001044 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001045 """Constructor
1046
Andrew Boiebbd670c2015-08-17 13:16:11 -07001047 @param filename Path to the output binary
1048 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001049 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001050 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001051 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001052 magic = f.read(4)
1053
Anas Nashifb4bdd662018-08-15 17:12:28 -05001054 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001055 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001056 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1057 except Exception as e:
1058 print(str(e))
1059 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001060
1061 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001062 # GREP can not be used as it returns an error if the symbol is not
1063 # found.
1064 is_xip_command = "nm " + filename + \
1065 " | awk '/CONFIG_XIP/ { print $3 }'"
1066 is_xip_output = subprocess.check_output(
1067 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1068 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001069 try:
1070 if is_xip_output.endswith("no symbols"):
1071 raise SanityRuntimeError("%s has no symbol information" % filename)
1072 except Exception as e:
1073 print(str(e))
1074 sys.exit(2)
1075
Andrew Boie6acbe632015-07-17 12:03:52 -07001076 self.is_xip = (len(is_xip_output) != 0)
1077
Andrew Boiebbd670c2015-08-17 13:16:11 -07001078 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001079 self.sections = []
1080 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001081 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001082 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001083
1084 self._calculate_sizes()
1085
1086 def get_ram_size(self):
1087 """Get the amount of RAM the application will use up on the device
1088
1089 @return amount of RAM, in bytes
1090 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001091 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001092
1093 def get_rom_size(self):
1094 """Get the size of the data that this application uses on device's flash
1095
1096 @return amount of ROM, in bytes
1097 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001098 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001099
1100 def unrecognized_sections(self):
1101 """Get a list of sections inside the binary that weren't recognized
1102
David B. Kinder29963c32017-06-16 12:32:42 -07001103 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001104 """
1105 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001106 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001107 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001108 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001109 return slist
1110
1111 def _calculate_sizes(self):
1112 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001113 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001114 objdump_output = subprocess.check_output(
1115 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001116
1117 for line in objdump_output:
1118 words = line.split()
1119
Anas Nashif83fc06a2019-06-22 11:04:10 -04001120 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001121 continue
1122
1123 index = words[0]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001124 if not index[0].isdigit(): # Skip lines that do not start
Andrew Boie6acbe632015-07-17 12:03:52 -07001125 continue # with a digit
1126
1127 name = words[1] # Skip lines with section names
Anas Nashif83fc06a2019-06-22 11:04:10 -04001128 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001129 continue
1130
Andrew Boie73b4ee62015-10-07 11:33:22 -07001131 # TODO this doesn't actually reflect the size in flash or RAM as
1132 # it doesn't include linker-imposed padding between sections.
1133 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001134 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001135 if size == 0:
1136 continue
1137
Andrew Boie73b4ee62015-10-07 11:33:22 -07001138 load_addr = int(words[4], 16)
1139 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001140
1141 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001142 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001143 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001144 if name in SizeCalculator.alloc_sections:
1145 self.ram_size += size
1146 stype = "alloc"
1147 elif name in SizeCalculator.rw_sections:
1148 self.ram_size += size
1149 self.rom_size += size
1150 stype = "rw"
1151 elif name in SizeCalculator.ro_sections:
1152 self.rom_size += size
1153 if not self.is_xip:
1154 self.ram_size += size
1155 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001156 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001157 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001158 if name not in self.extra_sections:
1159 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001160
Anas Nashif3ba1d432017-12-05 15:28:44 -05001161 self.sections.append({"name": name, "load_addr": load_addr,
1162 "size": size, "virt_addr": virt_addr,
1163 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001164
1165
Andrew Boie6acbe632015-07-17 12:03:52 -07001166# "list" - List of strings
1167# "list:<type>" - List of <type>
1168# "set" - Set of unordered, unique strings
1169# "set:<type>" - Set of <type>
1170# "float" - Floating point
1171# "int" - Integer
1172# "bool" - Boolean
1173# "str" - String
1174
1175# XXX Be sure to update __doc__ if you change any of this!!
1176
Anas Nashif83fc06a2019-06-22 11:04:10 -04001177platform_valid_keys = {
Anas Nashif924a4e72018-10-18 12:25:55 -04001178 "supported_toolchains": {"type": "list", "default": []},
1179 "env": {"type": "list", "default": []}
1180 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001181
Anas Nashif3ba1d432017-12-05 15:28:44 -05001182testcase_valid_keys = {"tags": {"type": "set", "required": False},
1183 "type": {"type": "str", "default": "integration"},
1184 "extra_args": {"type": "list"},
1185 "extra_configs": {"type": "list"},
1186 "build_only": {"type": "bool", "default": False},
1187 "build_on_all": {"type": "bool", "default": False},
1188 "skip": {"type": "bool", "default": False},
1189 "slow": {"type": "bool", "default": False},
1190 "timeout": {"type": "int", "default": 60},
1191 "min_ram": {"type": "int", "default": 8},
1192 "depends_on": {"type": "set"},
1193 "min_flash": {"type": "int", "default": 32},
1194 "arch_whitelist": {"type": "set"},
1195 "arch_exclude": {"type": "set"},
1196 "extra_sections": {"type": "list", "default": []},
1197 "platform_exclude": {"type": "set"},
1198 "platform_whitelist": {"type": "set"},
1199 "toolchain_exclude": {"type": "set"},
1200 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001201 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001202 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301203 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001204 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001205
Andrew Boie6acbe632015-07-17 12:03:52 -07001206class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001207 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001208 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001209
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001210 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001211 """Instantiate a new SanityConfigParser object
1212
Anas Nashifa792a3d2017-04-04 18:47:49 -04001213 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001214 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001215 self.data = {}
1216 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001217 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001218 self.tests = {}
1219 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001220
1221 def load(self):
1222 self.data = scl.yaml_load_verify(self.filename, self.schema)
1223
Anas Nashif255625b2017-12-05 15:08:26 -05001224 if 'tests' in self.data:
1225 self.tests = self.data['tests']
1226 if 'common' in self.data:
1227 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001228
Anas Nashif83fc06a2019-06-22 11:04:10 -04001229
Andrew Boie6acbe632015-07-17 12:03:52 -07001230 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001231 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001232 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001233 if typestr == "str":
1234 return v
1235
1236 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001237 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001238
1239 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001240 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001241
1242 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001243 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001244
Anas Nashif3ba1d432017-12-05 15:28:44 -05001245 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001246 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001247 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001248 vs = v.split()
1249 if len(typestr) > 4 and typestr[4] == ":":
1250 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1251 else:
1252 return vs
1253
1254 elif typestr.startswith("set"):
1255 vs = v.split()
1256 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001257 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001258 else:
1259 return set(vs)
1260
Anas Nashif576be982017-12-23 20:20:27 -05001261 elif typestr.startswith("map"):
1262 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001264 raise ConfigurationError(
1265 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001266
Anas Nashifb4754ed2017-12-05 17:27:58 -05001267 def get_test(self, name, valid_keys):
1268 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001269
Anas Nashifb4754ed2017-12-05 17:27:58 -05001270 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001271 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001272 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001273 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001274 here, it will generate an error. Each value in this dictionary
1275 is another dictionary containing metadata:
1276
1277 "default" - Default value if not given
1278 "type" - Data type to convert the text value to. Simple types
1279 supported are "str", "float", "int", "bool" which will get
1280 converted to respective Python data types. "set" and "list"
1281 may also be specified which will split the value by
1282 whitespace (but keep the elements as strings). finally,
1283 "list:<type>" and "set:<type>" may be given which will
1284 perform a type conversion after splitting the value up.
1285 "required" - If true, raise an error if not defined. If false
1286 and "default" isn't specified, a type conversion will be
1287 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001288 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001289 type conversion and default values filled in per valid_keys
1290 """
1291
1292 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001293 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001294 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001295
Anas Nashifb4754ed2017-12-05 17:27:58 -05001296 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001297 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001298 raise ConfigurationError(
1299 self.filename,
1300 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001301 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001302
Anas Nashiffa695d22017-10-04 16:14:27 -04001303 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001304 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001305 # By default, we just concatenate string values of keys
1306 # which appear both in "common" and per-test sections,
1307 # but some keys are handled in adhoc way based on their
1308 # semantics.
1309 if k == "filter":
1310 d[k] = "(%s) and (%s)" % (d[k], v)
1311 else:
1312 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001313 else:
1314 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001315
Andrew Boie08ce5a52016-02-22 13:28:10 -08001316 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001317 if k not in d:
1318 if "required" in kinfo:
1319 required = kinfo["required"]
1320 else:
1321 required = False
1322
1323 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001324 raise ConfigurationError(
1325 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001326 "missing required value for '%s' in test '%s'" %
1327 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001328 else:
1329 if "default" in kinfo:
1330 default = kinfo["default"]
1331 else:
1332 default = self._cast_value("", kinfo["type"])
1333 d[k] = default
1334 else:
1335 try:
1336 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001337 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001338 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001339 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1340 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001341
1342 return d
1343
1344
1345class Platform:
1346 """Class representing metadata for a particular platform
1347
Anas Nashifc7406082015-12-13 15:00:31 -05001348 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001349
Anas Nashif83fc06a2019-06-22 11:04:10 -04001350 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
1351 "scripts","sanity_chk","platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001352
Anas Nashif83fc06a2019-06-22 11:04:10 -04001353 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001354 """Constructor.
1355
Andrew Boie6acbe632015-07-17 12:03:52 -07001356 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001357
1358 self.name = ""
1359 self.sanitycheck = True
1360 # if no RAM size is specified by the board, take a default of 128K
1361 self.ram = 128
1362
1363 self.ignore_tags = []
1364 self.default = False
1365 # if no flash size is specified by the board, take a default of 512K
1366 self.flash = 512
1367 self.supported = set()
1368
1369 self.arch = ""
1370 self.type = "na"
1371 self.simulation = "na"
1372 self.supported_toolchains = []
1373 self.env = []
1374 self.env_satisfied = True
1375 self.filter_data = dict()
1376
1377 def load(self, platform_file):
1378 scp = SanityConfigParser(platform_file, self.platform_schema)
1379 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001380 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001381
Anas Nashif255625b2017-12-05 15:08:26 -05001382 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001383 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001384 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001385 self.ram = data.get("ram", 128)
1386 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001387 self.ignore_tags = testing.get("ignore_tags", [])
1388 self.default = testing.get("default", False)
1389 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001390 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001391 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001392 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001393 for item in supp_feature.split(":"):
1394 self.supported.add(item)
1395
Anas Nashif255625b2017-12-05 15:08:26 -05001396 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001397 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001398 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001399 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001400 self.env = data.get("env", [])
1401 self.env_satisfied = True
1402 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001403 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001404 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001405
Andrew Boie6acbe632015-07-17 12:03:52 -07001406
Andrew Boie6acbe632015-07-17 12:03:52 -07001407 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001408 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001409
Anas Nashif83fc06a2019-06-22 11:04:10 -04001410class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001411 """Class representing a test application
1412 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001413
Anas Nashif83fc06a2019-06-22 11:04:10 -04001414
1415 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001416 """TestCase constructor.
1417
Anas Nashif877d3ca2017-12-05 17:39:29 -05001418 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001419 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001420 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001421
Andrew Boie6acbe632015-07-17 12:03:52 -07001422 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001423 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001424 the test case is <workdir>/<name>.
1425
Marc Herbert1c8632c2019-04-15 17:58:45 -07001426 @param testcase_root os.path.abspath() of one of the --testcase-root
1427 @param workdir Sub-directory of testcase_root where the
1428 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001429 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001430 in the test case configuration file. For many test cases that just
1431 define one test, can be anything and is usually "test". This is
1432 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001433 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001434 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001435 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001436 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001437
Anas Nashif83fc06a2019-06-22 11:04:10 -04001438 self.id = ""
1439 self.source_dir = ""
1440 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001441 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001442 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001443
Anas Nashif83fc06a2019-06-22 11:04:10 -04001444 self.type = None
1445 self.tags = None
1446 self.extra_args = None
1447 self.extra_configs = None
1448 self.arch_whitelist = None
1449 self.arch_exclude = None
1450 self.skip = None
1451 self.platform_exclude = None
1452 self.platform_whitelist = None
1453 self.toolchain_exclude = None
1454 self.toolchain_whitelist = None
1455 self.tc_filter = None
1456 self.timeout = 60
1457 self.harness = ""
1458 self.harness_config = {}
1459 self.build_only = True
1460 self.build_on_all = False
1461 self.slow = False
1462 self.min_ram = None
1463 self.depends_on = None
1464 self.min_flash = None
1465 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001466
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001467
Anas Nashif83fc06a2019-06-22 11:04:10 -04001468 @staticmethod
1469 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001470
Marc Herbert1c8632c2019-04-15 17:58:45 -07001471 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001472 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001473 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001474 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001475 relative_tc_root = os.path.relpath(canonical_testcase_root,
1476 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001477 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001478 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001479
Marc Herbert1c8632c2019-04-15 17:58:45 -07001480 # workdir can be "."
1481 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001482 return unique
1483
Anas Nashif83fc06a2019-06-22 11:04:10 -04001484 @staticmethod
1485 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001486 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001487 # do not match until end-of-line, otherwise we won't allow
1488 # stc_regex below to catch the ones that are declared in the same
1489 # line--as we only search starting the end of this match
1490 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001491 re.MULTILINE)
1492 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001493 br"^\s*" # empy space at the beginning is ok
1494 # catch the case where it is declared in the same sentence, e.g:
1495 #
1496 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1497 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1498 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1499 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1500 # Consume the argument that becomes the extra testcse
1501 br"\(\s*"
1502 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1503 # _setup_teardown() variant has two extra arguments that we ignore
1504 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1505 br"\s*\)",
1506 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001507 re.MULTILINE)
1508 suite_run_regex = re.compile(
1509 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1510 re.MULTILINE)
1511 achtung_regex = re.compile(
1512 br"(#ifdef|#endif)",
1513 re.MULTILINE)
1514 warnings = None
1515
1516 with open(inf_name) as inf:
1517 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1518 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001519 suite_regex_match = suite_regex.search(main_c)
1520 if not suite_regex_match:
1521 # can't find ztest_test_suite, maybe a client, because
1522 # it includes ztest.h
1523 return None, None
1524
1525 suite_run_match = suite_run_regex.search(main_c)
1526 if not suite_run_match:
1527 raise ValueError("can't find ztest_run_test_suite")
1528
1529 achtung_matches = re.findall(
1530 achtung_regex,
1531 main_c[suite_regex_match.end():suite_run_match.start()])
1532 if achtung_matches:
1533 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001534 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001535 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001536 stc_regex,
1537 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001538 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001539 return matches, warnings
1540
1541 def scan_path(self, path):
1542 subcases = []
1543 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1544 try:
1545 _subcases, warnings = self.scan_file(filename)
1546 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001547 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001548 if _subcases:
1549 subcases += _subcases
1550 except ValueError as e:
Flavio Ceolinbf878ce2019-04-19 22:24:09 -07001551 error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001552 return subcases
1553
Anas Nashif83fc06a2019-06-22 11:04:10 -04001554 def parse_subcases(self, test_path):
1555 results = self.scan_path(os.path.dirname(test_path))
Anas Nashifaae71d72018-04-21 22:26:48 -05001556 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001557 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001558 self.cases.append(name)
1559
Anas Nashiff16e92c2019-03-31 16:58:12 -04001560 if not results:
1561 self.cases.append(self.id)
1562
Anas Nashifaae71d72018-04-21 22:26:48 -05001563
Anas Nashif75547e22018-02-24 08:32:14 -06001564 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001565 return self.name
1566
1567
Andrew Boie6acbe632015-07-17 12:03:52 -07001568class TestInstance:
1569 """Class representing the execution of a particular TestCase on a platform
1570
1571 @param test The TestCase object we want to build/execute
1572 @param platform Platform object that we want to build and run against
1573 @param base_outdir Base directory for all test results. The actual
1574 out directory used is <outdir>/<platform>/<test case name>
1575 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001576
Anas Nashif83fc06a2019-06-22 11:04:10 -04001577 def __init__(self, testcase, platform, base_outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001578
Anas Nashif83fc06a2019-06-22 11:04:10 -04001579 self.testcase = testcase
1580 self.platform = platform
1581
1582 self.status = None
1583 self.reason = "N/A"
1584 self.metrics = dict()
1585 self.handler = None
1586
1587
1588 self.name = os.path.join(platform.name, testcase.name)
1589 self.build_dir = os.path.join(base_outdir, platform.name, testcase.name)
1590
1591 self.build_only = self.check_build_or_run()
1592 self.run = not self.build_only
1593
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001594 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001595
Marc Herbert0f7255c2019-04-05 14:14:21 -07001596 def __lt__(self, other):
1597 return self.name < other.name
1598
Anas Nashif83fc06a2019-06-22 11:04:10 -04001599 def check_build_or_run(self):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001600
Anas Nashif83fc06a2019-06-22 11:04:10 -04001601 build_only = True
1602
1603 # we asked for build-only on the command line
1604 if options.build_only:
1605 return True
1606
1607 # The testcase is designed to be build only.
1608 if self.testcase.build_only:
1609 return True
1610
1611 # Do not run slow tests:
1612 skip_slow = self.testcase.slow and not options.enable_slow
1613 if skip_slow:
1614 return True
1615
1616 runnable =bool(self.testcase.type == "unit" or \
1617 self.platform.type == "native" or \
1618 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1619 options.device_testing)
1620
1621 if self.platform.simulation == "nsim":
1622 if not find_executable("nsimdrv"):
1623 runnable = False
1624
1625 if self.platform.simulation == "renode":
1626 if not find_executable("renode"):
1627 runnable = False
1628
1629 # console harness allows us to run the test and capture data.
1630 if self.testcase.harness == 'console':
1631
1632 # if we have a fixture that is also being supplied on the
1633 # command-line, then we need to run the test, not just build it.
1634 if "fixture" in self.testcase.harness_config:
1635 fixture = self.testcase.harness_config['fixture']
1636 if fixture in options.fixture:
1637 build_only = False
1638 else:
1639 build_only = True
1640 else:
1641 build_only = False
1642 elif self.testcase.harness:
1643 build_only = True
1644 else:
1645 build_only = False
1646
1647 return not (not build_only and runnable)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001648
Anas Nashifdbd76492018-11-23 20:24:19 -05001649 def create_overlay(self, platform):
Marc Herbertc7633de2019-07-06 15:52:31 -07001650 # Create this in a "sanitycheck/" subdirectory otherwise this
1651 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1652 # will silently give that second time precedence over any
1653 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001654 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001655 os.makedirs(subdir, exist_ok=True)
1656 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001657 with open(file, "w") as f:
1658 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001659
Anas Nashif83fc06a2019-06-22 11:04:10 -04001660 if self.testcase.extra_configs:
1661 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001662
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001663 if options.enable_coverage:
1664 if platform in options.coverage_platform:
1665 content = content + "\nCONFIG_COVERAGE=y"
1666
1667 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001668
Andrew Boie6acbe632015-07-17 12:03:52 -07001669 def calculate_sizes(self):
1670 """Get the RAM/ROM sizes of a test case.
1671
1672 This can only be run after the instance has been executed by
1673 MakeGenerator, otherwise there won't be any binaries to measure.
1674
1675 @return A SizeCalculator object
1676 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001677 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1678 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001679 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001680 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001681 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001682
1683 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001684
1685 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001686 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001687
1688
Anas Nashif83fc06a2019-06-22 11:04:10 -04001689class CMake():
Andrew Boie4ef16c52015-08-28 12:36:03 -07001690
Anas Nashif83fc06a2019-06-22 11:04:10 -04001691 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1692 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1693
1694 def __init__(self, testcase, platform, source_dir, build_dir):
1695
1696 self.cwd = None
1697 self.capture_output = True
1698
1699 self.defconfig = {}
1700 self.cmake_cache = {}
1701 self.devicetree = {}
1702
1703 self.instance = None
1704 self.testcase = testcase
1705 self.platform = platform
1706 self.source_dir = source_dir
1707 self.build_dir = build_dir
1708 self.log = "build.log"
1709
1710 def parse_generated(self):
1711 self.defconfig = {}
1712 return {}
1713
1714 def run_build(self, args=[]):
1715
1716 verbose("Building %s for %s" % (self.source_dir, self.platform.name))
1717
1718 cmake_args = []
1719 cmake_args.extend(args)
1720 cmake = shutil.which('cmake')
1721 cmd = [cmake] + cmake_args
1722 kwargs = dict()
1723
1724 if self.capture_output:
1725 kwargs['stdout'] = subprocess.PIPE
1726 # CMake sends the output of message() to stderr unless it's STATUS
1727 kwargs['stderr'] = subprocess.STDOUT
1728
1729 if self.cwd:
1730 kwargs['cwd'] = self.cwd
1731
1732 p = subprocess.Popen(cmd, **kwargs)
1733 out, _ = p.communicate()
1734
1735 results = {}
1736 if p.returncode == 0:
1737 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1738
1739 self.instance.status = "passed"
1740 self.instance.reason = ""
1741 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1742
1743 if out:
1744 log_msg = out.decode(sys.getdefaultencoding())
1745 with open(os.path.join(self.build_dir, self.log), "a") as log:
1746 log.write(log_msg)
1747
1748 else:
1749 return None
1750 else:
1751 # A real error occurred, raise an exception
1752 if out:
1753 log_msg = out.decode(sys.getdefaultencoding())
1754 with open(os.path.join(self.build_dir, self.log), "a") as log:
1755 log.write(log_msg)
1756
1757 overflow_flash = "region `FLASH' overflowed by"
1758 overflow_ram = "region `RAM' overflowed by"
1759
1760 if log_msg:
1761 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
1762 verbose("RAM/ROM Overflow")
1763 self.instance.status = "skipped"
1764 self.instance.reason = "overflow"
1765 else:
1766 self.instance.status = "failed"
1767 self.instance.reason = "Build failure"
1768
1769 results = {
1770 "returncode": p.returncode,
1771 "instance": self.instance,
1772 }
1773
1774 return results
1775
1776 def run_cmake(self, args=[]):
1777
1778 verbose("Running cmake on %s for %s" %(self.source_dir, self.platform.name))
1779
1780 cmake_args = ['-B{}'.format(self.build_dir),
1781 '-S{}'.format(self.source_dir),
1782 '-G{}'.format(get_generator()[1])]
1783
1784 args = ["-D{}".format(a.replace('"', '')) for a in args]
1785 cmake_args.extend(args)
1786
1787 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1788 cmake_args.extend(cmake_opts)
1789
1790 cmake = shutil.which('cmake')
1791 cmd = [cmake] + cmake_args
1792 kwargs = dict()
1793
1794 if self.capture_output:
1795 kwargs['stdout'] = subprocess.PIPE
1796 # CMake sends the output of message() to stderr unless it's STATUS
1797 kwargs['stderr'] = subprocess.STDOUT
1798
1799 if self.cwd:
1800 kwargs['cwd'] = self.cwd
1801
1802 p = subprocess.Popen(cmd, **kwargs)
1803 out, _ = p.communicate()
1804
1805 if p.returncode == 0:
1806 filter_results = self.parse_generated()
1807 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1808
1809 results = {'msg': msg, 'filter': filter_results}
1810
1811 else:
1812 self.instance.status = "failed"
1813 self.instance.reason = "Cmake build failure"
1814 results = {"returncode": p.returncode}
1815
1816
1817 if out:
1818 with open(os.path.join(self.build_dir, self.log), "a") as log:
1819 log_msg = out.decode(sys.getdefaultencoding())
1820 log.write(log_msg)
1821
1822 return results
1823
1824
1825class FilterBuilder(CMake):
1826
1827 def __init__(self, testcase, platform, source_dir, build_dir):
1828 super().__init__(testcase, platform, source_dir, build_dir)
1829
1830 self.log = "config-sanitycheck.log"
1831
1832 def parse_generated(self):
1833
1834 if self.platform.name == "unit_testing":
1835 return {}
1836
1837 _generated_dt_confg = "include/generated/generated_dts_board.conf"
1838
1839 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
1840 dt_config_path = os.path.join(self.build_dir, "zephyr", _generated_dt_confg)
1841 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1842
1843 with open(defconfig_path, "r") as fp:
1844 defconfig = {}
1845 for line in fp.readlines():
1846 m = self.config_re.match(line)
1847 if not m:
1848 if line.strip() and not line.startswith("#"):
1849 sys.stderr.write("Unrecognized line %s\n" % line)
1850 continue
1851 defconfig[m.group(1)] = m.group(2).strip()
1852
1853 self.defconfig = defconfig
1854
1855 cmake_conf = {}
1856 try:
1857 cache = CMakeCache.from_file(cmake_cache_path)
1858 except FileNotFoundError:
1859 cache = {}
1860
1861 for k in iter(cache):
1862 cmake_conf[k.name] = k.value
1863
1864 self.cmake_cache = cmake_conf
1865
1866 dt_conf = {}
1867 if os.path.exists(dt_config_path):
1868 with open(dt_config_path, "r") as fp:
1869 for line in fp.readlines():
1870 m = self.dt_re.match(line)
1871 if not m:
1872 if line.strip() and not line.startswith("#"):
1873 sys.stderr.write("Unrecognized line %s\n" % line)
1874 continue
1875 dt_conf[m.group(1)] = m.group(2).strip()
1876 self.devicetree = dt_conf
1877
1878 filter_data = {
1879 "ARCH": self.platform.arch,
1880 "PLATFORM": self.platform.name
1881 }
1882 filter_data.update(os.environ)
1883 filter_data.update(self.defconfig)
1884 filter_data.update(self.cmake_cache)
1885 filter_data.update(self.devicetree)
1886
1887 if self.testcase and self.testcase.tc_filter:
1888 try:
1889 res = expr_parser.parse(self.testcase.tc_filter, filter_data)
1890 except (ValueError, SyntaxError) as se:
1891 sys.stderr.write(
1892 "Failed processing %s\n" % self.testcase.yamlfile)
1893 raise se
1894
1895 if not res:
1896 return {os.path.join(self.platform.name, self.testcase.name): True}
1897 else:
1898 return {os.path.join(self.platform.name, self.testcase.name): False}
1899 else:
1900 self.platform.filter_data = filter_data
1901 return filter_data
1902
1903
1904class ProjectBuilder(FilterBuilder):
1905
1906 def __init__(self, suite, instance):
1907 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1908
1909 self.log = "build.log"
1910 self.instance = instance
1911 self.suite = suite
1912
1913 def setup_handler(self):
1914
1915 instance = self.instance
1916 args = []
1917
1918 # FIXME: Needs simplification
1919 if instance.platform.simulation == "qemu":
1920 instance.handler = QEMUHandler(instance, "qemu")
1921 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
1922 instance.handler.call_make_run = True
1923 elif instance.testcase.type == "unit":
1924 instance.handler = BinaryHandler(instance, "unit")
1925 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
1926 elif instance.platform.type == "native":
1927 instance.handler = BinaryHandler(instance, "native")
1928 instance.handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
1929 elif instance.platform.simulation == "nsim":
1930 if find_executable("nsimdrv"):
1931 instance.handler = BinaryHandler(instance, "nsim")
1932 instance.handler.call_make_run = True
1933 elif instance.platform.simulation == "renode":
1934 if find_executable("renode"):
1935 instance.handler = BinaryHandler(instance, "renode")
1936 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
1937 instance.handler.call_make_run = True
1938 elif options.device_testing:
1939 instance.handler = DeviceHandler(instance, "device")
1940
1941 if instance.handler:
1942 instance.handler.args = args
1943
1944 def process(self, message):
1945 op = message.get('op')
1946
1947 if not self.instance.handler:
1948 self.setup_handler()
1949
1950 # The build process, call cmake and build with configured generator
1951 if op == "cmake":
1952 results = self.cmake()
1953 if self.instance.status == "failed":
1954 pipeline.put({"op": "report", "test": self.instance})
1955 elif options.cmake_only:
1956 pipeline.put({"op": "report", "test": self.instance})
1957 else:
1958 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
1959 verbose("filtering %s" % self.instance.name)
1960 self.instance.status = "skipped"
1961 self.instance.reason = "filter"
1962 pipeline.put({"op": "report", "test": self.instance})
1963 else:
1964 pipeline.put({"op": "build", "test": self.instance})
1965
1966
1967 elif op == "build":
1968 verbose("build test: %s" %self.instance.name)
1969 results = self.build()
1970
1971 if results.get('returncode', 1) > 0:
1972 pipeline.put({"op": "report", "test": self.instance})
1973 else:
1974 if self.instance.run:
1975 pipeline.put({"op": "run", "test": self.instance})
1976 else:
1977 pipeline.put({"op": "report", "test": self.instance})
1978 # Run the generated binary using one of the supported handlers
1979 elif op == "run":
1980 verbose("run test: %s" %self.instance.name)
1981 self.run()
1982 self.instance.status, _ = self.instance.handler.get_state()
1983 self.instance.reason = ""
1984 pipeline.put({
1985 "op": "report",
1986 "test": self.instance,
1987 "state": "executed",
1988 "status": self.instance.status,
1989 "reason": self.instance.status}
1990 )
1991
1992 # Report results and output progress to screen
1993 elif op == "report":
1994 self.report_out()
1995
1996 def report_out(self):
1997 total_tests_width = len(str(self.suite.total_tests))
1998 self.suite.total_done += 1
1999 instance = self.instance
2000
2001 if instance.status in ["failed", "timeout"]:
2002 self.suite.total_failed += 1
2003 if VERBOSE or not TERMINAL:
2004 status = COLOR_RED + "FAILED " + COLOR_NORMAL + instance.reason
2005 else:
2006 info(
2007 "{:<25} {:<50} {}FAILED{}: {}".format(
2008 instance.platform.name,
2009 instance.testcase.name,
2010 COLOR_RED,
2011 COLOR_NORMAL,
2012 instance.reason), False)
2013
2014 # FIXME
2015 h_log = "{}/handler.log".format(instance.build_dir)
2016 b_log = "{}/build.log".format(instance.build_dir)
2017 if os.path.exists(h_log):
2018 log_info("{}".format(h_log))
2019 else:
2020 log_info("{}".format(b_log))
2021
2022 elif instance.status == "skipped":
2023 self.suite.total_skipped += 1
2024 status = COLOR_YELLOW + "SKIPPED" + COLOR_NORMAL
2025
2026 else:
2027 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2028
2029 if VERBOSE or not TERMINAL:
2030 if options.cmake_only:
2031 more_info = "cmake"
2032 elif instance.status == "skipped":
2033 more_info = instance.reason
2034 else:
2035 if instance.handler and instance.run:
2036 more_info = instance.handler.type_str
2037 htime = instance.handler.duration
2038 if htime:
2039 more_info += " {:.3f}s".format(htime)
2040 else:
2041 more_info = "build"
2042
2043 info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
2044 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2045 instance.testcase.name, status, more_info))
2046
2047 if instance.status in ["failed", "timeout"]:
2048 h_log = "{}/handler.log".format(instance.build_dir)
2049 b_log = "{}/build.log".format(instance.build_dir)
2050 if os.path.exists(h_log):
2051 log_info("{}".format(h_log))
2052 else:
2053 log_info("{}".format(b_log))
2054
2055 else:
2056 sys.stdout.write("\rtotal complete: %s%4d/%4d%s %2d%% skipped: %s%4d%s, failed: %s%4d%s" % (
2057 COLOR_GREEN,
2058 self.suite.total_done,
2059 self.suite.total_tests,
2060 COLOR_NORMAL,
2061 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
2062 COLOR_YELLOW if self.suite.total_skipped > 0 else COLOR_NORMAL,
2063 self.suite.total_skipped,
2064 COLOR_NORMAL,
2065 COLOR_RED if self.suite.total_failed > 0 else COLOR_NORMAL,
2066 self.suite.total_failed,
2067 COLOR_NORMAL
2068 )
2069 )
2070 sys.stdout.flush()
2071
2072 def cmake(self):
2073
2074 instance = self.instance
2075 args = self.testcase.extra_args[:]
2076
2077 if options.extra_args:
2078 args += options.extra_args
2079
2080 if instance.handler:
2081 args += instance.handler.args
2082
2083 # merge overlay files into one variable
2084 overlays = ""
2085 idx = 0
2086 for arg in args:
2087 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2088 if match:
2089 overlays += match.group(1)
2090 del args[idx]
2091 idx += 1
2092
2093 if self.testcase.extra_configs or options.coverage:
2094 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
2095 os.path.join(instance.build_dir,
2096 "sanitycheck", "testcase_extra.conf")))
2097
2098 results = self.run_cmake(args)
2099 return results
2100
2101 def build(self):
2102 results = self.run_build(['--build', self.build_dir])
2103 return results
2104
2105 def run(self):
2106
2107 instance = self.instance
2108
2109 if instance.handler.type_str == "device":
2110 instance.handler.suite = self.suite
2111
2112 instance.handler.handle()
2113
2114 if instance.handler.type_str == "qemu":
2115 verbose("Running %s (%s)" %(instance.name, instance.handler.type_str))
2116 command = [get_generator()[0]]
2117 command += ["-C", self.build_dir, "run"]
2118
2119 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
2120 verbose("Spawning QEMUHandler Thread for %s" % instance.name)
2121 proc.wait()
2122 self.returncode = proc.returncode
2123
2124 sys.stdout.flush()
2125
2126
2127pipeline = queue.LifoQueue()
2128
2129class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2130 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2131 calls to submit() once the limit given as "bound" work items are queued for
2132 execution.
2133 :param bound: Integer - the maximum number of items in the work queue
2134 :param max_workers: Integer - the size of the thread pool
2135 """
2136 def __init__(self, bound, max_workers, **kwargs):
2137 super().__init__(max_workers)
2138 #self.executor = ThreadPoolExecutor(max_workers=max_workers)
2139 self.semaphore = BoundedSemaphore(bound + max_workers)
2140
2141 def submit(self, fn, *args, **kwargs):
2142 self.semaphore.acquire()
2143 try:
2144 future = super().submit(fn, *args, **kwargs)
2145 except:
2146 self.semaphore.release()
2147 raise
2148 else:
2149 future.add_done_callback(lambda x: self.semaphore.release())
2150 return future
2151
2152 def shutdown(self, wait=True):
2153 super().shutdown(wait)
Andrew Boie4ef16c52015-08-28 12:36:03 -07002154
Andrew Boie6acbe632015-07-17 12:03:52 -07002155
2156class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002157 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002158 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002159
Anas Nashif83fc06a2019-06-22 11:04:10 -04002160 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002161 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002162 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002163
Anas Nashif37f9dc52018-02-23 08:53:46 -06002164 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002165
2166 self.roots = testcase_roots
2167 if not isinstance(board_root_list, list):
2168 self.board_roots= [board_root_list]
2169 else:
2170 self.board_roots = board_root_list
2171
Andrew Boie6acbe632015-07-17 12:03:52 -07002172 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002173 self.testcases = {}
2174 self.platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002175 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002176 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002177 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002178 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002179 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002180
Anas Nashif83fc06a2019-06-22 11:04:10 -04002181 self.total_tests = 0 # number of test instances
2182 self.total_cases = 0 # number of test cases
2183 self.total_done = 0 # tests completed
2184 self.total_failed = 0
2185 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002186
Anas Nashif83fc06a2019-06-22 11:04:10 -04002187 self.total_platforms = 0
2188 self.start_time = 0
2189 self.duration = 0
2190 self.warnings = 0
2191 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002192
Anas Nashif83fc06a2019-06-22 11:04:10 -04002193 # hardcoded for now
2194 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002195
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002196
Anas Nashif83fc06a2019-06-22 11:04:10 -04002197 if options.jobs:
2198 self.jobs = options.jobs
2199 elif options.build_only:
2200 self.jobs = multiprocessing.cpu_count() * 2
Andy Ross9c9162d2019-01-03 10:50:53 -08002201 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002202 self.jobs = multiprocessing.cpu_count()
Daniel Leung6b170072016-04-07 12:10:25 -07002203
Anas Nashif83fc06a2019-06-22 11:04:10 -04002204 info("JOBS: %d" % self.jobs)
Andrew Boie6acbe632015-07-17 12:03:52 -07002205
Anas Nashif83fc06a2019-06-22 11:04:10 -04002206 def update(self):
2207 self.total_tests = len(self.instances)
2208 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002209
Andrew Boie6acbe632015-07-17 12:03:52 -07002210
2211 def compare_metrics(self, filename):
2212 # name, datatype, lower results better
2213 interesting_metrics = [("ram_size", int, True),
2214 ("rom_size", int, True)]
2215
Andrew Boie6acbe632015-07-17 12:03:52 -07002216
2217 if not os.path.exists(filename):
2218 info("Cannot compare metrics, %s not found" % filename)
2219 return []
2220
2221 results = []
2222 saved_metrics = {}
2223 with open(filename) as fp:
2224 cr = csv.DictReader(fp)
2225 for row in cr:
2226 d = {}
2227 for m, _, _ in interesting_metrics:
2228 d[m] = row[m]
2229 saved_metrics[(row["test"], row["platform"])] = d
2230
Anas Nashif83fc06a2019-06-22 11:04:10 -04002231 for instance in self.instances.values():
2232 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002233 if mkey not in saved_metrics:
2234 continue
2235 sm = saved_metrics[mkey]
2236 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002237 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002238 continue
2239 if sm[metric] == "":
2240 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002241 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002242 if delta == 0:
2243 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002244 results.append((instance, metric, instance.metrics.get(metric, 0 ), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002245 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002246 return results
2247
Anas Nashif83fc06a2019-06-22 11:04:10 -04002248 def misc_reports(self, report, show_footprint, all_deltas,
2249 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002250
Anas Nashif83fc06a2019-06-22 11:04:10 -04002251 if not report:
2252 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002253
Anas Nashif83fc06a2019-06-22 11:04:10 -04002254 deltas = self.compare_metrics(report)
2255 warnings = 0
2256 if deltas and show_footprint:
2257 for i, metric, value, delta, lower_better in deltas:
2258 if not all_deltas and ((delta < 0 and lower_better) or
2259 (delta > 0 and not lower_better)):
2260 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002261
Anas Nashif83fc06a2019-06-22 11:04:10 -04002262 percentage = (float(delta) / float(value - delta))
2263 if not all_deltas and (percentage <
2264 (footprint_threshold / 100.0)):
2265 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002266
Anas Nashif83fc06a2019-06-22 11:04:10 -04002267 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
2268 i.platform.name, i.testcase.name, COLOR_YELLOW,
2269 "INFO" if all_deltas else "WARNING", COLOR_NORMAL,
2270 metric, delta, value, percentage))
2271 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002272
Anas Nashif83fc06a2019-06-22 11:04:10 -04002273 if warnings:
2274 info("Deltas based on metrics from last %s" %
2275 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002276
Anas Nashif83fc06a2019-06-22 11:04:10 -04002277 def summary(self, unrecognized_sections):
2278 failed = 0
2279 for instance in self.instances.values():
2280 if instance.status == "failed":
2281 failed += 1
2282 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
2283 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2284 (COLOR_RED, COLOR_NORMAL, instance.name,
2285 str(instance.metrics.get("unrecognized", []))))
2286 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002287
Anas Nashif83fc06a2019-06-22 11:04:10 -04002288 if self.total_tests and self.total_tests != self.total_skipped:
2289 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped)/ float(self.total_tests - self.total_skipped))
2290 else:
2291 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002292
Anas Nashif83fc06a2019-06-22 11:04:10 -04002293 info("{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
2294 COLOR_RED if failed else COLOR_GREEN,
2295 self.total_tests - self.total_failed - self.total_skipped,
2296 self.total_tests,
2297 COLOR_NORMAL,
2298 pass_rate,
2299 COLOR_RED if self.total_failed else COLOR_NORMAL,
2300 self.total_failed,
2301 COLOR_NORMAL,
2302 self.total_skipped,
2303 COLOR_YELLOW if self.warnings else COLOR_NORMAL,
2304 self.warnings,
2305 COLOR_NORMAL,
2306 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002307
Anas Nashif83fc06a2019-06-22 11:04:10 -04002308 platforms = set(p.platform for p in self.instances.values())
2309 self.total_platforms = len(self.platforms)
2310 if self.platforms:
2311 info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
2312 self.total_cases,
2313 len(platforms),
2314 self.total_platforms,
2315 (100 * len(platforms) / len(self.platforms))
2316 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002317
Anas Nashif83fc06a2019-06-22 11:04:10 -04002318 def save_reports(self):
2319 if not self.instances:
2320 return
Anas Nashif61e21632018-04-08 13:30:16 -05002321
Anas Nashif83fc06a2019-06-22 11:04:10 -04002322 report_name = "sanitycheck"
2323 if options.report_name:
2324 report_name = options.report_name
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002325
Anas Nashif83fc06a2019-06-22 11:04:10 -04002326 if options.report_dir:
Paul Sokolovsky3ea18692019-10-15 17:18:39 +03002327 os.makedirs(options.report_dir, exist_ok=True)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002328 filename = os.path.join(options.report_dir, report_name)
2329 outdir = options.report_dir
2330 else:
2331 filename = os.path.join(options.outdir, report_name)
2332 outdir = options.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002333
Anas Nashif83fc06a2019-06-22 11:04:10 -04002334 if not options.no_update:
2335 self.xunit_report(filename + ".xml")
2336 self.csv_report(filename + ".csv")
2337 self.target_report(outdir)
2338 if self.discards:
2339 self.discard_report(filename + "_discard.csv")
2340
2341 if options.release:
2342 self.csv_report(RELEASE_DATA)
2343
2344 if log_file:
2345 log_file.close()
2346
2347 def load_hardware_map_from_cmdline(self, serial, platform):
2348 device = {
2349 "serial": serial,
2350 "platform": platform,
2351 "counter": 0,
2352 "available": True,
2353 "connected": True
2354 }
2355 self.connected_hardware = [device]
2356
2357 def load_hardware_map(self, map_file):
2358 with open(map_file, 'r') as stream:
2359 try:
2360 self.connected_hardware = yaml.safe_load(stream)
2361 except yaml.YAMLError as exc:
2362 print(exc)
2363 for i in self.connected_hardware:
2364 i['counter'] = 0
2365
2366 def add_configurations(self):
2367
2368 for board_root in self.board_roots:
2369 board_root = os.path.abspath(board_root)
2370
2371 debug("Reading platform configuration files under %s..." %
2372 board_root)
2373
2374 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
2375 verbose("Found plaform configuration " + file)
2376 try:
2377 platform = Platform()
2378 platform.load(file)
2379 if platform.sanitycheck:
2380 self.platforms.append(platform)
2381 if platform.default:
2382 self.default_platforms.append(platform.name)
2383
2384 except RuntimeError as e:
2385 error("E: %s: can't load: %s" % (file, e))
2386 self.load_errors += 1
2387
2388 @staticmethod
2389 def get_toolchain():
2390 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2391 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2392
2393 if toolchain == "gccarmemb":
2394 # Remove this translation when gccarmemb is no longer supported.
2395 toolchain = "gnuarmemb"
2396
Anas Nashifb4bdd662018-08-15 17:12:28 -05002397 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002398 if not toolchain:
2399 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002400 except Exception as e:
2401 print(str(e))
2402 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002403
Anas Nashif83fc06a2019-06-22 11:04:10 -04002404 return toolchain
2405
2406
2407 def add_testcases(self):
2408 for root in self.roots:
2409 root = os.path.abspath(root)
2410
2411 debug("Reading test case configuration files under %s..." %root)
2412
2413 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
2414 verbose("scanning %s" % dirpath)
2415 if 'sample.yaml' in filenames:
2416 filename = 'sample.yaml'
2417 elif 'testcase.yaml' in filenames:
2418 filename = 'testcase.yaml'
2419 else:
2420 continue
2421
2422 verbose("Found possible test case in " + dirpath)
2423
2424 dirnames[:] = []
2425 tc_path = os.path.join(dirpath, filename)
2426 self.add_testcase(tc_path, root)
2427
2428 def add_testcase(self, tc_data_file, root):
2429 try:
2430 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2431 parsed_data.load()
2432
2433 tc_path = os.path.dirname(tc_data_file)
2434 workdir = os.path.relpath(tc_path, root)
2435
2436 for name in parsed_data.tests.keys():
2437 tc = TestCase()
2438 tc.name = tc.get_unique(root, workdir, name)
2439
2440 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2441
2442 tc.source_dir = tc_path
2443 tc.yamlfile = tc_data_file
2444
2445 tc.id = name
2446 tc.type = tc_dict["type"]
2447 tc.tags = tc_dict["tags"]
2448 tc.extra_args = tc_dict["extra_args"]
2449 tc.extra_configs = tc_dict["extra_configs"]
2450 tc.arch_whitelist = tc_dict["arch_whitelist"]
2451 tc.arch_exclude = tc_dict["arch_exclude"]
2452 tc.skip = tc_dict["skip"]
2453 tc.platform_exclude = tc_dict["platform_exclude"]
2454 tc.platform_whitelist = tc_dict["platform_whitelist"]
2455 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2456 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2457 tc.tc_filter = tc_dict["filter"]
2458 tc.timeout = tc_dict["timeout"]
2459 tc.harness = tc_dict["harness"]
2460 tc.harness_config = tc_dict["harness_config"]
2461 tc.build_only = tc_dict["build_only"]
2462 tc.build_on_all = tc_dict["build_on_all"]
2463 tc.slow = tc_dict["slow"]
2464 tc.min_ram = tc_dict["min_ram"]
2465 tc.depends_on = tc_dict["depends_on"]
2466 tc.min_flash = tc_dict["min_flash"]
2467 tc.extra_sections = tc_dict["extra_sections"]
2468
2469 tc.parse_subcases(tc_path)
2470
2471 if tc.name:
2472 self.testcases[tc.name] = tc
2473
2474 except Exception as e:
2475 error("E: %s: can't load (skipping): %s" % (tc_data_file, e))
2476 self.load_errors += 1
2477 return False
2478
2479 return True
2480
2481
2482 def get_platform(self, name):
2483 selected_platform = None
2484 for platform in self.platforms:
2485 if platform.name == name:
2486 selected_platform = platform
2487 break
2488 return selected_platform
2489
2490 def get_last_failed(self):
2491 last_run = os.path.join(options.outdir, "sanitycheck.csv")
2492 try:
2493 if not os.path.exists(last_run):
2494 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" %last_run)
2495 except Exception as e:
2496 print(str(e))
2497 sys.exit(2)
2498
2499 total_tests = 0
2500 with open(last_run, "r") as fp:
2501 cr = csv.DictReader(fp)
2502 instance_list = []
2503 for row in cr:
2504 total_tests += 1
2505 if row["passed"] == "True":
2506 continue
2507 test = row["test"]
2508 platform = self.get_platform(row["platform"])
2509 instance = TestInstance(self.testcases[test], platform, self.outdir)
2510 instance.create_overlay(platform.name)
2511 instance_list.append(instance)
2512 self.add_instances(instance_list)
2513
2514 tests_to_run = len(self.instances)
2515 info("%d tests passed already, retyring %d tests" %(total_tests - tests_to_run, tests_to_run))
2516
2517 def load_from_file(self, file):
2518 try:
2519 if not os.path.exists(file):
2520 raise SanityRuntimeError(
2521 "Couldn't find input file with list of tests.")
2522 except Exception as e:
2523 print(str(e))
2524 sys.exit(2)
2525
2526 with open(file, "r") as fp:
2527 cr = csv.DictReader(fp)
2528 instance_list = []
2529 for row in cr:
2530 if row["arch"] == "arch":
2531 continue
2532 test = row["test"]
2533 platform = self.get_platform(row["platform"])
2534 instance = TestInstance(self.testcases[test], platform, self.outdir)
2535 instance.create_overlay(platform.name)
2536 instance_list.append(instance)
2537 self.add_instances(instance_list)
2538
2539
2540 def apply_filters(self):
2541
2542 toolchain = self.get_toolchain()
2543
2544 discards = {}
2545 platform_filter = options.platform
2546 testcase_filter = run_individual_tests
2547 arch_filter = options.arch
2548 tag_filter = options.tag
2549 exclude_tag = options.exclude_tag
2550
2551 verbose("platform filter: " + str(platform_filter))
2552 verbose(" arch_filter: " + str(arch_filter))
2553 verbose(" tag_filter: " + str(tag_filter))
2554 verbose(" exclude_tag: " + str(exclude_tag))
2555
2556 default_platforms = False
2557
2558 if platform_filter:
2559 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2560 else:
2561 platforms = self.platforms
2562
2563 if options.all:
2564 info("Selecting all possible platforms per test case")
2565 # When --all used, any --platform arguments ignored
2566 platform_filter = []
2567 elif not platform_filter:
2568 info("Selecting default platforms per test case")
2569 default_platforms = True
2570
2571 info("Building initial testcase list...")
2572
2573 for tc_name, tc in self.testcases.items():
2574 # list of instances per testcase, aka configurations.
2575 instance_list = []
2576 for plat in platforms:
2577 instance = TestInstance(tc, plat, self.outdir)
2578
2579 if (plat.arch == "unit") != (tc.type == "unit"):
2580 # Discard silently
2581 continue
2582
2583 if options.device_testing and instance.build_only:
2584 discards[instance] = "Not runnable on device"
2585 continue
2586
2587 if tc.skip:
2588 discards[instance] = "Skip filter"
2589 continue
2590
2591 if tc.build_on_all and not platform_filter:
2592 platform_filter = []
2593
2594 if tag_filter and not tc.tags.intersection(tag_filter):
2595 discards[instance] = "Command line testcase tag filter"
2596 continue
2597
2598 if exclude_tag and tc.tags.intersection(exclude_tag):
2599 discards[instance] = "Command line testcase exclude filter"
2600 continue
2601
2602 if testcase_filter and tc_name not in testcase_filter:
2603 discards[instance] = "Testcase name filter"
2604 continue
2605
2606 if arch_filter and plat.arch not in arch_filter:
2607 discards[instance] = "Command line testcase arch filter"
2608 continue
2609
2610 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2611 discards[instance] = "Not in test case arch whitelist"
2612 continue
2613
2614 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2615 discards[instance] = "In test case arch exclude"
2616 continue
2617
2618 if tc.platform_exclude and plat.name in tc.platform_exclude:
2619 discards[instance] = "In test case platform exclude"
2620 continue
2621
2622 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2623 discards[instance] = "In test case toolchain exclude"
2624 continue
2625
2626 if platform_filter and plat.name not in platform_filter:
2627 discards[instance] = "Command line platform filter"
2628 continue
2629
2630 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2631 discards[instance] = "Not in testcase platform whitelist"
2632 continue
2633
2634 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2635 discards[instance] = "Not in testcase toolchain whitelist"
2636 continue
2637
2638 if not plat.env_satisfied:
2639 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2640 continue
2641
2642 if not options.force_toolchain \
2643 and toolchain and (toolchain not in plat.supported_toolchains) \
2644 and tc.type != 'unit':
2645 discards[instance] = "Not supported by the toolchain"
2646 continue
2647
2648 if plat.ram < tc.min_ram:
2649 discards[instance] = "Not enough RAM"
2650 continue
2651
2652 if tc.depends_on:
2653 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2654 if dep_intersection != set(tc.depends_on):
2655 discards[instance] = "No hardware support"
2656 continue
2657
2658 if plat.flash < tc.min_flash:
2659 discards[instance] = "Not enough FLASH"
2660 continue
2661
2662 if set(plat.ignore_tags) & tc.tags:
2663 discards[instance] = "Excluded tags per platform"
2664 continue
2665
2666 # if nothing stopped us until now, it means this configuration
2667 # needs to be added.
2668 instance_list.append(instance)
2669
2670 # no configurations, so jump to next testcase
2671 if not instance_list:
2672 continue
2673
2674 # if sanitycheck was launched with no platform options at all, we
2675 # take all default platforms
2676 if default_platforms and not tc.build_on_all:
2677 if tc.platform_whitelist:
2678 a = set(self.default_platforms)
2679 b = set(tc.platform_whitelist)
2680 c = a.intersection(b)
2681 if c:
2682 aa = list( filter( lambda tc: tc.platform.name in c, instance_list))
2683 self.add_instances(aa)
2684 else:
2685 self.add_instances(instance_list[:1])
2686 else:
2687 instances = list( filter( lambda tc: tc.platform.default, instance_list))
2688 self.add_instances(instances)
2689
2690 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2691 discards[instance] = "Not a default test platform"
2692
2693 else:
2694 self.add_instances(instance_list)
2695
2696 for _, case in self.instances.items():
2697 case.create_overlay(case.platform.name)
2698
2699 self.discards = discards
2700
2701 return discards
2702
2703 def add_instances(self, instance_list):
2704 for instance in instance_list:
2705 self.instances[instance.name] = instance
2706
2707 def add_tasks_to_queue(self):
2708 for instance in self.instances.values():
2709 if options.test_only:
2710 if instance.run:
2711 pipeline.put({"op": "run", "test": instance, "status": "built"})
2712 else:
2713 if instance.status not in ['passed', 'skipped']:
2714 instance.status = None
2715 pipeline.put({"op": "cmake", "test": instance})
2716
2717 return "DONE FEEDING"
2718
2719 def execute(self):
2720 def calc_one_elf_size(instance):
2721 if instance.status not in ["failed", "skipped"]:
2722 if instance.platform.type != "native":
2723 size_calc = instance.calculate_sizes()
2724 instance.metrics["ram_size"] = size_calc.get_ram_size()
2725 instance.metrics["rom_size"] = size_calc.get_rom_size()
2726 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2727 else:
2728 instance.metrics["ram_size"] = 0
2729 instance.metrics["rom_size"] = 0
2730 instance.metrics["unrecognized"] = []
2731
2732 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2733
2734 info("Adding tasks to the queue...")
2735 # We can use a with statement to ensure threads are cleaned up promptly
2736 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2737
2738 # start a future for a thread which sends work in through the queue
2739 future_to_test = {
2740 executor.submit(self.add_tasks_to_queue): 'FEEDER DONE'}
2741
2742 while future_to_test:
2743 # check for status of the futures which are currently working
2744 done, _ = concurrent.futures.wait(
2745 future_to_test, timeout=0.25,
2746 return_when=concurrent.futures.FIRST_COMPLETED)
2747
2748 # if there is incoming work, start a new future
2749 while not pipeline.empty():
2750 # fetch a url from the queue
2751 message = pipeline.get()
2752 test = message['test']
2753
2754 # Start the load operation and mark the future with its URL
2755 pb = ProjectBuilder(self, test)
2756 future_to_test[executor.submit(pb.process, message)] = test.name
2757
2758 # process any completed futures
2759 for future in done:
2760 test = future_to_test[future]
2761 try:
2762 data = future.result()
2763 except Exception as exc:
2764 print('%r generated an exception: %s' % (test, exc))
2765 else:
2766 if data:
2767 verbose(data)
2768
2769 # remove the now completed future
2770 del future_to_test[future]
2771
2772 if options.enable_size_report and not options.cmake_only:
2773 # Parallelize size calculation
2774 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2775 futures = [executor.submit(calc_one_elf_size, instance)
2776 for instance in self.instances.values()]
2777 concurrent.futures.wait(futures)
2778 else:
2779 for instance in self.instances.values():
2780 instance.metrics["ram_size"] = 0
2781 instance.metrics["rom_size"] = 0
2782 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2783 instance.metrics["unrecognized"] = []
2784
2785
2786 def discard_report(self, filename):
2787
2788 try:
2789 if self.discards is None:
2790 raise SanityRuntimeError("apply_filters() hasn't been run!")
2791 except Exception as e:
2792 error(str(e))
2793 sys.exit(2)
2794
2795 with open(filename, "wt") as csvfile:
2796 fieldnames = ["test", "arch", "platform", "reason"]
2797 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2798 cw.writeheader()
2799 for instance, reason in sorted(self.discards.items()):
2800 rowdict = {"test": instance.testcase.name,
2801 "arch": instance.platform.arch,
2802 "platform": instance.platform.name,
2803 "reason": reason}
2804 cw.writerow(rowdict)
2805
2806
2807 def target_report(self, outdir):
2808 run = "Sanitycheck"
2809 eleTestsuite = None
2810
2811 platforms = {inst.platform.name for _,inst in self.instances.items()}
2812 for platform in platforms:
2813 errors = 0
2814 passes = 0
2815 fails = 0
2816 duration = 0
2817 skips = 0
2818 for _, instance in self.instances.items():
2819 if instance.platform.name != platform:
2820 continue
2821
2822 handler_time = instance.metrics.get('handler_time', 0)
2823 duration += handler_time
2824 for k in instance.results.keys():
2825 if instance.results[k] == 'PASS':
2826 passes += 1
2827 elif instance.results[k] == 'BLOCK':
2828 errors += 1
2829 elif instance.results[k] == 'SKIP':
2830 skips += 1
2831 else:
2832 fails += 1
2833
2834 eleTestsuites = ET.Element('testsuites')
2835 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2836 name=run, time="%f" % duration,
2837 tests="%d" % (errors + passes + fails),
2838 failures="%d" % fails,
2839 errors="%d" % errors, skipped="%d" %skips)
2840
2841 handler_time = 0
2842
2843 # print out test results
2844 for _, instance in self.instances.items():
2845 if instance.platform.name != platform:
2846 continue
2847 handler_time = instance.metrics.get('handler_time', 0)
2848 for k in instance.results.keys():
2849 eleTestcase = ET.SubElement(
2850 eleTestsuite, 'testcase', classname="%s:%s" %(instance.platform.name, os.path.basename(instance.testcase.name)),
2851 name="%s" % (k), time="%f" %handler_time)
2852 if instance.results[k] in ['FAIL', 'BLOCK']:
2853 el = None
2854
2855 if instance.results[k] == 'FAIL':
2856 el = ET.SubElement(
2857 eleTestcase,
2858 'failure',
2859 type="failure",
2860 message="failed")
2861 elif instance.results[k] == 'BLOCK':
2862 el = ET.SubElement(
2863 eleTestcase,
2864 'error',
2865 type="failure",
2866 message="failed")
2867 p = os.path.join(options.outdir, instance.platform.name, instance.testcase.name)
2868 log_file = os.path.join(p, "handler.log")
2869
2870 if os.path.exists(log_file):
2871 with open(log_file, "rb") as f:
2872 log = f.read().decode("utf-8")
2873 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2874 el.text = filtered_string
2875
2876 elif instance.results[k] == 'SKIP':
2877 el = ET.SubElement(
2878 eleTestcase,
2879 'skipped',
2880 type="skipped",
2881 message="Skipped")
2882
2883
2884 result = ET.tostring(eleTestsuites)
2885 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
2886 f.write(result)
2887
2888
2889 def xunit_report(self, filename):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002890 fails = 0
2891 passes = 0
2892 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002893 skips = 0
2894 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04002895
Anas Nashif83fc06a2019-06-22 11:04:10 -04002896 for instance in self.instances.values():
2897 handler_time = instance.metrics.get('handler_time', 0)
2898 duration += handler_time
2899 if instance.status == "failed":
2900 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002901 errors += 1
2902 else:
2903 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04002904 elif instance.status == 'skipped':
2905 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04002906 else:
2907 passes += 1
2908
2909 run = "Sanitycheck"
2910 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002911 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002912
Anas Nashif83fc06a2019-06-22 11:04:10 -04002913 # When we re-run the tests, we re-use the results and update only with
2914 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04002915 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002916 tree = ET.parse(filename)
2917 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002918 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002919 else:
2920 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002921 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04002922 name=run, time="%f" % duration,
2923 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05002924 failures="%d" % fails,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002925 errors="%d" %(errors), skip="%s" %(skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002926
Anas Nashif83fc06a2019-06-22 11:04:10 -04002927 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04002928
Anas Nashif83fc06a2019-06-22 11:04:10 -04002929 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04002930 if append:
2931 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002932 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04002933 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002934 eleTestsuite.remove(tc)
2935
Anas Nashif83fc06a2019-06-22 11:04:10 -04002936 handler_time = 0
2937 if instance.status != "failed" and instance.handler:
2938 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002939
Anas Nashif3ba1d432017-12-05 15:28:44 -05002940 eleTestcase = ET.SubElement(
2941 eleTestsuite, 'testcase', classname="%s:%s" %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002942 (instance.platform.name, instance.testcase.name), name="%s" %
2943 (instance.testcase.name), time="%f" %handler_time)
2944
2945 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05002946 failure = ET.SubElement(
2947 eleTestcase,
2948 'failure',
2949 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002950 message=instance.reason)
2951 p = ("%s/%s/%s" % (options.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002952 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002953 hl = os.path.join(p, "handler.log")
2954 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04002955 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002956 if os.path.exists(hl):
2957 log_file = hl
2958 else:
2959 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002960
Anas Nashifc96c90a2019-02-05 07:38:32 -05002961 if os.path.exists(log_file):
2962 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002963 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002964 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2965 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002966 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002967 elif instance.status == "skipped":
2968 ET.SubElement( eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002969
2970 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002971 with open(filename, 'wb') as report:
2972 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002973
Andrew Boie6acbe632015-07-17 12:03:52 -07002974
Anas Nashif83fc06a2019-06-22 11:04:10 -04002975 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08002976 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002977 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002978 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002979 "rom_size"]
2980 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2981 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002982 for instance in sorted(self.instances.values()):
2983 rowdict = {"test": instance.testcase.name,
2984 "arch": instance.platform.arch,
2985 "platform": instance.platform.name,
2986 "extra_args": " ".join(instance.testcase.extra_args),
2987 "handler": instance.platform.simulation}
2988
2989 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07002990 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04002991 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07002992 else:
2993 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04002994 if instance.handler:
2995 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
2996 ram_size = instance.metrics.get("ram_size", 0)
2997 rom_size = instance.metrics.get("rom_size", 0)
2998 rowdict["ram_size"] = ram_size
2999 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003000 cw.writerow(rowdict)
3001
3002
3003def parse_arguments():
3004
Anas Nashif3ba1d432017-12-05 15:28:44 -05003005 parser = argparse.ArgumentParser(
3006 description=__doc__,
3007 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003008 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003009
Marc Herbert932a33a2019-03-12 11:37:53 -07003010 case_select = parser.add_argument_group("Test case selection",
3011 """
3012Artificially long but functional example:
3013 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003014 --testcase-root tests/ztest/base \\
3015 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003016 --test tests/ztest/base/testing.ztest.verbose_0 \\
3017 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3018
3019 "kernel.fifo.poll" is one of the test section names in
3020 __/fifo_api/testcase.yaml
3021 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003022
Anas Nashif07d54c02018-07-21 19:29:08 -05003023 parser.add_argument("--force-toolchain", action="store_true",
3024 help="Do not filter based on toolchain, use the set "
3025 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003026 parser.add_argument(
3027 "-p", "--platform", action="append",
3028 help="Platform filter for testing. This option may be used multiple "
3029 "times. Testcases will only be built/run on the platforms "
3030 "specified. If this option is not used, then platforms marked "
3031 "as default in the platform metadata file will be chosen "
3032 "to build and test. ")
3033 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003034 "-a", "--arch", action="append",
3035 help="Arch filter for testing. Takes precedence over --platform. "
3036 "If unspecified, test all arches. Multiple invocations "
3037 "are treated as a logical 'or' relationship")
3038 parser.add_argument(
3039 "-t", "--tag", action="append",
3040 help="Specify tags to restrict which tests to run by tag value. "
3041 "Default is to not do any tag filtering. Multiple invocations "
3042 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003043 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003044 help="Specify tags of tests that should not run. "
3045 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003046 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003047 "-f",
3048 "--only-failed",
3049 action="store_true",
3050 help="Run only those tests that failed the previous sanity check "
3051 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003052
Anas Nashif3ba1d432017-12-05 15:28:44 -05003053 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003054 "--retry-failed", type=int, default=0,
3055 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003056
Marc Herbert0c465bb2019-03-11 17:28:36 -07003057 test_xor_subtest = case_select.add_mutually_exclusive_group()
3058
3059 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003060 "-s", "--test", action="append",
3061 help="Run only the specified test cases. These are named by "
Marc Herberte5cedca2019-04-08 14:02:34 -07003062 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003063
Marc Herbert0c465bb2019-03-11 17:28:36 -07003064 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003065 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003066 help="""Recursively find sub-test functions and run the entire
3067 test section where they were found, including all sibling test
3068 functions. Sub-tests are named by:
3069 section.name.in.testcase.yaml.function_name_without_test_prefix
3070 Example: kernel.fifo.poll.fifo_loop
3071 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003072
Anas Nashif3ba1d432017-12-05 15:28:44 -05003073 parser.add_argument(
3074 "-l", "--all", action="store_true",
3075 help="Build/test on all platforms. Any --platform arguments "
3076 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003077
Anas Nashif3ba1d432017-12-05 15:28:44 -05003078 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003079 "-o", "--report-dir",
3080 help="""Output reports containing results of the test run into the
3081 specified directory.
3082 The output will be both in CSV and JUNIT format
3083 (sanitycheck.csv and sanitycheck.xml).
3084 """)
3085
Anas Nashif3ba1d432017-12-05 15:28:44 -05003086 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003087 "--report-name",
3088 help="""Create a report with a custom name.
3089 """)
3090
3091 parser.add_argument("--detailed-report",
3092 action="store",
3093 metavar="FILENAME",
3094 help="""Generate a junit report with detailed testcase results.
3095 Unlike the CSV file produced by --testcase-report, this XML
3096 report includes only tests which have run and none which were
3097 merely built. If an image with multiple tests crashes early then
3098 later tests are not accounted for either.""")
3099
3100 parser.add_argument("--report-excluded",
3101 action="store_true",
3102 help="""List all tests that are never run based on current scope and
3103 coverage. If you are looking for accurate results, run this with
3104 --all, but this will take a while...""")
3105
Daniel Leung7f850102016-04-08 11:07:32 -07003106 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003107 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003108
Anas Nashif3ba1d432017-12-05 15:28:44 -05003109 parser.add_argument(
3110 "-B", "--subset",
3111 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
3112 "3/5 means run the 3rd fifth of the total. "
3113 "This option is useful when running a large number of tests on "
3114 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003115
3116 parser.add_argument(
3117 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003118 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003119
Anas Nashif3ba1d432017-12-05 15:28:44 -05003120 parser.add_argument(
3121 "-y", "--dry-run", action="store_true",
3122 help="Create the filtered list of test cases, but don't actually "
3123 "run them. Useful if you're just interested in "
3124 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07003125
Anas Nashif75547e22018-02-24 08:32:14 -06003126 parser.add_argument("--list-tags", action="store_true",
3127 help="list all tags in selected tests")
3128
Marc Herbertedf17592019-03-08 12:39:11 -08003129 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07003130 help="""List of all sub-test functions recursively found in
3131 all --testcase-root arguments. Note different sub-tests can share
3132 the same section name and come from different directories.
3133 The output is flattened and reports --sub-test names only,
3134 not their directories. For instance net.socket.getaddrinfo_ok
3135 and net.socket.fd_set belong to different directories.
3136 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003137
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003138 parser.add_argument("--export-tests", action="store",
3139 metavar="FILENAME",
3140 help="Export tests case meta-data to a file in CSV format.")
3141
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003142
Anas Nashif654ec5982019-04-11 08:38:21 -04003143 parser.add_argument("--timestamps",
3144 action="store_true",
3145 help="Print all messages with time stamps")
3146
Anas Nashif3ba1d432017-12-05 15:28:44 -05003147 parser.add_argument(
3148 "-r", "--release", action="store_true",
3149 help="Update the benchmark database with the results of this test "
3150 "run. Intended to be run by CI when tagging an official "
3151 "release. This database is used as a basis for comparison "
3152 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003153 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003154 help="Treat warning conditions as errors")
3155 parser.add_argument(
3156 "-v",
3157 "--verbose",
3158 action="count",
3159 default=0,
3160 help="Emit debugging information, call multiple times to increase "
3161 "verbosity")
3162 parser.add_argument(
3163 "-i", "--inline-logs", action="store_true",
3164 help="Upon test failure, print relevant log data to stdout "
3165 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003166 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003167 help="log also to file")
3168 parser.add_argument(
3169 "-m", "--last-metrics", action="store_true",
3170 help="Instead of comparing metrics from the last --release, "
3171 "compare with the results of the previous sanity check "
3172 "invocation")
3173 parser.add_argument(
3174 "-u",
3175 "--no-update",
3176 action="store_true",
3177 help="do not update the results of the last run of the sanity "
3178 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003179 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003180 "-F",
3181 "--load-tests",
3182 metavar="FILENAME",
3183 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003184 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003185
Marc Herbertedf17592019-03-08 12:39:11 -08003186 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003187 "-E",
3188 "--save-tests",
3189 metavar="FILENAME",
3190 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003191 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003192
Andy Doancbecadd2019-02-08 10:19:10 -06003193 test_or_build = parser.add_mutually_exclusive_group()
3194 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003195 "-b", "--build-only", action="store_true",
3196 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003197
Andy Doancbecadd2019-02-08 10:19:10 -06003198 test_or_build.add_argument(
3199 "--test-only", action="store_true",
3200 help="""Only run device tests with current artifacts, do not build
3201 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003202 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003203 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003204 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003205
3206 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003207 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003208 help="Number of jobs for building, defaults to number of CPU threads, "
3209 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003210
3211 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06003212 "--show-footprint", action="store_true",
3213 help="Show footprint statistics and deltas since last release."
3214 )
3215 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003216 "-H", "--footprint-threshold", type=float, default=5,
3217 help="When checking test case footprint sizes, warn the user if "
3218 "the new app size is greater then the specified percentage "
3219 "from the last release. Default is 5. 0 to warn on any "
3220 "increase on app size")
3221 parser.add_argument(
3222 "-D", "--all-deltas", action="store_true",
3223 help="Show all footprint deltas, positive or negative. Implies "
3224 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003225 parser.add_argument(
3226 "-O", "--outdir",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003227 default=os.path.join(os.getcwd(),"sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003228 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05003229 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003230 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003231 parser.add_argument(
3232 "-n", "--no-clean", action="store_true",
3233 help="Do not delete the outdir before building. Will result in "
3234 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08003235 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003236 "-T", "--testcase-root", action="append", default=[],
3237 help="Base directory to recursively search for test cases. All "
3238 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07003239 "called multiple times. Defaults to the 'samples/' and "
3240 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003241
Anas Nashif3ba1d432017-12-05 15:28:44 -05003242 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3243 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003244
Anas Nashif3ba1d432017-12-05 15:28:44 -05003245 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003246 "-A", "--board-root", default=board_root_list,
3247 help="""Directory to search for board configuration files. All .yaml
3248files in the directory will be processed. The directory should have the same
3249structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3250
Anas Nashif3ba1d432017-12-05 15:28:44 -05003251 parser.add_argument(
3252 "-z", "--size", action="append",
3253 help="Don't run sanity checks. Instead, produce a report to "
3254 "stdout detailing RAM/ROM sizes on the specified filenames. "
3255 "All other command line arguments ignored.")
3256 parser.add_argument(
3257 "-S", "--enable-slow", action="store_true",
3258 help="Execute time-consuming test cases that have been marked "
3259 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003260 parser.add_argument(
3261 "--disable-unrecognized-section-test", action="store_true",
3262 default=False,
3263 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003264 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003265 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003266 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003267 parser.add_argument("--disable-asserts", action="store_false",
3268 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003269 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003270 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003271 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003272 parser.add_argument("--enable-size-report", action="store_true",
3273 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003274
3275 parser.add_argument(
3276 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003277 help="""Extra CMake cache entries to define when building test cases.
3278 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003279 prefixed with -D before being passed to CMake.
3280
3281 E.g
3282 "sanitycheck -x=USE_CCACHE=0"
3283 will translate to
3284 "cmake -DUSE_CCACHE=0"
3285
3286 which will ultimately disable ccache.
3287 """
3288 )
Michael Scott421ce462019-06-18 09:37:46 -07003289
Andy Doan79c48842019-02-08 10:09:04 -06003290 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003291 "--device-testing", action="store_true",
3292 help="Test on device directly. Specify the serial device to "
3293 "use with the --device-serial option.")
3294
3295 parser.add_argument(
3296 "-X", "--fixture", action="append", default=[],
3297 help="Specify a fixture that a board might support")
3298 parser.add_argument(
3299 "--device-serial",
3300 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3301
3302 parser.add_argument("--generate-hardware-map",
3303 help="""Probe serial devices connected to this platform
3304 and create a hardware map file to be used with
3305 --device-testing
3306 """)
3307
3308 parser.add_argument("--hardware-map",
3309 help="""Load hardware map from a file. This will be used
3310 for testing on hardware that is listed in the file.
3311 """)
3312
3313 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003314 "--west-flash", nargs='?', const=[],
3315 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003316 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003317
Michael Scott4ca54392019-07-09 14:21:30 -07003318 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003319 --west-flash="--board-id=foobar,--erase"
3320 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003321
3322 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003323 """
3324 )
Michael Scott421ce462019-06-18 09:37:46 -07003325 parser.add_argument(
3326 "--west-runner",
3327 help="""Uses the specified west runner instead of default when running
3328 with --west-flash.
3329
3330 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3331 --west-flash --west-runner=pyocd"
3332 will translate to "west flash --runner pyocd"
3333
3334 NOTE: west-flash must be enabled to use this option.
3335 """
3336 )
Andrew Boie8047a6f2019-07-02 15:43:29 -07003337
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003338 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003339 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003340
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003341 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003342 help="Generate coverage reports. Implies "
3343 "--enable_coverage and --enable-slow")
Andrew Boie6acbe632015-07-17 12:03:52 -07003344
Andrew Boie8047a6f2019-07-02 15:43:29 -07003345 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003346 help="Plarforms to run coverage reports on. "
Andrew Boie8047a6f2019-07-02 15:43:29 -07003347 "This option may be used multiple times. "
3348 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003349
Anas Nashif83fc06a2019-06-22 11:04:10 -04003350 parser.add_argument("--gcov-tool", default=None,
3351 help="Path to the gcov tool to use for code coverage "
3352 "reports")
3353
Andrew Boie6acbe632015-07-17 12:03:52 -07003354 return parser.parse_args()
3355
Anas Nashif3ba1d432017-12-05 15:28:44 -05003356
Andrew Boie6acbe632015-07-17 12:03:52 -07003357def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003358 filename = os.path.relpath(os.path.realpath(filename))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003359 if options.inline_logs:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003360 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003361
3362 try:
3363 with open(filename) as fp:
3364 data = fp.read()
3365 except Exception as e:
3366 data = "Unable to read log data (%s)\n" % (str(e))
3367
3368 sys.stdout.write(data)
3369 if log_file:
3370 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003371 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003372 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003373 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003374
Andrew Boiebbd670c2015-08-17 13:16:11 -07003375def size_report(sc):
3376 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07003377 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003378 for i in range(len(sc.sections)):
3379 v = sc.sections[i]
3380
Andrew Boie73b4ee62015-10-07 11:33:22 -07003381 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
3382 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3383 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003384
Andrew Boie73b4ee62015-10-07 11:33:22 -07003385 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003386 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003387 info("")
3388
Anas Nashiff29087e2019-01-25 09:37:38 -05003389def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003390 if VERBOSE:
3391 print("Working on %s" %intput_file)
3392 extracted_coverage_info = {}
3393 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003394 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003395 with open(intput_file, 'r') as fp:
3396 for line in fp.readlines():
3397 if re.search("GCOV_COVERAGE_DUMP_START", line):
3398 capture_data = True
3399 continue
3400 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003401 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003402 break
3403 # Loop until the coverage data is found.
3404 if not capture_data:
3405 continue
3406 if line.startswith("*"):
3407 sp = line.split("<")
3408 if len(sp) > 1:
3409 # Remove the leading delimiter "*"
3410 file_name = sp[0][1:]
3411 # Remove the trailing new line char
3412 hex_dump = sp[1][:-1]
3413 else:
3414 continue
3415 else:
3416 continue
3417 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003418 if not capture_data:
3419 capture_complete = True
3420 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003421
3422def create_gcda_files(extracted_coverage_info):
3423 if VERBOSE:
3424 print("Generating gcda files")
3425 for filename, hexdump_val in extracted_coverage_info.items():
3426 # if kobject_hash is given for coverage gcovr fails
3427 # hence skipping it problem only in gcovr v4.1
3428 if "kobject_hash" in filename:
3429 filename = (filename[:-4]) +"gcno"
3430 try:
3431 os.remove(filename)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003432 except Exception:
Anas Nashifdb9592a2018-10-08 10:19:41 -04003433 pass
3434 continue
3435
3436 with open(filename, 'wb') as fp:
3437 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003438
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003439def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003440
3441 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003442 gcov_data = retrieve_gcov_data(filename)
3443 capture_complete = gcov_data['complete']
3444 extracted_coverage_info = gcov_data['data']
3445 if capture_complete:
3446 create_gcda_files(extracted_coverage_info)
3447 verbose("Gcov data captured: {}".format(filename))
3448 else:
3449 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003450
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003451 gcov_tool = options.gcov_tool
3452
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003453 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3454 coveragefile = os.path.join(outdir, "coverage.info")
3455 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003456 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3457 "--capture", "--directory", outdir,
3458 "--rc", "lcov_branch_coverage=1",
3459 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003460 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003461 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003462 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003463 "--output-file", ztestfile,
3464 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3465
Anas Nashif3cbffef2018-11-07 23:50:54 -05003466 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003467 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003468 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3469 "--output-file", ztestfile,
3470 "--rc", "lcov_branch_coverage=1"],
3471 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003472 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003473 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003474 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003475
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003476 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003477 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003478 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3479 coveragefile, i, "--output-file",
3480 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003481 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003482
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003483 #The --ignore-errors source option is added to avoid it exiting due to
3484 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003485 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003486 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003487 "-output-directory",
3488 os.path.join(outdir, "coverage")] + files,
3489 stdout=coveragelog)
3490 if ret==0:
3491 info("HTML report generated: %s"%
Anas Nashif83fc06a2019-06-22 11:04:10 -04003492 os.path.join(outdir, "coverage","index.html"))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003493
Anas Nashif83fc06a2019-06-22 11:04:10 -04003494def get_generator():
3495 if options.ninja:
3496 generator_cmd = "ninja"
3497 generator = "Ninja"
3498 else:
3499 generator_cmd = "make"
3500 generator = "Unix Makefiles"
3501 return generator_cmd, generator
3502
3503
3504def export_tests(filename, tests):
3505 with open(filename, "wt") as csvfile:
3506 fieldnames = ['section', 'subsection', 'title', 'reference']
3507 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3508 for test in tests:
3509 data = test.split(".")
3510 subsec = " ".join(data[1].split("_")).title()
3511 rowdict = {
3512 "section": data[0].capitalize(),
3513 "subsection": subsec,
3514 "title": test,
3515 "reference": test
3516 }
3517 cw.writerow(rowdict)
3518
3519def get_unique_tests(suite):
3520 unq = []
3521 run_individual_tests = []
3522 for _, tc in suite.testcases.items():
3523 for c in tc.cases:
3524 if options.sub_test and c in options.sub_test:
3525 if tc.name not in run_individual_tests:
3526 run_individual_tests.append(tc.name)
3527 unq.append(c)
3528
3529 return unq
3530
3531
3532
3533def native_and_unit_first(a, b):
3534 if a[0].startswith('unit_testing'):
3535 return -1
3536 if b[0].startswith('unit_testing'):
3537 return 1
3538 if a[0].startswith('native_posix'):
3539 return -1
3540 if b[0].startswith('native_posix'):
3541 return 1
3542 if a[0].split("/",1)[0].endswith("_bsim"):
3543 return -1
3544 if b[0].split("/",1)[0].endswith("_bsim"):
3545 return 1
3546
3547 return (a > b) - (a < b)
3548
3549
3550run_individual_tests = None
3551options = None
Andrew Boiebbd670c2015-08-17 13:16:11 -07003552
Andrew Boie6acbe632015-07-17 12:03:52 -07003553def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003554 start_time = time.time()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003555 global VERBOSE, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003556 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003557 global run_individual_tests
Andrew Boie1578ef72019-07-03 10:19:29 -07003558
Anas Nashife10b6512017-12-30 13:01:45 -05003559 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003560
Anas Nashif83fc06a2019-06-22 11:04:10 -04003561
3562 if options.generate_hardware_map:
3563 from serial.tools import list_ports
3564 serial_devices = list_ports.comports()
3565 filtered = []
3566 for d in serial_devices:
3567 if d.manufacturer in ['ARM', 'SEGGER', 'MBED', 'STMicroelectronics', 'Atmel Corp.']:
3568 s_dev = {}
3569 s_dev['platform'] = "unknown"
3570 s_dev['id'] = d.serial_number
3571 s_dev['serial'] = d.device
3572 s_dev['product'] = d.product
3573 if s_dev['product'] in ['DAPLink CMSIS-DAP', 'MBED CMSIS-DAP']:
3574 s_dev['runner'] = "pyocd"
3575 else:
3576 s_dev['runner'] = "unknown"
3577 s_dev['available'] = True
3578 s_dev['connected'] = True
3579 filtered.append(s_dev)
3580 else:
3581 print("Unsupported device (%s): %s" %(d.manufacturer, d))
3582
3583 if os.path.exists(options.generate_hardware_map):
3584 # use existing map
3585
3586 with open(options.generate_hardware_map, 'r') as yaml_file:
3587 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3588 # disconnect everything
3589 for h in hwm:
3590 h['connected'] = False
3591
3592 for d in filtered:
3593 for h in hwm:
3594 if d['id'] == h['id'] and d['product'] == h['product']:
3595 print("Already in map: %s (%s)" %(d['product'], d['id']))
3596 h['connected'] = True
3597 h['serial'] = d['serial']
3598 d['match'] = True
3599
3600 new = list(filter(lambda n: not n.get('match', False), filtered))
3601 hwm = hwm + new
3602
3603 #import pprint
3604 #pprint.pprint(hwm)
3605 with open(options.generate_hardware_map, 'w') as yaml_file:
3606 yaml.dump(hwm, yaml_file, default_flow_style=False)
3607
3608
3609 else:
3610 # create new file
3611 with open(options.generate_hardware_map, 'w') as yaml_file:
3612 yaml.dump(filtered, yaml_file, default_flow_style=False)
3613
3614 return
3615
3616
Michael Scott421ce462019-06-18 09:37:46 -07003617 if options.west_runner and not options.west_flash:
3618 error("west-runner requires west-flash to be enabled")
3619 sys.exit(1)
3620
Michael Scott4ca54392019-07-09 14:21:30 -07003621 if options.west_flash and not options.device_testing:
3622 error("west-flash requires device-testing to be enabled")
3623 sys.exit(1)
3624
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003625 if options.coverage:
3626 options.enable_coverage = True
Andrew Boie8047a6f2019-07-02 15:43:29 -07003627 options.enable_slow = True
3628 if not options.coverage_platform:
3629 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003630
Anas Nashife10b6512017-12-30 13:01:45 -05003631 if options.size:
3632 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003633 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003634 sys.exit(0)
3635
Anas Nashife10b6512017-12-30 13:01:45 -05003636 VERBOSE += options.verbose
Anas Nashif83fc06a2019-06-22 11:04:10 -04003637
Anas Nashife10b6512017-12-30 13:01:45 -05003638 if options.log_file:
3639 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003640
Anas Nashife10b6512017-12-30 13:01:45 -05003641 if options.subset:
3642 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003643 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003644 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003645 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003646 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003647 return
3648
Anas Nashif83fc06a2019-06-22 11:04:10 -04003649 # Cleanup
3650
3651 if options.no_clean or options.only_failed or options.test_only:
3652 if os.path.exists(options.outdir):
3653 info("Keeping artifacts untouched")
3654 elif os.path.exists(options.outdir):
3655 for i in range(1,100):
3656 new_out = options.outdir + ".{}".format(i)
3657 if not os.path.exists(new_out):
3658 info("Renaming output directory to {}".format(new_out))
3659 shutil.move(options.outdir, new_out)
3660 break
3661 #shutil.rmtree("%s.old" %options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003662
Anas Nashife10b6512017-12-30 13:01:45 -05003663 if not options.testcase_root:
3664 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003665 os.path.join(ZEPHYR_BASE, "samples")]
3666
Anas Nashif83fc06a2019-06-22 11:04:10 -04003667 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
3668 suite.add_testcases()
3669 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04003670
Anas Nashif83fc06a2019-06-22 11:04:10 -04003671 if options.device_testing:
3672 if options.hardware_map:
3673 suite.load_hardware_map(options.hardware_map)
3674 if not options.platform:
3675 options.platform = []
3676 for platform in suite.connected_hardware:
3677 if platform['connected']:
3678 options.platform.append(platform['platform'])
3679
3680 elif options.device_serial: #back-ward compatibility
3681 if options.platform and len(options.platform) == 1:
3682 suite.load_hardware_map_from_cmdline(options.device_serial,
3683 options.platform[0])
3684 else:
3685 error("""When --device-testing is used with --device-serial, only one
3686 platform is allowed""")
3687
3688
3689
3690 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05003691 sys.exit(1)
3692
Anas Nashif75547e22018-02-24 08:32:14 -06003693 if options.list_tags:
3694 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003695 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06003696 tags = tags.union(tc.tags)
3697
3698 for t in tags:
3699 print("- {}".format(t))
3700
3701 return
3702
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003703 if options.export_tests:
3704 cnt = 0
3705 unq = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04003706 for _, tc in suite.testcases.items():
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003707 for c in tc.cases:
3708 unq.append(c)
3709
3710 tests = sorted(set(unq))
3711 export_tests(options.export_tests, tests)
3712 return
3713
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003714 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003715
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003716 if options.test:
3717 run_individual_tests = options.test
3718
Anas Nashif49b22d42019-06-14 13:45:34 -04003719 if options.list_tests or options.sub_test:
3720 cnt = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04003721 unq = get_unique_tests(suite)
Anas Nashif49b22d42019-06-14 13:45:34 -04003722
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003723 if options.sub_test:
3724 if run_individual_tests:
3725 info("Running the following tests:")
3726 for t in run_individual_tests:
3727 print(" - {}".format(t))
3728 else:
3729 info("Tests not found")
3730 return
3731
3732 elif options.list_tests:
3733 for u in sorted(set(unq)):
3734 cnt = cnt + 1
3735 print(" - {}".format(u))
3736 print("{} total.".format(cnt))
3737 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003738
Anas Nashifbd166f42017-09-02 12:32:08 -04003739 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04003740
3741 if options.only_failed:
3742 suite.get_last_failed()
3743 elif options.load_tests:
3744 suite.load_from_file(options.load_tests)
3745 elif options.test_only:
3746 last_run = os.path.join(options.outdir, "sanitycheck.csv")
3747 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04003748 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003749 discards = suite.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003750
Anas Nashif30551f42018-01-12 21:56:59 -05003751 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003752 # if we are using command line platform filter, no need to list every
3753 # other platform as excluded, we know that already.
3754 # Show only the discards that apply to the selected platforms on the
3755 # command line
3756
Andrew Boie08ce5a52016-02-22 13:28:10 -08003757 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003758 if options.platform and i.platform.name not in options.platform:
3759 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003760 debug(
3761 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3762 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003763 i.testcase.name,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003764 COLOR_YELLOW,
3765 COLOR_NORMAL,
3766 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003767
Anas Nashif49b22d42019-06-14 13:45:34 -04003768 if options.report_excluded:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003769 all_tests = set(get_unique_tests(suite))
Anas Nashif49b22d42019-06-14 13:45:34 -04003770 to_be_run = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003771 for i,p in suite.instances.items():
3772 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04003773
Anas Nashif83fc06a2019-06-22 11:04:10 -04003774 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04003775 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003776 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04003777 print("- {}".format(not_run))
3778
3779 return
3780
Anas Nashife10b6512017-12-30 13:01:45 -05003781 if options.subset:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003782 #suite.instances = OrderedDict(sorted(suite.instances.items(),
3783 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05003784 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003785 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003786 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003787 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003788 if subset == sets:
3789 end = total
3790 else:
3791 end = start + per_set
3792
Anas Nashif83fc06a2019-06-22 11:04:10 -04003793 sliced_instances = islice(suite.instances.items(), start, end)
3794 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003795
Andrew Boie6acbe632015-07-17 12:03:52 -07003796
Anas Nashif83fc06a2019-06-22 11:04:10 -04003797 if options.save_tests:
3798 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07003799 return
3800
Anas Nashif83fc06a2019-06-22 11:04:10 -04003801 info("%d test configurations selected, %d configurations discarded due to filters." %
3802 (len(suite.instances), len(discards)))
3803
3804 if options.dry_run:
3805 duration = time.time() - start_time
3806 info("Completed in %d seconds" % (duration))
3807 return
3808
3809 retries = options.retry_failed + 1
3810 completed = 0
3811
3812 suite.update()
3813 suite.start_time = start_time
3814
3815 while True:
3816 completed += 1
3817
3818 if completed > 1:
3819 info("%d Iteration:" %(completed ))
3820 time.sleep(60) # waiting for the system to settle down
3821 suite.total_done = suite.total_tests - suite.total_failed
3822 suite.total_failed = 0
3823
3824 suite.execute()
Anas Nashif654ec5982019-04-11 08:38:21 -04003825 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003826
Anas Nashif83fc06a2019-06-22 11:04:10 -04003827 retries = retries - 1
3828 if retries == 0 or suite.total_failed == 0:
3829 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003830
Anas Nashif83fc06a2019-06-22 11:04:10 -04003831 suite.misc_reports(options.compare_report, options.show_footprint,
3832 options.all_deltas, options.footprint_threshold, options.last_metrics)
3833 suite.save_reports()
Andrew Boie6acbe632015-07-17 12:03:52 -07003834
Anas Nashife10b6512017-12-30 13:01:45 -05003835 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003836 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003837 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07003838
3839 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003840 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003841 if ts_plat and (ts_plat.type in {"native", "unit"}):
3842 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07003843
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02003844 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07003845 options.gcov_tool = "gcov"
3846 else:
3847 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
3848 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
3849
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003850 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003851 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003852
Anas Nashif83fc06a2019-06-22 11:04:10 -04003853 suite.duration = time.time() - start_time
3854 suite.summary(options.disable_unrecognized_section_test)
Andrew Boie6acbe632015-07-17 12:03:52 -07003855
Anas Nashif83fc06a2019-06-22 11:04:10 -04003856 if options.device_testing:
3857 print("\nHardware distribution summary:\n")
3858 for p in suite.connected_hardware:
3859 if p['connected']:
3860 print("%s (%s): %d" %(p['platform'], p.get('id', None), p['counter']))
3861
3862 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003863 sys.exit(1)
3864
Andrew Boie6acbe632015-07-17 12:03:52 -07003865if __name__ == "__main__":
3866 main()