blob: a1288d1195ebe30fab77123c8b606f9b8dd42d17 [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,
Anas Nashif12d8cce2019-11-20 03:47:27 -0800152running with -v or examining the discard report (sanitycheck_discard.csv)
153can help show why particular test cases were 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
Anas Nashifaae71d72018-04-21 22:26:48 -0500168import contextlib
Anas Nashifa4c368e2018-10-15 09:45:59 -0400169import string
Anas Nashifaae71d72018-04-21 22:26:48 -0500170import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700171import argparse
Andrew Boie6acbe632015-07-17 12:03:52 -0700172import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700173import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700174import subprocess
175import multiprocessing
176import select
177import shutil
Marc Herbertaf1090c2019-04-30 14:11:29 -0700178import shlex
Andrew Boie6acbe632015-07-17 12:03:52 -0700179import signal
180import threading
Anas Nashif83fc06a2019-06-22 11:04:10 -0400181import concurrent.futures
182from threading import BoundedSemaphore
183import queue
Andrew Boie6acbe632015-07-17 12:03:52 -0700184import time
Anas Nashif654ec5982019-04-11 08:38:21 -0400185import datetime
Andrew Boie6acbe632015-07-17 12:03:52 -0700186import csv
Anas Nashif83fc06a2019-06-22 11:04:10 -0400187import yaml
Andrew Boie5d4eb782015-10-02 10:04:56 -0700188import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600189import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700190import concurrent
Anas Nashifb3311ed2017-04-13 14:44:48 -0400191import xml.etree.ElementTree as ET
Anas Nashif7a361b82019-12-06 11:37:40 -0500192import logging
Anas Nashif035799f2017-05-13 21:31:53 -0400193from collections import OrderedDict
194from itertools import islice
Anas Nashife24350c2018-07-11 15:09:22 -0500195from pathlib import Path
196from distutils.spawn import find_executable
Anas Nashif434995c2019-12-01 13:55:11 -0500197try:
198 from anytree import Node, RenderTree, find
199except ImportError:
200 print("Install the anytree module to use the --test-tree option")
201
Anas Nashif5f908822019-11-25 08:19:25 -0500202try:
203 from tabulate import tabulate
204except ImportError:
205 print("Install tabulate python module with pip to use --device-testing option.")
Andrew Boie6acbe632015-07-17 12:03:52 -0700206
Kumar Gala7733b942019-09-12 17:08:43 -0500207ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
208if not ZEPHYR_BASE:
209 sys.exit("$ZEPHYR_BASE environment variable undefined")
210
211sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "dts"))
212import edtlib
Anas Nashif83fc06a2019-06-22 11:04:10 -0400213
Anas Nashif83fc06a2019-06-22 11:04:10 -0400214hw_map_local = threading.Lock()
Anas Nashifc1ea4522019-10-11 07:32:45 -0700215report_lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400216
Anas Nashif3ba1d432017-12-05 15:28:44 -0500217
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700218
Marc Herbert1c8632c2019-04-15 17:58:45 -0700219# Use this for internal comparisons; that's what canonicalization is
220# for. Don't use it when invoking other components of the build system
221# to avoid confusing and hard to trace inconsistencies in error messages
222# and logs, generated Makefiles, etc. compared to when users invoke these
223# components directly.
224# Note "normalization" is different from canonicalization, see os.path.
225canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
226
Andrew Boie3ea78922016-03-24 14:46:00 -0700227sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
228
Anas Nashif83fc06a2019-06-22 11:04:10 -0400229from sanity_chk import scl
230from sanity_chk import expr_parser
231
Andrew Boie3ea78922016-03-24 14:46:00 -0700232
Andrew Boie6acbe632015-07-17 12:03:52 -0700233VERBOSE = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400234
Andrew Boie6acbe632015-07-17 12:03:52 -0700235RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
236 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700237
238if os.isatty(sys.stdout.fileno()):
239 TERMINAL = True
240 COLOR_NORMAL = '\033[0m'
241 COLOR_RED = '\033[91m'
242 COLOR_GREEN = '\033[92m'
243 COLOR_YELLOW = '\033[93m'
244else:
245 TERMINAL = False
246 COLOR_NORMAL = ""
247 COLOR_RED = ""
248 COLOR_GREEN = ""
249 COLOR_YELLOW = ""
250
Anas Nashif11ee5252019-12-04 12:59:10 -0500251
Anas Nashif7a361b82019-12-06 11:37:40 -0500252logger = logging.getLogger('sanitycheck')
253#coloredlogs.install(level='INFO', logger=logger, fmt="%(levelname)s %(message)s")
254logger.setLevel(logging.DEBUG)
255
256
257#log_format = "%(levelname)s %(message)s"
258#logging.basicConfig(format=log_format, level=logging.INFO)
259
Anas Nashif45a97862019-01-09 08:46:42 -0500260class CMakeCacheEntry:
261 '''Represents a CMake cache entry.
262
263 This class understands the type system in a CMakeCache.txt, and
264 converts the following cache types to Python types:
265
266 Cache Type Python type
267 ---------- -------------------------------------------
268 FILEPATH str
269 PATH str
270 STRING str OR list of str (if ';' is in the value)
271 BOOL bool
272 INTERNAL str OR list of str (if ';' is in the value)
273 ---------- -------------------------------------------
274 '''
275
276 # Regular expression for a cache entry.
277 #
278 # CMake variable names can include escape characters, allowing a
279 # wider set of names than is easy to match with a regular
280 # expression. To be permissive here, use a non-greedy match up to
281 # the first colon (':'). This breaks if the variable name has a
282 # colon inside, but it's good enough.
283 CACHE_ENTRY = re.compile(
284 r'''(?P<name>.*?) # name
285 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
286 =(?P<value>.*) # value
287 ''', re.X)
288
289 @classmethod
290 def _to_bool(cls, val):
291 # Convert a CMake BOOL string into a Python bool.
292 #
293 # "True if the constant is 1, ON, YES, TRUE, Y, or a
294 # non-zero number. False if the constant is 0, OFF, NO,
295 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
296 # the suffix -NOTFOUND. Named boolean constants are
297 # case-insensitive. If the argument is not one of these
298 # constants, it is treated as a variable."
299 #
300 # https://cmake.org/cmake/help/v3.0/command/if.html
301 val = val.upper()
302 if val in ('ON', 'YES', 'TRUE', 'Y'):
303 return 1
304 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
305 return 0
306 elif val.endswith('-NOTFOUND'):
307 return 0
308 else:
309 try:
310 v = int(val)
311 return v != 0
312 except ValueError as exc:
313 raise ValueError('invalid bool {}'.format(val)) from exc
314
315 @classmethod
316 def from_line(cls, line, line_no):
317 # Comments can only occur at the beginning of a line.
318 # (The value of an entry could contain a comment character).
319 if line.startswith('//') or line.startswith('#'):
320 return None
321
322 # Whitespace-only lines do not contain cache entries.
323 if not line.strip():
324 return None
325
326 m = cls.CACHE_ENTRY.match(line)
327 if not m:
328 return None
329
330 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
331 if type_ == 'BOOL':
332 try:
333 value = cls._to_bool(value)
334 except ValueError as exc:
335 args = exc.args + ('on line {}: {}'.format(line_no, line),)
336 raise ValueError(args) from exc
Anas Nashif83fc06a2019-06-22 11:04:10 -0400337 elif type_ in ['STRING','INTERNAL']:
Anas Nashif45a97862019-01-09 08:46:42 -0500338 # If the value is a CMake list (i.e. is a string which
339 # contains a ';'), convert to a Python list.
340 if ';' in value:
341 value = value.split(';')
342
343 return CMakeCacheEntry(name, value)
344
345 def __init__(self, name, value):
346 self.name = name
347 self.value = value
348
349 def __str__(self):
350 fmt = 'CMakeCacheEntry(name={}, value={})'
351 return fmt.format(self.name, self.value)
352
353
354class CMakeCache:
355 '''Parses and represents a CMake cache file.'''
356
357 @staticmethod
358 def from_file(cache_file):
359 return CMakeCache(cache_file)
360
361 def __init__(self, cache_file):
362 self.cache_file = cache_file
363 self.load(cache_file)
364
365 def load(self, cache_file):
366 entries = []
367 with open(cache_file, 'r') as cache:
368 for line_no, line in enumerate(cache):
369 entry = CMakeCacheEntry.from_line(line, line_no)
370 if entry:
371 entries.append(entry)
372 self._entries = OrderedDict((e.name, e) for e in entries)
373
374 def get(self, name, default=None):
375 entry = self._entries.get(name)
376 if entry is not None:
377 return entry.value
378 else:
379 return default
380
381 def get_list(self, name, default=None):
382 if default is None:
383 default = []
384 entry = self._entries.get(name)
385 if entry is not None:
386 value = entry.value
387 if isinstance(value, list):
388 return value
389 elif isinstance(value, str):
390 return [value] if value else []
391 else:
392 msg = 'invalid value {} type {}'
393 raise RuntimeError(msg.format(value, type(value)))
394 else:
395 return default
396
397 def __contains__(self, name):
398 return name in self._entries
399
400 def __getitem__(self, name):
401 return self._entries[name].value
402
403 def __setitem__(self, name, entry):
404 if not isinstance(entry, CMakeCacheEntry):
405 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
406 raise TypeError(msg.format(type(entry), entry))
407 self._entries[name] = entry
408
409 def __delitem__(self, name):
410 del self._entries[name]
411
412 def __iter__(self):
413 return iter(self._entries.values())
414
Anas Nashif11ee5252019-12-04 12:59:10 -0500415
Andrew Boie6acbe632015-07-17 12:03:52 -0700416class SanityCheckException(Exception):
417 pass
418
Anas Nashif3ba1d432017-12-05 15:28:44 -0500419
Andrew Boie6acbe632015-07-17 12:03:52 -0700420class SanityRuntimeError(SanityCheckException):
421 pass
422
Anas Nashif11ee5252019-12-04 12:59:10 -0500423
Andrew Boie6acbe632015-07-17 12:03:52 -0700424class ConfigurationError(SanityCheckException):
425 def __init__(self, cfile, message):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400426 SanityCheckException.__init__(self, cfile + ": " + message)
Andrew Boie6acbe632015-07-17 12:03:52 -0700427
Anas Nashif11ee5252019-12-04 12:59:10 -0500428
Anas Nashif83fc06a2019-06-22 11:04:10 -0400429class BuildError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700430 pass
431
Anas Nashif3ba1d432017-12-05 15:28:44 -0500432
Anas Nashif83fc06a2019-06-22 11:04:10 -0400433class ExecutionError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700434 pass
435
Anas Nashif11ee5252019-12-04 12:59:10 -0500436
Anas Nashif576be982017-12-23 20:20:27 -0500437class HarnessImporter:
438
439 def __init__(self, name):
440 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
441 module = __import__("harness")
442 if name:
443 my_class = getattr(module, name)
444 else:
445 my_class = getattr(module, "Test")
446
447 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500448
Anas Nashif11ee5252019-12-04 12:59:10 -0500449
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300450class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400451 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300452 """Constructor
453
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300454 """
455 self.lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400456
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300457 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500458 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -0400459 self.duration = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400460 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300461
Anas Nashifdf7ee612018-07-07 06:09:01 -0500462 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100463 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500464 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500465
Anas Nashifd3384fb2018-02-22 06:44:16 -0600466 self.name = instance.name
467 self.instance = instance
Anas Nashif83fc06a2019-06-22 11:04:10 -0400468 self.timeout = instance.testcase.timeout
469 self.sourcedir = instance.testcase.source_dir
470 self.build_dir = instance.build_dir
471 self.log = os.path.join(self.build_dir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600472 self.returncode = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400473 self.set_state("running", self.duration)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600474
Anas Nashif83fc06a2019-06-22 11:04:10 -0400475 self.args = []
476
477 def set_state(self, state, duration):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300478 self.lock.acquire()
479 self.state = state
Anas Nashif83fc06a2019-06-22 11:04:10 -0400480 self.duration = duration
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300481 self.lock.release()
482
483 def get_state(self):
484 self.lock.acquire()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400485 ret = (self.state, self.duration)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300486 self.lock.release()
487 return ret
488
Anas Nashif83fc06a2019-06-22 11:04:10 -0400489 def record(self, harness):
490 if harness.recording:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -0500491 filename = os.path.join(self.build_dir, "recording.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -0400492 with open(filename, "at") as csvfile:
493 cw = csv.writer(csvfile, harness.fieldnames, lineterminator=os.linesep)
494 cw.writerow(harness.fieldnames)
495 for instance in harness.recording:
496 cw.writerow(instance)
497
Anas Nashif11ee5252019-12-04 12:59:10 -0500498
Anas Nashifdf7ee612018-07-07 06:09:01 -0500499class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400500 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500501 """Constructor
502
503 @param instance Test Instance
504 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400505 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500506
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100507 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500508
Anas Nashif6c0e1702019-12-05 15:24:52 -0500509 # Tool options
510 self.valgrind = False
511 self.lsan = False
512 self.asan = False
513 self.coverage = False
514
515
Jan Kowalewski265895b2019-01-07 16:40:24 +0100516 def try_kill_process_by_pid(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400517 if self.pid_fn:
Jan Kowalewski265895b2019-01-07 16:40:24 +0100518 pid = int(open(self.pid_fn).read())
519 os.unlink(self.pid_fn)
520 self.pid_fn = None # clear so we don't try to kill the binary twice
521 try:
522 os.kill(pid, signal.SIGTERM)
523 except ProcessLookupError:
524 pass
525
Kumar Gala34b1ef82019-12-12 04:38:42 -0600526 def terminate(self, proc):
527 # encapsulate terminate functionality so we do it consistently where ever
528 # we might want to terminate the proc. We need try_kill_process_by_pid
529 # because of both how newer ninja (1.6.0 or greater) and .NET / renode
530 # work. Newer ninja's don't seem to pass SIGTERM down to the children
531 # so we need to use try_kill_process_by_pid.
532 self.try_kill_process_by_pid()
533 proc.terminate()
534 self.terminated = True
535
Anas Nashifdf7ee612018-07-07 06:09:01 -0500536 def _output_reader(self, proc, harness):
537 log_out_fp = open(self.log, "wt")
538 for line in iter(proc.stdout.readline, b''):
Anas Nashif7a361b82019-12-06 11:37:40 -0500539 logger.debug("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
Anas Nashifdf7ee612018-07-07 06:09:01 -0500540 log_out_fp.write(line.decode('utf-8'))
541 log_out_fp.flush()
542 harness.handle(line.decode('utf-8').rstrip())
543 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100544 try:
545 #POSIX arch based ztests end on their own,
546 #so let's give it up to 100ms to do so
547 proc.wait(0.1)
548 except subprocess.TimeoutExpired:
Kumar Gala34b1ef82019-12-12 04:38:42 -0600549 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500550 break
551
552 log_out_fp.close()
553
554 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500555
Anas Nashif83fc06a2019-06-22 11:04:10 -0400556 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500557 harness_import = HarnessImporter(harness_name)
558 harness = harness_import.instance
559 harness.configure(self.instance)
560
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500561 if self.call_make_run:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400562 command = [get_generator()[0], "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500563 else:
564 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500565
Anas Nashifc1ea4522019-10-11 07:32:45 -0700566 run_valgrind = False
Anas Nashif6c0e1702019-12-05 15:24:52 -0500567 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500568 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100569 "--leak-check=full",
570 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
Anas Nashif83fc06a2019-06-22 11:04:10 -0400571 "--log-file="+self.build_dir+"/valgrind.log"
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100572 ] + command
Anas Nashifc1ea4522019-10-11 07:32:45 -0700573 run_valgrind = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500574
Anas Nashif7a361b82019-12-06 11:37:40 -0500575 logger.debug("Spawning process: " +
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100576 " ".join(shlex.quote(word) for word in command) + os.linesep +
Anas Nashif7a361b82019-12-06 11:37:40 -0500577 "in directory: " + self.build_dir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200578
Anas Nashif83fc06a2019-06-22 11:04:10 -0400579 start_time = time.time()
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200580
Anas Nashif89c83042019-11-05 05:55:39 -0800581 env = os.environ.copy()
Anas Nashif6c0e1702019-12-05 15:24:52 -0500582 if self.asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200583 env["ASAN_OPTIONS"] = "log_path=stdout:" + \
584 env.get("ASAN_OPTIONS", "")
Anas Nashif6c0e1702019-12-05 15:24:52 -0500585 if not self.lsan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200586 env["ASAN_OPTIONS"] += "detect_leaks=0"
587 with subprocess.Popen(command, stdout=subprocess.PIPE,
588 stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -0500589 logger.debug("Spawning BinaryHandler Thread for %s" % self.name)
Flavio Ceolin063ab902019-10-15 16:10:49 -0700590 t = threading.Thread(target=self._output_reader, args=(proc, harness, ), daemon=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500591 t.start()
592 t.join(self.timeout)
593 if t.is_alive():
Kumar Gala34b1ef82019-12-12 04:38:42 -0600594 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500595 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500596 proc.wait()
597 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500598
Anas Nashif83fc06a2019-06-22 11:04:10 -0400599 handler_time = time.time() - start_time
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200600
Anas Nashif6c0e1702019-12-05 15:24:52 -0500601 if self.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400602 subprocess.call(["GCOV_PREFIX=" + self.build_dir,
603 "gcov", self.sourcedir, "-b", "-s", self.build_dir], shell=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500604
Jan Kowalewski265895b2019-01-07 16:40:24 +0100605 self.try_kill_process_by_pid()
606
Anas Nashif83fc06a2019-06-22 11:04:10 -0400607 # FIXME: This is needed when killing the simulator, the console is
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500608 # garbled and needs to be reset. Did not find a better way to do that.
609
610 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500611 self.instance.results = harness.tests
Anas Nashifc1ea4522019-10-11 07:32:45 -0700612
Anas Nashif83fc06a2019-06-22 11:04:10 -0400613 if not self.terminated and self.returncode != 0:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100614 #When a process is killed, the default handler returns 128 + SIGTERM
615 #so in that case the return code itself is not meaningful
Anas Nashiff72d1902019-10-18 17:20:44 -0400616 self.set_state("failed", handler_time)
Anas Nashifc1ea4522019-10-11 07:32:45 -0700617 self.instance.reason = "Handler Error"
618 elif run_valgrind and self.returncode == 2:
619 self.set_state("failed", handler_time)
620 self.instance.reason = "Valgrind error"
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100621 elif harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400622 self.set_state(harness.state, handler_time)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500623 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400624 self.set_state("timeout", handler_time)
625 self.instance.reason = "Handler timeout"
626
Anas Nashif83fc06a2019-06-22 11:04:10 -0400627 self.record(harness)
Anas Nashif73440ea2018-02-19 10:57:03 -0600628
Anas Nashif11ee5252019-12-04 12:59:10 -0500629
Anas Nashif73440ea2018-02-19 10:57:03 -0600630class DeviceHandler(Handler):
631
Anas Nashifd18ec532019-04-11 23:20:39 -0400632 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600633 """Constructor
634
635 @param instance Test Instance
636 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400637 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600638
Anas Nashif83fc06a2019-06-22 11:04:10 -0400639 self.suite = None
640
Marti Bolivar5591ca22019-02-07 15:53:39 -0700641 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500642 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600643
Marti Bolivar5591ca22019-02-07 15:53:39 -0700644 ser_fileno = ser.fileno()
645 readlist = [halt_fileno, ser_fileno]
646
Anas Nashif73440ea2018-02-19 10:57:03 -0600647 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700648 readable, _, _ = select.select(readlist, [], [], self.timeout)
649
650 if halt_fileno in readable:
Anas Nashif7a361b82019-12-06 11:37:40 -0500651 logger.debug('halted')
Marti Bolivar5591ca22019-02-07 15:53:39 -0700652 ser.close()
653 break
654 if ser_fileno not in readable:
655 continue # Timeout.
656
657 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500658 try:
659 serial_line = ser.readline()
660 except TypeError:
661 pass
Anas Nashif83fc06a2019-06-22 11:04:10 -0400662 except serial.SerialException:
Anas Nashif59237602019-03-03 10:36:35 -0500663 ser.close()
664 break
Anas Nashif61e21632018-04-08 13:30:16 -0500665
Marti Bolivar5591ca22019-02-07 15:53:39 -0700666 # Just because ser_fileno has data doesn't mean an entire line
667 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600668 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600669 sl = serial_line.decode('utf-8', 'ignore')
Anas Nashif7a361b82019-12-06 11:37:40 -0500670 logger.debug("DEVICE: {0}".format(sl.rstrip()))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600671
672 log_out_fp.write(sl)
673 log_out_fp.flush()
674 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700675
Anas Nashif73440ea2018-02-19 10:57:03 -0600676 if harness.state:
677 ser.close()
678 break
679
680 log_out_fp.close()
681
Anas Nashif83fc06a2019-06-22 11:04:10 -0400682 def device_is_available(self, device):
683 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500684 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400685 return True
686
687 return False
688
689 def get_available_device(self, device):
690 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500691 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400692 i['available'] = False
693 i['counter'] += 1
694 return i
695
696 return None
697
698 def make_device_available(self, serial):
699 with hw_map_local:
700 for i in self.suite.connected_hardware:
701 if i['serial'] == serial:
702 i['available'] = True
703
Anas Nashif73440ea2018-02-19 10:57:03 -0600704 def handle(self):
705 out_state = "failed"
706
Anas Nashif83fc06a2019-06-22 11:04:10 -0400707 if options.west_flash:
708 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
709 if options.west_runner:
Michael Scott421ce462019-06-18 09:37:46 -0700710 command.append("--runner")
711 command.append(options.west_runner)
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200712 # There are three ways this option is used.
Andy Doan79c48842019-02-08 10:09:04 -0600713 # 1) bare: --west-flash
714 # This results in options.west_flash == []
715 # 2) with a value: --west-flash="--board-id=42"
716 # This results in options.west_flash == "--board-id=42"
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200717 # 3) Multiple values: --west-flash="--board-id=42,--erase"
718 # This results in options.west_flash == "--board-id=42 --erase"
Andy Doan79c48842019-02-08 10:09:04 -0600719 if options.west_flash != []:
720 command.append('--')
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200721 command.extend(options.west_flash.split(','))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600722 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400723 command = [get_generator()[0], "-C", self.build_dir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600724
Anas Nashifd3384fb2018-02-22 06:44:16 -0600725
Anas Nashif83fc06a2019-06-22 11:04:10 -0400726 while not self.device_is_available(self.instance.platform.name):
727 time.sleep(1)
728
729 hardware = self.get_available_device(self.instance.platform.name)
730
731 runner = hardware.get('runner', None)
732 if runner:
Peter Bigotda738482019-11-21 11:55:26 -0600733 board_id = hardware.get("probe_id", hardware.get("id", None))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400734 product = hardware.get("product", None)
735 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
736 command.append("--runner")
737 command.append(hardware.get('runner', None))
738 if runner == "pyocd":
739 command.append("--board-id")
740 command.append(board_id)
741 elif runner == "nrfjprog":
742 command.append('--')
743 command.append("--snr")
744 command.append(board_id)
745 elif runner == "openocd" and product == "STM32 STLink":
746 command.append('--')
747 command.append("--cmd-pre-init")
748 command.append("hla_serial %s" %(board_id))
749 elif runner == "openocd" and product == "EDBG CMSIS-DAP":
750 command.append('--')
751 command.append("--cmd-pre-init")
752 command.append("cmsis_dap_serial %s" %(board_id))
753 elif runner == "jlink":
754 command.append("--tool-opt=-SelectEmuBySN %s" %(board_id))
755
756 serial_device = hardware['serial']
757
758 try:
759 ser = serial.Serial(
760 serial_device,
761 baudrate=115200,
762 parity=serial.PARITY_NONE,
763 stopbits=serial.STOPBITS_ONE,
764 bytesize=serial.EIGHTBITS,
765 timeout=self.timeout
766 )
767 except serial.SerialException as e:
768 self.set_state("failed", 0)
Anas Nashif67f9ecb2019-12-09 10:46:19 -0500769 logger.error("Serial device error: %s" %(str(e)))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400770 self.make_device_available(serial_device)
771 return
Anas Nashif73440ea2018-02-19 10:57:03 -0600772
773 ser.flush()
774
Anas Nashif83fc06a2019-06-22 11:04:10 -0400775 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashif73440ea2018-02-19 10:57:03 -0600776 harness_import = HarnessImporter(harness_name)
777 harness = harness_import.instance
778 harness.configure(self.instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400779 read_pipe, write_pipe = os.pipe()
780 start_time = time.time()
Anas Nashif73440ea2018-02-19 10:57:03 -0600781
Marti Bolivar5591ca22019-02-07 15:53:39 -0700782 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400783 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600784 t.start()
785
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500786 d_log = "{}/device.log".format(self.instance.build_dir)
Anas Nashif7a361b82019-12-06 11:37:40 -0500787 logger.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500788 try:
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500789 stdout = stderr = None
790 with subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
791 try:
792 (stdout, stderr) = proc.communicate(timeout=30)
793 if VERBOSE:
794 print(stdout.decode())
795 if proc.returncode != 0:
796 self.instance.reason = "Device issue (Flash?)"
797 with open(d_log, "w") as dlog_fp:
798 dlog_fp.write(stderr.decode())
799 except subprocess.TimeoutExpired:
800 proc.kill()
801 (stdout, stderr) = proc.communicate()
802 self.instance.reason = "Device issue (Timeout)"
803
804 with open(d_log, "w") as dlog_fp:
805 dlog_fp.write(stderr.decode())
Anas Nashif83fc06a2019-06-22 11:04:10 -0400806
Anas Nashif61e21632018-04-08 13:30:16 -0500807 except subprocess.CalledProcessError:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400808 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600809
810 t.join(self.timeout)
811 if t.is_alive():
812 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600813
814 if ser.isOpen():
815 ser.close()
816
Anas Nashifd3384fb2018-02-22 06:44:16 -0600817 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400818 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600819 if c not in harness.tests:
820 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500821
Anas Nashif83fc06a2019-06-22 11:04:10 -0400822 handler_time = time.time() - start_time
823
Anas Nashif61e21632018-04-08 13:30:16 -0500824 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600825 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400826 self.set_state(harness.state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600827 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400828 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600829
Anas Nashif83fc06a2019-06-22 11:04:10 -0400830 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500831
Anas Nashif3a00d0c2019-11-12 11:07:15 -0500832 self.record(harness)
833
Anas Nashif11ee5252019-12-04 12:59:10 -0500834
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300835class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700836 """Spawns a thread to monitor QEMU output from pipes
837
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400838 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700839 We need to do this as once qemu starts, it runs forever until killed.
840 Test cases emit special messages to the console as they run, we check
841 for these to collect whether the test passed or failed.
842 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700843
Anas Nashif83fc06a2019-06-22 11:04:10 -0400844
845 def __init__(self, instance, type_str):
846 """Constructor
847
848 @param instance Test instance
849 """
850
851 super().__init__(instance, type_str)
852 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
853
854 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
855
856
Andrew Boie6acbe632015-07-17 12:03:52 -0700857 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500858 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700859 fifo_in = fifo_fn + ".in"
860 fifo_out = fifo_fn + ".out"
861
862 # These in/out nodes are named from QEMU's perspective, not ours
863 if os.path.exists(fifo_in):
864 os.unlink(fifo_in)
865 os.mkfifo(fifo_in)
866 if os.path.exists(fifo_out):
867 os.unlink(fifo_out)
868 os.mkfifo(fifo_out)
869
870 # We don't do anything with out_fp but we need to open it for
871 # writing so that QEMU doesn't block, due to the way pipes work
872 out_fp = open(fifo_in, "wb")
873 # Disable internal buffering, we don't
874 # want read() or poll() to ever block if there is data in there
875 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800876 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700877
878 start_time = time.time()
879 timeout_time = start_time + timeout
880 p = select.poll()
881 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400882 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700883
Andrew Boie6acbe632015-07-17 12:03:52 -0700884 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500885 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700886 while True:
887 this_timeout = int((timeout_time - time.time()) * 1000)
888 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400889 if not out_state:
890 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700891 break
892
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500893 try:
894 c = in_fp.read(1).decode("utf-8")
895 except UnicodeDecodeError:
896 # Test is writing something weird, fail
897 out_state = "unexpected byte"
898 break
899
Andrew Boie6acbe632015-07-17 12:03:52 -0700900 if c == "":
901 # EOF, this shouldn't happen unless QEMU crashes
902 out_state = "unexpected eof"
903 break
904 line = line + c
905 if c != "\n":
906 continue
907
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300908 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700909 log_out_fp.write(line)
910 log_out_fp.flush()
911 line = line.strip()
Anas Nashif7a361b82019-12-06 11:37:40 -0500912 logger.debug("QEMU: %s" % line)
Andrew Boie6acbe632015-07-17 12:03:52 -0700913
Anas Nashif576be982017-12-23 20:20:27 -0500914 harness.handle(line)
915 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400916 # if we have registered a fail make sure the state is not
917 # overridden by a false success message coming from the
918 # testsuite
919 if out_state != 'failed':
920 out_state = harness.state
921
922 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700923 # the timeout and wait for 2 more seconds to catch anything
924 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700925 # coverage is enabled since dumping this information can
926 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500927 if not timeout_extended or harness.capture_coverage:
928 timeout_extended= True
929 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700930 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500931 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500932 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700933 line = ""
934
Anas Nashif83fc06a2019-06-22 11:04:10 -0400935 handler.record(harness)
936
937 handler_time = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -0500938 logger.debug("QEMU complete (%s) after %f seconds" %
Anas Nashif83fc06a2019-06-22 11:04:10 -0400939 (out_state, handler_time))
940 handler.set_state(out_state, handler_time)
Andrew Boie6acbe632015-07-17 12:03:52 -0700941
942 log_out_fp.close()
943 out_fp.close()
944 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400945 if os.path.exists(pid_fn):
946 pid = int(open(pid_fn).read())
947 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700948
Anas Nashifd6476ee2019-04-11 11:40:09 -0400949 try:
950 if pid:
951 os.kill(pid, signal.SIGTERM)
952 except ProcessLookupError:
953 # Oh well, as long as it's dead! User probably sent Ctrl-C
954 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800955
Andrew Boie6acbe632015-07-17 12:03:52 -0700956 os.unlink(fifo_in)
957 os.unlink(fifo_out)
958
Anas Nashif83fc06a2019-06-22 11:04:10 -0400959 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700960 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500961 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700962
963 # We pass this to QEMU which looks for fifos with .in and .out
964 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400965 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700966
Anas Nashif83fc06a2019-06-22 11:04:10 -0400967 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700968 if os.path.exists(self.pid_fn):
969 os.unlink(self.pid_fn)
970
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500971 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500972
Anas Nashif83fc06a2019-06-22 11:04:10 -0400973 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500974 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600975 harness.configure(self.instance)
976 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400977 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300978 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500979 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600980
981 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700982 self.thread.daemon = True
Anas Nashif7a361b82019-12-06 11:37:40 -0500983 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700984 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400985 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700986
Anas Nashif7a361b82019-12-06 11:37:40 -0500987 logger.debug("Running %s (%s)" %(self.name, self.type_str))
Stephanos Ioannidise1827c02019-11-12 14:11:31 +0900988 command = [get_generator()[0]]
989 command += ["-C", self.build_dir, "run"]
990
991 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -0500992 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Stephanos Ioannidise1827c02019-11-12 14:11:31 +0900993 proc.wait()
994 self.returncode = proc.returncode
995
996 if self.returncode != 0:
997 self.set_state("failed", 0)
998 self.instance.reason = "Exited with {}".format(self.returncode)
999
Andrew Boie6acbe632015-07-17 12:03:52 -07001000 def get_fifo(self):
1001 return self.fifo_fn
1002
Anas Nashif11ee5252019-12-04 12:59:10 -05001003
Andrew Boie6acbe632015-07-17 12:03:52 -07001004class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001005
Anas Nashif83fc06a2019-06-22 11:04:10 -04001006 alloc_sections = [
1007 "bss",
1008 "noinit",
1009 "app_bss",
1010 "app_noinit",
1011 "ccm_bss",
1012 "ccm_noinit"
1013 ]
1014
1015 rw_sections = [
1016 "datas",
1017 "initlevel",
1018 "exceptions",
1019 "initshell",
1020 "_static_thread_area",
1021 "_k_timer_area",
1022 "_k_mem_slab_area",
1023 "_k_mem_pool_area",
1024 "sw_isr_table",
1025 "_k_sem_area",
1026 "_k_mutex_area",
1027 "app_shmem_regions",
1028 "_k_fifo_area",
1029 "_k_lifo_area",
1030 "_k_stack_area",
1031 "_k_msgq_area",
1032 "_k_mbox_area",
1033 "_k_pipe_area",
1034 "net_if",
1035 "net_if_dev",
1036 "net_stack",
1037 "net_l2_data",
1038 "_k_queue_area",
1039 "_net_buf_pool_area",
1040 "app_datas",
1041 "kobject_data",
1042 "mmu_tables",
1043 "app_pad",
1044 "priv_stacks",
1045 "ccm_data",
1046 "usb_descriptor",
1047 "usb_data", "usb_bos_desc",
1048 'log_backends_sections',
1049 'log_dynamic_sections',
1050 'log_const_sections',
1051 "app_smem",
1052 'shell_root_cmds_sections',
1053 'log_const_sections',
1054 "font_entry_sections",
1055 "priv_stacks_noinit",
1056 "_TEXT_SECTION_NAME_2",
1057 "_GCOV_BSS_SECTION_NAME",
1058 "gcov",
1059 "nocache"
1060 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001061
Andrew Boie73b4ee62015-10-07 11:33:22 -07001062 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001063 ro_sections = [
1064 "text",
1065 "ctors",
1066 "init_array",
1067 "reset",
1068 "object_access",
1069 "rodata",
1070 "devconfig",
1071 "net_l2",
1072 "vector",
1073 "sw_isr_table",
1074 "_settings_handlers_area",
1075 "_bt_channels_area",
1076 "_bt_br_channels_area",
1077 "_bt_services_area",
1078 "vectors",
1079 "net_socket_register",
1080 "net_ppp_proto"
1081 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001082
Andrew Boie52fef672016-11-29 12:21:59 -08001083 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001084 """Constructor
1085
Andrew Boiebbd670c2015-08-17 13:16:11 -07001086 @param filename Path to the output binary
1087 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001088 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001089 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001090 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001091 magic = f.read(4)
1092
Anas Nashifb4bdd662018-08-15 17:12:28 -05001093 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001094 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001095 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1096 except Exception as e:
1097 print(str(e))
1098 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001099
1100 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001101 # GREP can not be used as it returns an error if the symbol is not
1102 # found.
1103 is_xip_command = "nm " + filename + \
1104 " | awk '/CONFIG_XIP/ { print $3 }'"
1105 is_xip_output = subprocess.check_output(
1106 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1107 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001108 try:
1109 if is_xip_output.endswith("no symbols"):
1110 raise SanityRuntimeError("%s has no symbol information" % filename)
1111 except Exception as e:
1112 print(str(e))
1113 sys.exit(2)
1114
Andrew Boie6acbe632015-07-17 12:03:52 -07001115 self.is_xip = (len(is_xip_output) != 0)
1116
Andrew Boiebbd670c2015-08-17 13:16:11 -07001117 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001118 self.sections = []
1119 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001120 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001121 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001122
1123 self._calculate_sizes()
1124
1125 def get_ram_size(self):
1126 """Get the amount of RAM the application will use up on the device
1127
1128 @return amount of RAM, in bytes
1129 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001130 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001131
1132 def get_rom_size(self):
1133 """Get the size of the data that this application uses on device's flash
1134
1135 @return amount of ROM, in bytes
1136 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001137 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001138
1139 def unrecognized_sections(self):
1140 """Get a list of sections inside the binary that weren't recognized
1141
David B. Kinder29963c32017-06-16 12:32:42 -07001142 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001143 """
1144 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001145 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001146 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001147 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001148 return slist
1149
1150 def _calculate_sizes(self):
1151 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001152 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001153 objdump_output = subprocess.check_output(
1154 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001155
1156 for line in objdump_output:
1157 words = line.split()
1158
Anas Nashif83fc06a2019-06-22 11:04:10 -04001159 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001160 continue
1161
1162 index = words[0]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001163 if not index[0].isdigit(): # Skip lines that do not start
Andrew Boie6acbe632015-07-17 12:03:52 -07001164 continue # with a digit
1165
1166 name = words[1] # Skip lines with section names
Anas Nashif83fc06a2019-06-22 11:04:10 -04001167 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 continue
1169
Andrew Boie73b4ee62015-10-07 11:33:22 -07001170 # TODO this doesn't actually reflect the size in flash or RAM as
1171 # it doesn't include linker-imposed padding between sections.
1172 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001173 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001174 if size == 0:
1175 continue
1176
Andrew Boie73b4ee62015-10-07 11:33:22 -07001177 load_addr = int(words[4], 16)
1178 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001179
1180 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001181 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001182 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001183 if name in SizeCalculator.alloc_sections:
1184 self.ram_size += size
1185 stype = "alloc"
1186 elif name in SizeCalculator.rw_sections:
1187 self.ram_size += size
1188 self.rom_size += size
1189 stype = "rw"
1190 elif name in SizeCalculator.ro_sections:
1191 self.rom_size += size
1192 if not self.is_xip:
1193 self.ram_size += size
1194 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001195 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001196 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001197 if name not in self.extra_sections:
1198 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001199
Anas Nashif3ba1d432017-12-05 15:28:44 -05001200 self.sections.append({"name": name, "load_addr": load_addr,
1201 "size": size, "virt_addr": virt_addr,
1202 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001203
1204
Andrew Boie6acbe632015-07-17 12:03:52 -07001205# "list" - List of strings
1206# "list:<type>" - List of <type>
1207# "set" - Set of unordered, unique strings
1208# "set:<type>" - Set of <type>
1209# "float" - Floating point
1210# "int" - Integer
1211# "bool" - Boolean
1212# "str" - String
1213
1214# XXX Be sure to update __doc__ if you change any of this!!
1215
Anas Nashif83fc06a2019-06-22 11:04:10 -04001216platform_valid_keys = {
Anas Nashif924a4e72018-10-18 12:25:55 -04001217 "supported_toolchains": {"type": "list", "default": []},
1218 "env": {"type": "list", "default": []}
1219 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001220
Anas Nashif3ba1d432017-12-05 15:28:44 -05001221testcase_valid_keys = {"tags": {"type": "set", "required": False},
1222 "type": {"type": "str", "default": "integration"},
1223 "extra_args": {"type": "list"},
1224 "extra_configs": {"type": "list"},
1225 "build_only": {"type": "bool", "default": False},
1226 "build_on_all": {"type": "bool", "default": False},
1227 "skip": {"type": "bool", "default": False},
1228 "slow": {"type": "bool", "default": False},
1229 "timeout": {"type": "int", "default": 60},
1230 "min_ram": {"type": "int", "default": 8},
1231 "depends_on": {"type": "set"},
1232 "min_flash": {"type": "int", "default": 32},
1233 "arch_whitelist": {"type": "set"},
1234 "arch_exclude": {"type": "set"},
1235 "extra_sections": {"type": "list", "default": []},
1236 "platform_exclude": {"type": "set"},
1237 "platform_whitelist": {"type": "set"},
1238 "toolchain_exclude": {"type": "set"},
1239 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001240 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001241 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301242 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001243 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001244
Anas Nashif11ee5252019-12-04 12:59:10 -05001245
Andrew Boie6acbe632015-07-17 12:03:52 -07001246class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001247 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001248 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001249
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001250 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001251 """Instantiate a new SanityConfigParser object
1252
Anas Nashifa792a3d2017-04-04 18:47:49 -04001253 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001254 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001255 self.data = {}
1256 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001257 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001258 self.tests = {}
1259 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001260
1261 def load(self):
1262 self.data = scl.yaml_load_verify(self.filename, self.schema)
1263
Anas Nashif255625b2017-12-05 15:08:26 -05001264 if 'tests' in self.data:
1265 self.tests = self.data['tests']
1266 if 'common' in self.data:
1267 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001268
Anas Nashif83fc06a2019-06-22 11:04:10 -04001269
Andrew Boie6acbe632015-07-17 12:03:52 -07001270 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001271 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001272 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 if typestr == "str":
1274 return v
1275
1276 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001277 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001278
1279 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001280 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001281
1282 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001283 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001284
Anas Nashif3ba1d432017-12-05 15:28:44 -05001285 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001286 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001287 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001288 vs = v.split()
1289 if len(typestr) > 4 and typestr[4] == ":":
1290 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1291 else:
1292 return vs
1293
1294 elif typestr.startswith("set"):
1295 vs = v.split()
1296 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001297 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001298 else:
1299 return set(vs)
1300
Anas Nashif576be982017-12-23 20:20:27 -05001301 elif typestr.startswith("map"):
1302 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001303 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001304 raise ConfigurationError(
1305 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001306
Anas Nashifb4754ed2017-12-05 17:27:58 -05001307 def get_test(self, name, valid_keys):
1308 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001309
Anas Nashifb4754ed2017-12-05 17:27:58 -05001310 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001311 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001312 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001313 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001314 here, it will generate an error. Each value in this dictionary
1315 is another dictionary containing metadata:
1316
1317 "default" - Default value if not given
1318 "type" - Data type to convert the text value to. Simple types
1319 supported are "str", "float", "int", "bool" which will get
1320 converted to respective Python data types. "set" and "list"
1321 may also be specified which will split the value by
1322 whitespace (but keep the elements as strings). finally,
1323 "list:<type>" and "set:<type>" may be given which will
1324 perform a type conversion after splitting the value up.
1325 "required" - If true, raise an error if not defined. If false
1326 and "default" isn't specified, a type conversion will be
1327 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001328 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001329 type conversion and default values filled in per valid_keys
1330 """
1331
1332 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001333 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001334 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001335
Anas Nashifb4754ed2017-12-05 17:27:58 -05001336 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001337 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001338 raise ConfigurationError(
1339 self.filename,
1340 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001341 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001342
Anas Nashiffa695d22017-10-04 16:14:27 -04001343 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001344 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001345 # By default, we just concatenate string values of keys
1346 # which appear both in "common" and per-test sections,
1347 # but some keys are handled in adhoc way based on their
1348 # semantics.
1349 if k == "filter":
1350 d[k] = "(%s) and (%s)" % (d[k], v)
1351 else:
1352 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001353 else:
1354 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001355
Andrew Boie08ce5a52016-02-22 13:28:10 -08001356 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001357 if k not in d:
1358 if "required" in kinfo:
1359 required = kinfo["required"]
1360 else:
1361 required = False
1362
1363 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001364 raise ConfigurationError(
1365 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001366 "missing required value for '%s' in test '%s'" %
1367 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001368 else:
1369 if "default" in kinfo:
1370 default = kinfo["default"]
1371 else:
1372 default = self._cast_value("", kinfo["type"])
1373 d[k] = default
1374 else:
1375 try:
1376 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001377 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001378 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001379 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1380 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001381
1382 return d
1383
1384
1385class Platform:
1386 """Class representing metadata for a particular platform
1387
Anas Nashifc7406082015-12-13 15:00:31 -05001388 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001389
Anas Nashif83fc06a2019-06-22 11:04:10 -04001390 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
1391 "scripts","sanity_chk","platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001392
Anas Nashif83fc06a2019-06-22 11:04:10 -04001393 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001394 """Constructor.
1395
Andrew Boie6acbe632015-07-17 12:03:52 -07001396 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001397
1398 self.name = ""
1399 self.sanitycheck = True
1400 # if no RAM size is specified by the board, take a default of 128K
1401 self.ram = 128
1402
1403 self.ignore_tags = []
1404 self.default = False
1405 # if no flash size is specified by the board, take a default of 512K
1406 self.flash = 512
1407 self.supported = set()
1408
1409 self.arch = ""
1410 self.type = "na"
1411 self.simulation = "na"
1412 self.supported_toolchains = []
1413 self.env = []
1414 self.env_satisfied = True
1415 self.filter_data = dict()
1416
1417 def load(self, platform_file):
1418 scp = SanityConfigParser(platform_file, self.platform_schema)
1419 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001420 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001421
Anas Nashif255625b2017-12-05 15:08:26 -05001422 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001423 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001424 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001425 self.ram = data.get("ram", 128)
1426 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001427 self.ignore_tags = testing.get("ignore_tags", [])
1428 self.default = testing.get("default", False)
1429 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001430 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001431 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001432 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001433 for item in supp_feature.split(":"):
1434 self.supported.add(item)
1435
Anas Nashif255625b2017-12-05 15:08:26 -05001436 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001437 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001438 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001439 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001440 self.env = data.get("env", [])
1441 self.env_satisfied = True
1442 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001443 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001444 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001445
Andrew Boie6acbe632015-07-17 12:03:52 -07001446 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001447 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001448
Anas Nashif11ee5252019-12-04 12:59:10 -05001449
Anas Nashif83fc06a2019-06-22 11:04:10 -04001450class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001451 """Class representing a test application
1452 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001453
Anas Nashif83fc06a2019-06-22 11:04:10 -04001454 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001455 """TestCase constructor.
1456
Anas Nashif877d3ca2017-12-05 17:39:29 -05001457 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001458 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001459 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001460
Andrew Boie6acbe632015-07-17 12:03:52 -07001461 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001462 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001463 the test case is <workdir>/<name>.
1464
Marc Herbert1c8632c2019-04-15 17:58:45 -07001465 @param testcase_root os.path.abspath() of one of the --testcase-root
1466 @param workdir Sub-directory of testcase_root where the
1467 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001468 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001469 in the test case configuration file. For many test cases that just
1470 define one test, can be anything and is usually "test". This is
1471 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001472 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001473 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001474 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001475 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001476
Anas Nashif83fc06a2019-06-22 11:04:10 -04001477 self.id = ""
1478 self.source_dir = ""
1479 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001480 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001481 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001482
Anas Nashif83fc06a2019-06-22 11:04:10 -04001483 self.type = None
1484 self.tags = None
1485 self.extra_args = None
1486 self.extra_configs = None
1487 self.arch_whitelist = None
1488 self.arch_exclude = None
1489 self.skip = None
1490 self.platform_exclude = None
1491 self.platform_whitelist = None
1492 self.toolchain_exclude = None
1493 self.toolchain_whitelist = None
1494 self.tc_filter = None
1495 self.timeout = 60
1496 self.harness = ""
1497 self.harness_config = {}
1498 self.build_only = True
1499 self.build_on_all = False
1500 self.slow = False
1501 self.min_ram = None
1502 self.depends_on = None
1503 self.min_flash = None
1504 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001505
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001506
Anas Nashif83fc06a2019-06-22 11:04:10 -04001507 @staticmethod
1508 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001509
Marc Herbert1c8632c2019-04-15 17:58:45 -07001510 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001511 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001512 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001513 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001514 relative_tc_root = os.path.relpath(canonical_testcase_root,
Anas Nashif11ee5252019-12-04 12:59:10 -05001515 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001516 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001517 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001518
Marc Herbert1c8632c2019-04-15 17:58:45 -07001519 # workdir can be "."
1520 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001521 return unique
1522
Anas Nashif83fc06a2019-06-22 11:04:10 -04001523 @staticmethod
1524 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001525 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001526 # do not match until end-of-line, otherwise we won't allow
1527 # stc_regex below to catch the ones that are declared in the same
1528 # line--as we only search starting the end of this match
1529 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001530 re.MULTILINE)
1531 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001532 br"^\s*" # empy space at the beginning is ok
1533 # catch the case where it is declared in the same sentence, e.g:
1534 #
1535 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1536 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1537 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
Anas Nashif9091a012019-11-24 09:22:22 -05001538 br"ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?"
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001539 # Consume the argument that becomes the extra testcse
1540 br"\(\s*"
1541 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1542 # _setup_teardown() variant has two extra arguments that we ignore
1543 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1544 br"\s*\)",
1545 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001546 re.MULTILINE)
1547 suite_run_regex = re.compile(
1548 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1549 re.MULTILINE)
1550 achtung_regex = re.compile(
1551 br"(#ifdef|#endif)",
1552 re.MULTILINE)
1553 warnings = None
1554
1555 with open(inf_name) as inf:
Anas Nashif19d67e42019-11-21 11:33:12 -05001556 if os.name == 'nt':
1557 mmap_args = {'fileno':inf.fileno(), 'length':0, 'access':mmap.ACCESS_READ}
1558 else:
1559 mmap_args = {'fileno':inf.fileno(), 'length':0, 'flags':mmap.MAP_PRIVATE, 'prot':mmap.PROT_READ, 'offset':0}
1560
1561 with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001562 # contextlib makes pylint think main_c isn't subscriptable
1563 # pylint: disable=unsubscriptable-object
1564
Anas Nashifaae71d72018-04-21 22:26:48 -05001565 suite_regex_match = suite_regex.search(main_c)
1566 if not suite_regex_match:
1567 # can't find ztest_test_suite, maybe a client, because
1568 # it includes ztest.h
1569 return None, None
1570
1571 suite_run_match = suite_run_regex.search(main_c)
1572 if not suite_run_match:
1573 raise ValueError("can't find ztest_run_test_suite")
1574
1575 achtung_matches = re.findall(
1576 achtung_regex,
1577 main_c[suite_regex_match.end():suite_run_match.start()])
1578 if achtung_matches:
1579 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001580 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001581 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001582 stc_regex,
1583 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001584 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001585 return matches, warnings
1586
1587 def scan_path(self, path):
1588 subcases = []
1589 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1590 try:
1591 _subcases, warnings = self.scan_file(filename)
1592 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001593 logger.error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001594 if _subcases:
1595 subcases += _subcases
1596 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001597 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashif434995c2019-12-01 13:55:11 -05001598 for filename in glob.glob(os.path.join(path, "*.c")):
1599 try:
1600 _subcases, warnings = self.scan_file(filename)
1601 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001602 logger.error("%s: %s" % (filename, warnings))
Anas Nashif434995c2019-12-01 13:55:11 -05001603 if _subcases:
1604 subcases += _subcases
1605 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001606 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001607 return subcases
1608
Anas Nashif83fc06a2019-06-22 11:04:10 -04001609 def parse_subcases(self, test_path):
Anas Nashif9091a012019-11-24 09:22:22 -05001610 results = self.scan_path(test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001611 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001612 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001613 self.cases.append(name)
1614
Anas Nashiff16e92c2019-03-31 16:58:12 -04001615 if not results:
1616 self.cases.append(self.id)
1617
Anas Nashif75547e22018-02-24 08:32:14 -06001618 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001619 return self.name
1620
1621
Andrew Boie6acbe632015-07-17 12:03:52 -07001622class TestInstance:
1623 """Class representing the execution of a particular TestCase on a platform
1624
1625 @param test The TestCase object we want to build/execute
1626 @param platform Platform object that we want to build and run against
1627 @param base_outdir Base directory for all test results. The actual
1628 out directory used is <outdir>/<platform>/<test case name>
1629 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001630
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001631 def __init__(self, testcase, platform, outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001632
Anas Nashif83fc06a2019-06-22 11:04:10 -04001633 self.testcase = testcase
1634 self.platform = platform
1635
1636 self.status = None
1637 self.reason = "N/A"
1638 self.metrics = dict()
1639 self.handler = None
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001640 self.outdir = outdir
Anas Nashif83fc06a2019-06-22 11:04:10 -04001641
1642 self.name = os.path.join(platform.name, testcase.name)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001643 self.build_dir = os.path.join(outdir, platform.name, testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001644
1645 self.build_only = self.check_build_or_run()
1646 self.run = not self.build_only
1647
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001648 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001649
Marc Herbert0f7255c2019-04-05 14:14:21 -07001650 def __lt__(self, other):
1651 return self.name < other.name
1652
Anas Nashif83fc06a2019-06-22 11:04:10 -04001653 def check_build_or_run(self):
Anas Nashif19d67e42019-11-21 11:33:12 -05001654 # right now we only support building on windows. running is still work
1655 # in progress.
1656
1657 if os.name == 'nt':
1658 return True
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001659
Anas Nashif83fc06a2019-06-22 11:04:10 -04001660 build_only = True
1661
1662 # we asked for build-only on the command line
1663 if options.build_only:
1664 return True
1665
1666 # The testcase is designed to be build only.
1667 if self.testcase.build_only:
1668 return True
1669
1670 # Do not run slow tests:
1671 skip_slow = self.testcase.slow and not options.enable_slow
1672 if skip_slow:
1673 return True
1674
1675 runnable =bool(self.testcase.type == "unit" or \
1676 self.platform.type == "native" or \
1677 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1678 options.device_testing)
1679
1680 if self.platform.simulation == "nsim":
1681 if not find_executable("nsimdrv"):
1682 runnable = False
1683
1684 if self.platform.simulation == "renode":
1685 if not find_executable("renode"):
1686 runnable = False
1687
1688 # console harness allows us to run the test and capture data.
1689 if self.testcase.harness == 'console':
1690
1691 # if we have a fixture that is also being supplied on the
1692 # command-line, then we need to run the test, not just build it.
1693 if "fixture" in self.testcase.harness_config:
1694 fixture = self.testcase.harness_config['fixture']
1695 if fixture in options.fixture:
1696 build_only = False
1697 else:
1698 build_only = True
1699 else:
1700 build_only = False
1701 elif self.testcase.harness:
1702 build_only = True
1703 else:
1704 build_only = False
1705
1706 return not (not build_only and runnable)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001707
Anas Nashifdbd76492018-11-23 20:24:19 -05001708 def create_overlay(self, platform):
Marc Herbertc7633de2019-07-06 15:52:31 -07001709 # Create this in a "sanitycheck/" subdirectory otherwise this
1710 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1711 # will silently give that second time precedence over any
1712 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001713 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001714 os.makedirs(subdir, exist_ok=True)
1715 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001716 with open(file, "w") as f:
1717 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001718
Anas Nashif83fc06a2019-06-22 11:04:10 -04001719 if self.testcase.extra_configs:
1720 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001721
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001722 if options.enable_coverage:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001723 if platform.name in options.coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001724 content = content + "\nCONFIG_COVERAGE=y"
1725
Jan Van Winkel21212f32019-09-12 00:03:35 +02001726 if options.enable_asan:
1727 if platform.type == "native":
1728 content = content + "\nCONFIG_ASAN=y"
1729
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001730 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001731
Andrew Boie6acbe632015-07-17 12:03:52 -07001732 def calculate_sizes(self):
1733 """Get the RAM/ROM sizes of a test case.
1734
1735 This can only be run after the instance has been executed by
1736 MakeGenerator, otherwise there won't be any binaries to measure.
1737
1738 @return A SizeCalculator object
1739 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001740 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1741 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001742 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001743 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001744 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001745
1746 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001747
1748 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001749 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001750
1751
Anas Nashif83fc06a2019-06-22 11:04:10 -04001752class CMake():
Andrew Boie4ef16c52015-08-28 12:36:03 -07001753
Anas Nashif83fc06a2019-06-22 11:04:10 -04001754 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1755 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1756
1757 def __init__(self, testcase, platform, source_dir, build_dir):
1758
1759 self.cwd = None
1760 self.capture_output = True
1761
1762 self.defconfig = {}
1763 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001764
1765 self.instance = None
1766 self.testcase = testcase
1767 self.platform = platform
1768 self.source_dir = source_dir
1769 self.build_dir = build_dir
1770 self.log = "build.log"
1771
1772 def parse_generated(self):
1773 self.defconfig = {}
1774 return {}
1775
1776 def run_build(self, args=[]):
1777
Anas Nashif7a361b82019-12-06 11:37:40 -05001778 logger.debug("Building %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001779
1780 cmake_args = []
1781 cmake_args.extend(args)
1782 cmake = shutil.which('cmake')
1783 cmd = [cmake] + cmake_args
1784 kwargs = dict()
1785
1786 if self.capture_output:
1787 kwargs['stdout'] = subprocess.PIPE
1788 # CMake sends the output of message() to stderr unless it's STATUS
1789 kwargs['stderr'] = subprocess.STDOUT
1790
1791 if self.cwd:
1792 kwargs['cwd'] = self.cwd
1793
1794 p = subprocess.Popen(cmd, **kwargs)
1795 out, _ = p.communicate()
1796
1797 results = {}
1798 if p.returncode == 0:
1799 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1800
1801 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001802 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1803
1804 if out:
1805 log_msg = out.decode(sys.getdefaultencoding())
1806 with open(os.path.join(self.build_dir, self.log), "a") as log:
1807 log.write(log_msg)
1808
1809 else:
1810 return None
1811 else:
1812 # A real error occurred, raise an exception
1813 if out:
1814 log_msg = out.decode(sys.getdefaultencoding())
1815 with open(os.path.join(self.build_dir, self.log), "a") as log:
1816 log.write(log_msg)
1817
1818 overflow_flash = "region `FLASH' overflowed by"
1819 overflow_ram = "region `RAM' overflowed by"
1820
1821 if log_msg:
1822 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05001823 logger.debug("RAM/ROM Overflow")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001824 self.instance.status = "skipped"
1825 self.instance.reason = "overflow"
1826 else:
1827 self.instance.status = "failed"
1828 self.instance.reason = "Build failure"
1829
1830 results = {
1831 "returncode": p.returncode,
1832 "instance": self.instance,
1833 }
1834
1835 return results
1836
1837 def run_cmake(self, args=[]):
1838
Anas Nashif11ee5252019-12-04 12:59:10 -05001839 ldflags = "-Wl,--fatal-warnings"
Anas Nashif7a361b82019-12-06 11:37:40 -05001840 logger.debug("Running cmake on %s for %s" %(self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001841
Anas Nashif11ee5252019-12-04 12:59:10 -05001842 # fixme: add additional cflags based on options
Anas Nashifa5984ab2019-10-22 07:36:24 -07001843 cmake_args = [
1844 '-B{}'.format(self.build_dir),
1845 '-S{}'.format(self.source_dir),
1846 '-DEXTRA_CFLAGS="-Werror ',
1847 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1848 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1849 '-G{}'.format(get_generator()[1])
1850 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001851
Anas Nashifd91f9932019-11-30 10:15:23 -05001852 if options.cmake_only:
1853 cmake_args.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=1")
1854
Anas Nashif83fc06a2019-06-22 11:04:10 -04001855 args = ["-D{}".format(a.replace('"', '')) for a in args]
1856 cmake_args.extend(args)
1857
1858 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1859 cmake_args.extend(cmake_opts)
1860
1861 cmake = shutil.which('cmake')
1862 cmd = [cmake] + cmake_args
1863 kwargs = dict()
1864
1865 if self.capture_output:
1866 kwargs['stdout'] = subprocess.PIPE
1867 # CMake sends the output of message() to stderr unless it's STATUS
1868 kwargs['stderr'] = subprocess.STDOUT
1869
1870 if self.cwd:
1871 kwargs['cwd'] = self.cwd
1872
1873 p = subprocess.Popen(cmd, **kwargs)
1874 out, _ = p.communicate()
1875
1876 if p.returncode == 0:
1877 filter_results = self.parse_generated()
1878 msg = "Finished building %s for %s" %(self.source_dir, self.platform.name)
1879
1880 results = {'msg': msg, 'filter': filter_results}
1881
1882 else:
1883 self.instance.status = "failed"
1884 self.instance.reason = "Cmake build failure"
1885 results = {"returncode": p.returncode}
1886
Anas Nashif83fc06a2019-06-22 11:04:10 -04001887 if out:
1888 with open(os.path.join(self.build_dir, self.log), "a") as log:
1889 log_msg = out.decode(sys.getdefaultencoding())
1890 log.write(log_msg)
1891
1892 return results
1893
1894
1895class FilterBuilder(CMake):
1896
1897 def __init__(self, testcase, platform, source_dir, build_dir):
1898 super().__init__(testcase, platform, source_dir, build_dir)
1899
1900 self.log = "config-sanitycheck.log"
1901
1902 def parse_generated(self):
1903
1904 if self.platform.name == "unit_testing":
1905 return {}
1906
Anas Nashif83fc06a2019-06-22 11:04:10 -04001907 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001908 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1909
1910 with open(defconfig_path, "r") as fp:
1911 defconfig = {}
1912 for line in fp.readlines():
1913 m = self.config_re.match(line)
1914 if not m:
1915 if line.strip() and not line.startswith("#"):
1916 sys.stderr.write("Unrecognized line %s\n" % line)
1917 continue
1918 defconfig[m.group(1)] = m.group(2).strip()
1919
1920 self.defconfig = defconfig
1921
1922 cmake_conf = {}
1923 try:
1924 cache = CMakeCache.from_file(cmake_cache_path)
1925 except FileNotFoundError:
1926 cache = {}
1927
1928 for k in iter(cache):
1929 cmake_conf[k.name] = k.value
1930
1931 self.cmake_cache = cmake_conf
1932
Anas Nashif83fc06a2019-06-22 11:04:10 -04001933 filter_data = {
1934 "ARCH": self.platform.arch,
1935 "PLATFORM": self.platform.name
1936 }
1937 filter_data.update(os.environ)
1938 filter_data.update(self.defconfig)
1939 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001940
Anas Nashif556f3cb2019-11-05 15:36:15 -08001941 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001942 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001943 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001944 if os.path.exists(dts_path):
1945 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1946 else:
1947 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001948 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1949
Anas Nashif83fc06a2019-06-22 11:04:10 -04001950 except (ValueError, SyntaxError) as se:
1951 sys.stderr.write(
1952 "Failed processing %s\n" % self.testcase.yamlfile)
1953 raise se
1954
1955 if not res:
1956 return {os.path.join(self.platform.name, self.testcase.name): True}
1957 else:
1958 return {os.path.join(self.platform.name, self.testcase.name): False}
1959 else:
1960 self.platform.filter_data = filter_data
1961 return filter_data
1962
1963
1964class ProjectBuilder(FilterBuilder):
1965
1966 def __init__(self, suite, instance):
1967 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1968
1969 self.log = "build.log"
1970 self.instance = instance
1971 self.suite = suite
1972
1973 def setup_handler(self):
1974
1975 instance = self.instance
1976 args = []
1977
1978 # FIXME: Needs simplification
1979 if instance.platform.simulation == "qemu":
1980 instance.handler = QEMUHandler(instance, "qemu")
1981 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
1982 instance.handler.call_make_run = True
1983 elif instance.testcase.type == "unit":
1984 instance.handler = BinaryHandler(instance, "unit")
1985 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
1986 elif instance.platform.type == "native":
Anas Nashif6c0e1702019-12-05 15:24:52 -05001987 handler = BinaryHandler(instance, "native")
1988
1989 handler.asan = options.enable_lsan
1990 handler.valgrind = options.enable_valgrind
1991 handler.lsan = options.enable_lsan
1992 handler.coverage = options.enable_coverage
1993
1994 handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
1995 instance.handler = handler
Anas Nashif83fc06a2019-06-22 11:04:10 -04001996 elif instance.platform.simulation == "nsim":
1997 if find_executable("nsimdrv"):
1998 instance.handler = BinaryHandler(instance, "nsim")
1999 instance.handler.call_make_run = True
2000 elif instance.platform.simulation == "renode":
2001 if find_executable("renode"):
2002 instance.handler = BinaryHandler(instance, "renode")
2003 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
2004 instance.handler.call_make_run = True
2005 elif options.device_testing:
2006 instance.handler = DeviceHandler(instance, "device")
2007
2008 if instance.handler:
2009 instance.handler.args = args
2010
2011 def process(self, message):
2012 op = message.get('op')
2013
2014 if not self.instance.handler:
2015 self.setup_handler()
2016
2017 # The build process, call cmake and build with configured generator
2018 if op == "cmake":
2019 results = self.cmake()
2020 if self.instance.status == "failed":
2021 pipeline.put({"op": "report", "test": self.instance})
2022 elif options.cmake_only:
2023 pipeline.put({"op": "report", "test": self.instance})
2024 else:
2025 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
Anas Nashif7a361b82019-12-06 11:37:40 -05002026 logger.debug("filtering %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002027 self.instance.status = "skipped"
2028 self.instance.reason = "filter"
2029 pipeline.put({"op": "report", "test": self.instance})
2030 else:
2031 pipeline.put({"op": "build", "test": self.instance})
2032
Anas Nashif83fc06a2019-06-22 11:04:10 -04002033 elif op == "build":
Anas Nashif7a361b82019-12-06 11:37:40 -05002034 logger.debug("build test: %s" %self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002035 results = self.build()
2036
2037 if results.get('returncode', 1) > 0:
2038 pipeline.put({"op": "report", "test": self.instance})
2039 else:
2040 if self.instance.run:
2041 pipeline.put({"op": "run", "test": self.instance})
2042 else:
2043 pipeline.put({"op": "report", "test": self.instance})
2044 # Run the generated binary using one of the supported handlers
2045 elif op == "run":
Anas Nashif7a361b82019-12-06 11:37:40 -05002046 logger.debug("run test: %s" %self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002047 self.run()
2048 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002049 pipeline.put({
2050 "op": "report",
2051 "test": self.instance,
2052 "state": "executed",
2053 "status": self.instance.status,
Stephanos Ioannidis93889002019-11-11 20:31:03 +09002054 "reason": self.instance.reason}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002055 )
2056
2057 # Report results and output progress to screen
2058 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002059 with report_lock:
2060 self.report_out()
2061
Anas Nashif83fc06a2019-06-22 11:04:10 -04002062 def report_out(self):
2063 total_tests_width = len(str(self.suite.total_tests))
2064 self.suite.total_done += 1
2065 instance = self.instance
2066
2067 if instance.status in ["failed", "timeout"]:
2068 self.suite.total_failed += 1
2069 if VERBOSE or not TERMINAL:
2070 status = COLOR_RED + "FAILED " + COLOR_NORMAL + instance.reason
2071 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002072 print("")
2073 logger.error(
2074 "{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002075 instance.platform.name,
2076 instance.testcase.name,
2077 COLOR_RED,
2078 COLOR_NORMAL,
Anas Nashif7a361b82019-12-06 11:37:40 -05002079 instance.reason))
Anas Nashifc1ea4522019-10-11 07:32:45 -07002080 if not VERBOSE:
Ulf Magnussone73d2862019-10-29 11:54:02 +01002081 log_info_file(instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002082 elif instance.status == "skipped":
2083 self.suite.total_skipped += 1
2084 status = COLOR_YELLOW + "SKIPPED" + COLOR_NORMAL
Anas Nashif83fc06a2019-06-22 11:04:10 -04002085 else:
2086 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2087
2088 if VERBOSE or not TERMINAL:
2089 if options.cmake_only:
2090 more_info = "cmake"
2091 elif instance.status == "skipped":
2092 more_info = instance.reason
2093 else:
2094 if instance.handler and instance.run:
2095 more_info = instance.handler.type_str
2096 htime = instance.handler.duration
2097 if htime:
2098 more_info += " {:.3f}s".format(htime)
2099 else:
2100 more_info = "build"
2101
Anas Nashif7a361b82019-12-06 11:37:40 -05002102 logger.info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002103 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2104 instance.testcase.name, status, more_info))
2105
2106 if instance.status in ["failed", "timeout"]:
Ulf Magnussone73d2862019-10-29 11:54:02 +01002107 log_info_file(instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002108 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002109 sys.stdout.write("\rINFO - Total complete: %s%4d/%4d%s %2d%% skipped: %s%4d%s, failed: %s%4d%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04002110 COLOR_GREEN,
2111 self.suite.total_done,
2112 self.suite.total_tests,
2113 COLOR_NORMAL,
2114 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
2115 COLOR_YELLOW if self.suite.total_skipped > 0 else COLOR_NORMAL,
2116 self.suite.total_skipped,
2117 COLOR_NORMAL,
2118 COLOR_RED if self.suite.total_failed > 0 else COLOR_NORMAL,
2119 self.suite.total_failed,
2120 COLOR_NORMAL
2121 )
2122 )
2123 sys.stdout.flush()
2124
2125 def cmake(self):
2126
2127 instance = self.instance
2128 args = self.testcase.extra_args[:]
2129
2130 if options.extra_args:
2131 args += options.extra_args
2132
2133 if instance.handler:
2134 args += instance.handler.args
2135
2136 # merge overlay files into one variable
2137 overlays = ""
2138 idx = 0
2139 for arg in args:
2140 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2141 if match:
2142 overlays += match.group(1)
2143 del args[idx]
2144 idx += 1
2145
Jan Van Winkel21212f32019-09-12 00:03:35 +02002146 if (self.testcase.extra_configs or options.coverage or
2147 options.enable_asan):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002148 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
2149 os.path.join(instance.build_dir,
2150 "sanitycheck", "testcase_extra.conf")))
2151
2152 results = self.run_cmake(args)
2153 return results
2154
2155 def build(self):
2156 results = self.run_build(['--build', self.build_dir])
2157 return results
2158
2159 def run(self):
2160
2161 instance = self.instance
2162
2163 if instance.handler.type_str == "device":
2164 instance.handler.suite = self.suite
2165
2166 instance.handler.handle()
2167
Anas Nashif83fc06a2019-06-22 11:04:10 -04002168 sys.stdout.flush()
2169
2170
2171pipeline = queue.LifoQueue()
2172
Anas Nashif11ee5252019-12-04 12:59:10 -05002173
Anas Nashif83fc06a2019-06-22 11:04:10 -04002174class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2175 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2176 calls to submit() once the limit given as "bound" work items are queued for
2177 execution.
2178 :param bound: Integer - the maximum number of items in the work queue
2179 :param max_workers: Integer - the size of the thread pool
2180 """
2181 def __init__(self, bound, max_workers, **kwargs):
2182 super().__init__(max_workers)
Anas Nashif11ee5252019-12-04 12:59:10 -05002183 # self.executor = ThreadPoolExecutor(max_workers=max_workers)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002184 self.semaphore = BoundedSemaphore(bound + max_workers)
2185
2186 def submit(self, fn, *args, **kwargs):
2187 self.semaphore.acquire()
2188 try:
2189 future = super().submit(fn, *args, **kwargs)
Anas Nashif11ee5252019-12-04 12:59:10 -05002190 except Exception:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002191 self.semaphore.release()
2192 raise
2193 else:
2194 future.add_done_callback(lambda x: self.semaphore.release())
2195 return future
2196
Andrew Boie6acbe632015-07-17 12:03:52 -07002197
2198class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002199 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002200 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002201
Anas Nashif83fc06a2019-06-22 11:04:10 -04002202 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002203 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002204 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002205
Anas Nashif37f9dc52018-02-23 08:53:46 -06002206 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002207
2208 self.roots = testcase_roots
2209 if not isinstance(board_root_list, list):
Anas Nashif11ee5252019-12-04 12:59:10 -05002210 self.board_roots = [board_root_list]
Anas Nashif83fc06a2019-06-22 11:04:10 -04002211 else:
2212 self.board_roots = board_root_list
2213
Andrew Boie6acbe632015-07-17 12:03:52 -07002214 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002215 self.testcases = {}
2216 self.platforms = []
Anas Nashif5f908822019-11-25 08:19:25 -05002217 self.selected_platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002218 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002219 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002220 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002221 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002222 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002223
Anas Nashif11ee5252019-12-04 12:59:10 -05002224 self.total_tests = 0 # number of test instances
2225 self.total_cases = 0 # number of test cases
2226 self.total_done = 0 # tests completed
Anas Nashif83fc06a2019-06-22 11:04:10 -04002227 self.total_failed = 0
2228 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002229
Anas Nashif83fc06a2019-06-22 11:04:10 -04002230 self.total_platforms = 0
2231 self.start_time = 0
2232 self.duration = 0
2233 self.warnings = 0
2234 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002235
Anas Nashif83fc06a2019-06-22 11:04:10 -04002236 # hardcoded for now
2237 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002238
Anas Nashiff16ed8e2019-12-09 16:22:27 -05002239 # Debug Functions
2240 @staticmethod
2241 def info(what):
2242 sys.stdout.write(what + "\n")
2243 sys.stdout.flush()
2244
Anas Nashif83fc06a2019-06-22 11:04:10 -04002245 def update(self):
2246 self.total_tests = len(self.instances)
2247 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002248
Andrew Boie6acbe632015-07-17 12:03:52 -07002249 def compare_metrics(self, filename):
2250 # name, datatype, lower results better
2251 interesting_metrics = [("ram_size", int, True),
2252 ("rom_size", int, True)]
2253
Andrew Boie6acbe632015-07-17 12:03:52 -07002254 if not os.path.exists(filename):
Anas Nashif7a361b82019-12-06 11:37:40 -05002255 logger.info("Cannot compare metrics, %s not found" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -07002256 return []
2257
2258 results = []
2259 saved_metrics = {}
2260 with open(filename) as fp:
2261 cr = csv.DictReader(fp)
2262 for row in cr:
2263 d = {}
2264 for m, _, _ in interesting_metrics:
2265 d[m] = row[m]
2266 saved_metrics[(row["test"], row["platform"])] = d
2267
Anas Nashif83fc06a2019-06-22 11:04:10 -04002268 for instance in self.instances.values():
2269 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002270 if mkey not in saved_metrics:
2271 continue
2272 sm = saved_metrics[mkey]
2273 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002274 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002275 continue
2276 if sm[metric] == "":
2277 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002278 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002279 if delta == 0:
2280 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002281 results.append((instance, metric, instance.metrics.get(metric, 0 ), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002282 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002283 return results
2284
Anas Nashif83fc06a2019-06-22 11:04:10 -04002285 def misc_reports(self, report, show_footprint, all_deltas,
2286 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002287
Anas Nashif83fc06a2019-06-22 11:04:10 -04002288 if not report:
2289 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002290
Anas Nashif83fc06a2019-06-22 11:04:10 -04002291 deltas = self.compare_metrics(report)
2292 warnings = 0
2293 if deltas and show_footprint:
2294 for i, metric, value, delta, lower_better in deltas:
2295 if not all_deltas and ((delta < 0 and lower_better) or
2296 (delta > 0 and not lower_better)):
2297 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002298
Anas Nashif83fc06a2019-06-22 11:04:10 -04002299 percentage = (float(delta) / float(value - delta))
2300 if not all_deltas and (percentage <
2301 (footprint_threshold / 100.0)):
2302 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002303
Anas Nashif83fc06a2019-06-22 11:04:10 -04002304 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
2305 i.platform.name, i.testcase.name, COLOR_YELLOW,
2306 "INFO" if all_deltas else "WARNING", COLOR_NORMAL,
2307 metric, delta, value, percentage))
2308 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002309
Anas Nashif83fc06a2019-06-22 11:04:10 -04002310 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05002311 logger.warning("Deltas based on metrics from last %s" %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002312 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002313
Anas Nashif83fc06a2019-06-22 11:04:10 -04002314 def summary(self, unrecognized_sections):
2315 failed = 0
2316 for instance in self.instances.values():
2317 if instance.status == "failed":
2318 failed += 1
2319 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
Anas Nashif7a361b82019-12-06 11:37:40 -05002320 logger.error("%sFAILED%s: %s has unrecognized binary sections: %s" %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002321 (COLOR_RED, COLOR_NORMAL, instance.name,
2322 str(instance.metrics.get("unrecognized", []))))
2323 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002324
Anas Nashif83fc06a2019-06-22 11:04:10 -04002325 if self.total_tests and self.total_tests != self.total_skipped:
2326 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped)/ float(self.total_tests - self.total_skipped))
2327 else:
2328 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002329
Anas Nashif7a361b82019-12-06 11:37:40 -05002330 logger.info("{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002331 COLOR_RED if failed else COLOR_GREEN,
2332 self.total_tests - self.total_failed - self.total_skipped,
2333 self.total_tests,
2334 COLOR_NORMAL,
2335 pass_rate,
2336 COLOR_RED if self.total_failed else COLOR_NORMAL,
2337 self.total_failed,
2338 COLOR_NORMAL,
2339 self.total_skipped,
2340 COLOR_YELLOW if self.warnings else COLOR_NORMAL,
2341 self.warnings,
2342 COLOR_NORMAL,
2343 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002344
Anas Nashif83fc06a2019-06-22 11:04:10 -04002345 self.total_platforms = len(self.platforms)
2346 if self.platforms:
Anas Nashif7a361b82019-12-06 11:37:40 -05002347 logger.info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002348 self.total_cases,
Anas Nashif5f908822019-11-25 08:19:25 -05002349 len(self.selected_platforms),
Anas Nashif83fc06a2019-06-22 11:04:10 -04002350 self.total_platforms,
Anas Nashif5f908822019-11-25 08:19:25 -05002351 (100 * len(self.selected_platforms) / len(self.platforms))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002352 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002353
Anas Nashif83fc06a2019-06-22 11:04:10 -04002354 def save_reports(self):
2355 if not self.instances:
2356 return
Anas Nashif61e21632018-04-08 13:30:16 -05002357
Anas Nashif83fc06a2019-06-22 11:04:10 -04002358 report_name = "sanitycheck"
2359 if options.report_name:
2360 report_name = options.report_name
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002361
Anas Nashif83fc06a2019-06-22 11:04:10 -04002362 if options.report_dir:
Paul Sokolovsky3ea18692019-10-15 17:18:39 +03002363 os.makedirs(options.report_dir, exist_ok=True)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002364 filename = os.path.join(options.report_dir, report_name)
2365 outdir = options.report_dir
2366 else:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002367 filename = os.path.join(self.outdir, report_name)
2368 outdir = self.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002369
Anas Nashif83fc06a2019-06-22 11:04:10 -04002370 if not options.no_update:
2371 self.xunit_report(filename + ".xml")
2372 self.csv_report(filename + ".csv")
2373 self.target_report(outdir)
2374 if self.discards:
2375 self.discard_report(filename + "_discard.csv")
2376
2377 if options.release:
2378 self.csv_report(RELEASE_DATA)
2379
Anas Nashif83fc06a2019-06-22 11:04:10 -04002380 def add_configurations(self):
2381
2382 for board_root in self.board_roots:
2383 board_root = os.path.abspath(board_root)
2384
Anas Nashif7a361b82019-12-06 11:37:40 -05002385 logger.debug("Reading platform configuration files under %s..." %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002386 board_root)
2387
2388 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
Anas Nashif7a361b82019-12-06 11:37:40 -05002389 logger.debug("Found plaform configuration " + file)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002390 try:
2391 platform = Platform()
2392 platform.load(file)
2393 if platform.sanitycheck:
2394 self.platforms.append(platform)
2395 if platform.default:
2396 self.default_platforms.append(platform.name)
2397
2398 except RuntimeError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002399 logger.error("E: %s: can't load: %s" % (file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002400 self.load_errors += 1
2401
Anas Nashifeabaa7f2019-11-18 07:49:17 -08002402
2403 def get_all_tests(self):
2404 tests = []
2405 for _, tc in self.testcases.items():
2406 for case in tc.cases:
2407 tests.append(case)
2408
2409 return tests
2410
Anas Nashif83fc06a2019-06-22 11:04:10 -04002411 @staticmethod
2412 def get_toolchain():
2413 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2414 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2415
2416 if toolchain == "gccarmemb":
2417 # Remove this translation when gccarmemb is no longer supported.
2418 toolchain = "gnuarmemb"
2419
Anas Nashifb4bdd662018-08-15 17:12:28 -05002420 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002421 if not toolchain:
2422 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002423 except Exception as e:
2424 print(str(e))
2425 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002426
Anas Nashif83fc06a2019-06-22 11:04:10 -04002427 return toolchain
2428
Anas Nashif83fc06a2019-06-22 11:04:10 -04002429 def add_testcases(self):
2430 for root in self.roots:
2431 root = os.path.abspath(root)
2432
Anas Nashif7a361b82019-12-06 11:37:40 -05002433 logger.debug("Reading test case configuration files under %s..." %root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002434
2435 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
Anas Nashif7a361b82019-12-06 11:37:40 -05002436 logger.debug("scanning %s" % dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002437 if 'sample.yaml' in filenames:
2438 filename = 'sample.yaml'
2439 elif 'testcase.yaml' in filenames:
2440 filename = 'testcase.yaml'
2441 else:
2442 continue
2443
Anas Nashif7a361b82019-12-06 11:37:40 -05002444 logger.debug("Found possible test case in " + dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002445
2446 dirnames[:] = []
2447 tc_path = os.path.join(dirpath, filename)
2448 self.add_testcase(tc_path, root)
2449
2450 def add_testcase(self, tc_data_file, root):
2451 try:
2452 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2453 parsed_data.load()
2454
2455 tc_path = os.path.dirname(tc_data_file)
2456 workdir = os.path.relpath(tc_path, root)
2457
2458 for name in parsed_data.tests.keys():
2459 tc = TestCase()
2460 tc.name = tc.get_unique(root, workdir, name)
2461
2462 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2463
2464 tc.source_dir = tc_path
2465 tc.yamlfile = tc_data_file
2466
2467 tc.id = name
2468 tc.type = tc_dict["type"]
2469 tc.tags = tc_dict["tags"]
2470 tc.extra_args = tc_dict["extra_args"]
2471 tc.extra_configs = tc_dict["extra_configs"]
2472 tc.arch_whitelist = tc_dict["arch_whitelist"]
2473 tc.arch_exclude = tc_dict["arch_exclude"]
2474 tc.skip = tc_dict["skip"]
2475 tc.platform_exclude = tc_dict["platform_exclude"]
2476 tc.platform_whitelist = tc_dict["platform_whitelist"]
2477 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2478 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2479 tc.tc_filter = tc_dict["filter"]
2480 tc.timeout = tc_dict["timeout"]
2481 tc.harness = tc_dict["harness"]
2482 tc.harness_config = tc_dict["harness_config"]
2483 tc.build_only = tc_dict["build_only"]
2484 tc.build_on_all = tc_dict["build_on_all"]
2485 tc.slow = tc_dict["slow"]
2486 tc.min_ram = tc_dict["min_ram"]
2487 tc.depends_on = tc_dict["depends_on"]
2488 tc.min_flash = tc_dict["min_flash"]
2489 tc.extra_sections = tc_dict["extra_sections"]
2490
2491 tc.parse_subcases(tc_path)
2492
2493 if tc.name:
2494 self.testcases[tc.name] = tc
2495
2496 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002497 logger.error("%s: can't load (skipping): %s" % (tc_data_file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002498 self.load_errors += 1
2499 return False
2500
2501 return True
2502
Anas Nashif83fc06a2019-06-22 11:04:10 -04002503 def get_platform(self, name):
2504 selected_platform = None
2505 for platform in self.platforms:
2506 if platform.name == name:
2507 selected_platform = platform
2508 break
2509 return selected_platform
2510
2511 def get_last_failed(self):
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002512 last_run = os.path.join(self.outdir, "sanitycheck.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002513 try:
2514 if not os.path.exists(last_run):
2515 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" %last_run)
2516 except Exception as e:
2517 print(str(e))
2518 sys.exit(2)
2519
2520 total_tests = 0
2521 with open(last_run, "r") as fp:
2522 cr = csv.DictReader(fp)
2523 instance_list = []
2524 for row in cr:
2525 total_tests += 1
2526 if row["passed"] == "True":
2527 continue
2528 test = row["test"]
2529 platform = self.get_platform(row["platform"])
2530 instance = TestInstance(self.testcases[test], platform, self.outdir)
Jan Van Winkel21212f32019-09-12 00:03:35 +02002531 instance.create_overlay(platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002532 instance_list.append(instance)
2533 self.add_instances(instance_list)
2534
2535 tests_to_run = len(self.instances)
Anas Nashif7a361b82019-12-06 11:37:40 -05002536 logger.info("%d tests passed already, retrying %d tests" %(total_tests - tests_to_run, tests_to_run))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002537
2538 def load_from_file(self, file):
2539 try:
2540 if not os.path.exists(file):
2541 raise SanityRuntimeError(
2542 "Couldn't find input file with list of tests.")
2543 except Exception as e:
2544 print(str(e))
2545 sys.exit(2)
2546
2547 with open(file, "r") as fp:
2548 cr = csv.DictReader(fp)
2549 instance_list = []
2550 for row in cr:
2551 if row["arch"] == "arch":
2552 continue
2553 test = row["test"]
2554 platform = self.get_platform(row["platform"])
2555 instance = TestInstance(self.testcases[test], platform, self.outdir)
Jan Van Winkel21212f32019-09-12 00:03:35 +02002556 instance.create_overlay(platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002557 instance_list.append(instance)
2558 self.add_instances(instance_list)
2559
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002560 def apply_filters(self, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002561
2562 toolchain = self.get_toolchain()
2563
2564 discards = {}
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002565 platform_filter = kwargs.get('platform')
2566 testcase_filter = kwargs.get('run_individual_tests')
2567 arch_filter = kwargs.get('arch')
2568 tag_filter = kwargs.get('tag')
2569 exclude_tag = kwargs.get('exclude_tag')
2570 all_filter = kwargs.get('all')
2571 device_testing_filter = kwargs.get('device_testing')
2572 force_toolchain = kwargs.get('force_toolchain')
Anas Nashif83fc06a2019-06-22 11:04:10 -04002573
Anas Nashif7a361b82019-12-06 11:37:40 -05002574 logger.debug("platform filter: " + str(platform_filter))
2575 logger.debug(" arch_filter: " + str(arch_filter))
2576 logger.debug(" tag_filter: " + str(tag_filter))
2577 logger.debug(" exclude_tag: " + str(exclude_tag))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002578
2579 default_platforms = False
2580
2581 if platform_filter:
2582 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2583 else:
2584 platforms = self.platforms
2585
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002586 if all_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002587 logger.info("Selecting all possible platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002588 # When --all used, any --platform arguments ignored
2589 platform_filter = []
2590 elif not platform_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002591 logger.info("Selecting default platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002592 default_platforms = True
2593
Anas Nashif7a361b82019-12-06 11:37:40 -05002594 logger.info("Building initial testcase list...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002595
2596 for tc_name, tc in self.testcases.items():
2597 # list of instances per testcase, aka configurations.
2598 instance_list = []
2599 for plat in platforms:
2600 instance = TestInstance(tc, plat, self.outdir)
2601
2602 if (plat.arch == "unit") != (tc.type == "unit"):
2603 # Discard silently
2604 continue
2605
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002606 if device_testing_filter and instance.build_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002607 discards[instance] = "Not runnable on device"
2608 continue
2609
2610 if tc.skip:
2611 discards[instance] = "Skip filter"
2612 continue
2613
2614 if tc.build_on_all and not platform_filter:
2615 platform_filter = []
2616
2617 if tag_filter and not tc.tags.intersection(tag_filter):
2618 discards[instance] = "Command line testcase tag filter"
2619 continue
2620
2621 if exclude_tag and tc.tags.intersection(exclude_tag):
2622 discards[instance] = "Command line testcase exclude filter"
2623 continue
2624
2625 if testcase_filter and tc_name not in testcase_filter:
2626 discards[instance] = "Testcase name filter"
2627 continue
2628
2629 if arch_filter and plat.arch not in arch_filter:
2630 discards[instance] = "Command line testcase arch filter"
2631 continue
2632
2633 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2634 discards[instance] = "Not in test case arch whitelist"
2635 continue
2636
2637 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2638 discards[instance] = "In test case arch exclude"
2639 continue
2640
2641 if tc.platform_exclude and plat.name in tc.platform_exclude:
2642 discards[instance] = "In test case platform exclude"
2643 continue
2644
2645 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2646 discards[instance] = "In test case toolchain exclude"
2647 continue
2648
2649 if platform_filter and plat.name not in platform_filter:
2650 discards[instance] = "Command line platform filter"
2651 continue
2652
2653 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2654 discards[instance] = "Not in testcase platform whitelist"
2655 continue
2656
2657 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2658 discards[instance] = "Not in testcase toolchain whitelist"
2659 continue
2660
2661 if not plat.env_satisfied:
2662 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2663 continue
2664
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002665 if not force_toolchain \
Anas Nashif83fc06a2019-06-22 11:04:10 -04002666 and toolchain and (toolchain not in plat.supported_toolchains) \
2667 and tc.type != 'unit':
2668 discards[instance] = "Not supported by the toolchain"
2669 continue
2670
2671 if plat.ram < tc.min_ram:
2672 discards[instance] = "Not enough RAM"
2673 continue
2674
2675 if tc.depends_on:
2676 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2677 if dep_intersection != set(tc.depends_on):
2678 discards[instance] = "No hardware support"
2679 continue
2680
2681 if plat.flash < tc.min_flash:
2682 discards[instance] = "Not enough FLASH"
2683 continue
2684
2685 if set(plat.ignore_tags) & tc.tags:
2686 discards[instance] = "Excluded tags per platform"
2687 continue
2688
2689 # if nothing stopped us until now, it means this configuration
2690 # needs to be added.
2691 instance_list.append(instance)
2692
2693 # no configurations, so jump to next testcase
2694 if not instance_list:
2695 continue
2696
2697 # if sanitycheck was launched with no platform options at all, we
2698 # take all default platforms
2699 if default_platforms and not tc.build_on_all:
2700 if tc.platform_whitelist:
2701 a = set(self.default_platforms)
2702 b = set(tc.platform_whitelist)
2703 c = a.intersection(b)
2704 if c:
2705 aa = list( filter( lambda tc: tc.platform.name in c, instance_list))
2706 self.add_instances(aa)
2707 else:
2708 self.add_instances(instance_list[:1])
2709 else:
2710 instances = list( filter( lambda tc: tc.platform.default, instance_list))
2711 self.add_instances(instances)
2712
2713 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2714 discards[instance] = "Not a default test platform"
2715
2716 else:
2717 self.add_instances(instance_list)
2718
2719 for _, case in self.instances.items():
Jan Van Winkel21212f32019-09-12 00:03:35 +02002720 case.create_overlay(case.platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002721
2722 self.discards = discards
Anas Nashif5f908822019-11-25 08:19:25 -05002723 self.selected_platforms = set(p.platform.name for p in self.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04002724
2725 return discards
2726
2727 def add_instances(self, instance_list):
2728 for instance in instance_list:
2729 self.instances[instance.name] = instance
2730
2731 def add_tasks_to_queue(self):
2732 for instance in self.instances.values():
2733 if options.test_only:
2734 if instance.run:
2735 pipeline.put({"op": "run", "test": instance, "status": "built"})
2736 else:
2737 if instance.status not in ['passed', 'skipped']:
2738 instance.status = None
2739 pipeline.put({"op": "cmake", "test": instance})
2740
2741 return "DONE FEEDING"
2742
2743 def execute(self):
2744 def calc_one_elf_size(instance):
2745 if instance.status not in ["failed", "skipped"]:
2746 if instance.platform.type != "native":
2747 size_calc = instance.calculate_sizes()
2748 instance.metrics["ram_size"] = size_calc.get_ram_size()
2749 instance.metrics["rom_size"] = size_calc.get_rom_size()
2750 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2751 else:
2752 instance.metrics["ram_size"] = 0
2753 instance.metrics["rom_size"] = 0
2754 instance.metrics["unrecognized"] = []
2755
2756 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2757
Anas Nashif7a361b82019-12-06 11:37:40 -05002758 logger.info("Adding tasks to the queue...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002759 # We can use a with statement to ensure threads are cleaned up promptly
2760 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2761
2762 # start a future for a thread which sends work in through the queue
2763 future_to_test = {
2764 executor.submit(self.add_tasks_to_queue): 'FEEDER DONE'}
2765
2766 while future_to_test:
2767 # check for status of the futures which are currently working
2768 done, _ = concurrent.futures.wait(
2769 future_to_test, timeout=0.25,
2770 return_when=concurrent.futures.FIRST_COMPLETED)
2771
2772 # if there is incoming work, start a new future
2773 while not pipeline.empty():
2774 # fetch a url from the queue
2775 message = pipeline.get()
2776 test = message['test']
2777
2778 # Start the load operation and mark the future with its URL
2779 pb = ProjectBuilder(self, test)
2780 future_to_test[executor.submit(pb.process, message)] = test.name
2781
2782 # process any completed futures
2783 for future in done:
2784 test = future_to_test[future]
2785 try:
2786 data = future.result()
2787 except Exception as exc:
Anas Nashif4f043862019-11-05 06:01:49 -08002788 sys.exit('%r generated an exception: %s' % (test, exc))
2789
Anas Nashif83fc06a2019-06-22 11:04:10 -04002790 else:
2791 if data:
Anas Nashif7a361b82019-12-06 11:37:40 -05002792 logger.debug(data)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002793
2794 # remove the now completed future
2795 del future_to_test[future]
2796
2797 if options.enable_size_report and not options.cmake_only:
2798 # Parallelize size calculation
2799 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2800 futures = [executor.submit(calc_one_elf_size, instance)
2801 for instance in self.instances.values()]
2802 concurrent.futures.wait(futures)
2803 else:
2804 for instance in self.instances.values():
2805 instance.metrics["ram_size"] = 0
2806 instance.metrics["rom_size"] = 0
2807 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2808 instance.metrics["unrecognized"] = []
2809
Anas Nashif83fc06a2019-06-22 11:04:10 -04002810 def discard_report(self, filename):
2811
2812 try:
2813 if self.discards is None:
2814 raise SanityRuntimeError("apply_filters() hasn't been run!")
2815 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002816 logger.error(str(e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002817 sys.exit(2)
2818
2819 with open(filename, "wt") as csvfile:
2820 fieldnames = ["test", "arch", "platform", "reason"]
2821 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2822 cw.writeheader()
2823 for instance, reason in sorted(self.discards.items()):
2824 rowdict = {"test": instance.testcase.name,
2825 "arch": instance.platform.arch,
2826 "platform": instance.platform.name,
2827 "reason": reason}
2828 cw.writerow(rowdict)
2829
Anas Nashif83fc06a2019-06-22 11:04:10 -04002830 def target_report(self, outdir):
2831 run = "Sanitycheck"
2832 eleTestsuite = None
2833
2834 platforms = {inst.platform.name for _,inst in self.instances.items()}
2835 for platform in platforms:
2836 errors = 0
2837 passes = 0
2838 fails = 0
2839 duration = 0
2840 skips = 0
2841 for _, instance in self.instances.items():
2842 if instance.platform.name != platform:
2843 continue
2844
2845 handler_time = instance.metrics.get('handler_time', 0)
2846 duration += handler_time
2847 for k in instance.results.keys():
2848 if instance.results[k] == 'PASS':
2849 passes += 1
2850 elif instance.results[k] == 'BLOCK':
2851 errors += 1
2852 elif instance.results[k] == 'SKIP':
2853 skips += 1
2854 else:
2855 fails += 1
2856
2857 eleTestsuites = ET.Element('testsuites')
2858 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2859 name=run, time="%f" % duration,
2860 tests="%d" % (errors + passes + fails),
2861 failures="%d" % fails,
2862 errors="%d" % errors, skipped="%d" %skips)
2863
2864 handler_time = 0
2865
2866 # print out test results
2867 for _, instance in self.instances.items():
2868 if instance.platform.name != platform:
2869 continue
2870 handler_time = instance.metrics.get('handler_time', 0)
2871 for k in instance.results.keys():
2872 eleTestcase = ET.SubElement(
2873 eleTestsuite, 'testcase', classname="%s:%s" %(instance.platform.name, os.path.basename(instance.testcase.name)),
2874 name="%s" % (k), time="%f" %handler_time)
2875 if instance.results[k] in ['FAIL', 'BLOCK']:
2876 el = None
2877
2878 if instance.results[k] == 'FAIL':
2879 el = ET.SubElement(
2880 eleTestcase,
2881 'failure',
2882 type="failure",
2883 message="failed")
2884 elif instance.results[k] == 'BLOCK':
2885 el = ET.SubElement(
2886 eleTestcase,
2887 'error',
2888 type="failure",
2889 message="failed")
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002890 p = os.path.join(self.outdir, instance.platform.name, instance.testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002891 log_file = os.path.join(p, "handler.log")
2892
2893 if os.path.exists(log_file):
2894 with open(log_file, "rb") as f:
2895 log = f.read().decode("utf-8")
2896 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2897 el.text = filtered_string
2898
2899 elif instance.results[k] == 'SKIP':
2900 el = ET.SubElement(
2901 eleTestcase,
2902 'skipped',
2903 type="skipped",
2904 message="Skipped")
2905
2906
2907 result = ET.tostring(eleTestsuites)
2908 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
2909 f.write(result)
2910
Anas Nashif83fc06a2019-06-22 11:04:10 -04002911 def xunit_report(self, filename):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002912 fails = 0
2913 passes = 0
2914 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002915 skips = 0
2916 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04002917
Anas Nashif83fc06a2019-06-22 11:04:10 -04002918 for instance in self.instances.values():
2919 handler_time = instance.metrics.get('handler_time', 0)
2920 duration += handler_time
2921 if instance.status == "failed":
2922 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002923 errors += 1
2924 else:
2925 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04002926 elif instance.status == 'skipped':
2927 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04002928 else:
2929 passes += 1
2930
2931 run = "Sanitycheck"
2932 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002933 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002934
Anas Nashif83fc06a2019-06-22 11:04:10 -04002935 # When we re-run the tests, we re-use the results and update only with
2936 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04002937 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002938 tree = ET.parse(filename)
2939 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002940 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002941 else:
2942 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002943 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04002944 name=run, time="%f" % duration,
2945 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05002946 failures="%d" % fails,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002947 errors="%d" %(errors), skip="%s" %(skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002948
Anas Nashif83fc06a2019-06-22 11:04:10 -04002949 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04002950
Anas Nashif83fc06a2019-06-22 11:04:10 -04002951 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04002952 if append:
2953 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002954 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04002955 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002956 eleTestsuite.remove(tc)
2957
Anas Nashif83fc06a2019-06-22 11:04:10 -04002958 handler_time = 0
2959 if instance.status != "failed" and instance.handler:
2960 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002961
Anas Nashif3ba1d432017-12-05 15:28:44 -05002962 eleTestcase = ET.SubElement(
2963 eleTestsuite, 'testcase', classname="%s:%s" %
Anas Nashif83fc06a2019-06-22 11:04:10 -04002964 (instance.platform.name, instance.testcase.name), name="%s" %
2965 (instance.testcase.name), time="%f" %handler_time)
2966
2967 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05002968 failure = ET.SubElement(
2969 eleTestcase,
2970 'failure',
2971 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002972 message=instance.reason)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002973 p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002974 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002975 hl = os.path.join(p, "handler.log")
2976 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04002977 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002978 if os.path.exists(hl):
2979 log_file = hl
2980 else:
2981 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002982
Anas Nashifc96c90a2019-02-05 07:38:32 -05002983 if os.path.exists(log_file):
2984 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002985 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002986 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2987 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002988 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002989 elif instance.status == "skipped":
2990 ET.SubElement( eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002991
2992 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002993 with open(filename, 'wb') as report:
2994 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002995
Anas Nashif83fc06a2019-06-22 11:04:10 -04002996 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08002997 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002998 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04002999 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07003000 "rom_size"]
3001 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3002 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003003 for instance in sorted(self.instances.values()):
3004 rowdict = {"test": instance.testcase.name,
3005 "arch": instance.platform.arch,
3006 "platform": instance.platform.name,
3007 "extra_args": " ".join(instance.testcase.extra_args),
3008 "handler": instance.platform.simulation}
3009
3010 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07003011 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04003012 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07003013 else:
3014 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04003015 if instance.handler:
3016 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
3017 ram_size = instance.metrics.get("ram_size", 0)
3018 rom_size = instance.metrics.get("rom_size", 0)
3019 rowdict["ram_size"] = ram_size
3020 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003021 cw.writerow(rowdict)
3022
3023
Anas Nashif19ca7832019-11-18 08:16:21 -08003024 def get_testcase(self, identifier):
3025 results = []
3026 for _, tc in self.testcases.items():
3027 for case in tc.cases:
3028 if case == identifier:
3029 results.append(tc)
3030 return results
3031
3032
Andrew Boie6acbe632015-07-17 12:03:52 -07003033def parse_arguments():
3034
Anas Nashif3ba1d432017-12-05 15:28:44 -05003035 parser = argparse.ArgumentParser(
3036 description=__doc__,
3037 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003038 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003039
Marc Herbert932a33a2019-03-12 11:37:53 -07003040 case_select = parser.add_argument_group("Test case selection",
3041 """
3042Artificially long but functional example:
3043 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003044 --testcase-root tests/ztest/base \\
3045 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003046 --test tests/ztest/base/testing.ztest.verbose_0 \\
3047 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3048
3049 "kernel.fifo.poll" is one of the test section names in
3050 __/fifo_api/testcase.yaml
3051 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003052
Anas Nashif07d54c02018-07-21 19:29:08 -05003053 parser.add_argument("--force-toolchain", action="store_true",
3054 help="Do not filter based on toolchain, use the set "
3055 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003056 parser.add_argument(
3057 "-p", "--platform", action="append",
3058 help="Platform filter for testing. This option may be used multiple "
3059 "times. Testcases will only be built/run on the platforms "
3060 "specified. If this option is not used, then platforms marked "
3061 "as default in the platform metadata file will be chosen "
3062 "to build and test. ")
3063 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003064 "-a", "--arch", action="append",
3065 help="Arch filter for testing. Takes precedence over --platform. "
3066 "If unspecified, test all arches. Multiple invocations "
3067 "are treated as a logical 'or' relationship")
3068 parser.add_argument(
3069 "-t", "--tag", action="append",
3070 help="Specify tags to restrict which tests to run by tag value. "
3071 "Default is to not do any tag filtering. Multiple invocations "
3072 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003073 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003074 help="Specify tags of tests that should not run. "
3075 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003076 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003077 "-f",
3078 "--only-failed",
3079 action="store_true",
3080 help="Run only those tests that failed the previous sanity check "
3081 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003082
Anas Nashif3ba1d432017-12-05 15:28:44 -05003083 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003084 "--retry-failed", type=int, default=0,
3085 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003086
Marc Herbert0c465bb2019-03-11 17:28:36 -07003087 test_xor_subtest = case_select.add_mutually_exclusive_group()
3088
3089 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003090 "-s", "--test", action="append",
3091 help="Run only the specified test cases. These are named by "
Marc Herberte5cedca2019-04-08 14:02:34 -07003092 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003093
Marc Herbert0c465bb2019-03-11 17:28:36 -07003094 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003095 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003096 help="""Recursively find sub-test functions and run the entire
3097 test section where they were found, including all sibling test
3098 functions. Sub-tests are named by:
3099 section.name.in.testcase.yaml.function_name_without_test_prefix
3100 Example: kernel.fifo.poll.fifo_loop
3101 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003102
Anas Nashif3ba1d432017-12-05 15:28:44 -05003103 parser.add_argument(
3104 "-l", "--all", action="store_true",
3105 help="Build/test on all platforms. Any --platform arguments "
3106 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003107
Anas Nashif3ba1d432017-12-05 15:28:44 -05003108 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003109 "-o", "--report-dir",
3110 help="""Output reports containing results of the test run into the
3111 specified directory.
3112 The output will be both in CSV and JUNIT format
3113 (sanitycheck.csv and sanitycheck.xml).
3114 """)
3115
Anas Nashif3ba1d432017-12-05 15:28:44 -05003116 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003117 "--report-name",
3118 help="""Create a report with a custom name.
3119 """)
3120
Anas Nashif83fc06a2019-06-22 11:04:10 -04003121 parser.add_argument("--report-excluded",
3122 action="store_true",
3123 help="""List all tests that are never run based on current scope and
3124 coverage. If you are looking for accurate results, run this with
3125 --all, but this will take a while...""")
3126
Daniel Leung7f850102016-04-08 11:07:32 -07003127 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003128 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003129
Anas Nashif3ba1d432017-12-05 15:28:44 -05003130 parser.add_argument(
3131 "-B", "--subset",
3132 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
3133 "3/5 means run the 3rd fifth of the total. "
3134 "This option is useful when running a large number of tests on "
3135 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003136
3137 parser.add_argument(
3138 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003139 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003140
Anas Nashif3ba1d432017-12-05 15:28:44 -05003141 parser.add_argument(
3142 "-y", "--dry-run", action="store_true",
Anas Nashif12d8cce2019-11-20 03:47:27 -08003143 help="""Create the filtered list of test cases, but don't actually
3144 run them. Useful if you're just interested in the discard report
3145 generated for every run and saved in the specified output
3146 directory (sanitycheck_discard.csv).
3147 """)
Andrew Boie6acbe632015-07-17 12:03:52 -07003148
Anas Nashif75547e22018-02-24 08:32:14 -06003149 parser.add_argument("--list-tags", action="store_true",
3150 help="list all tags in selected tests")
3151
Marc Herbertedf17592019-03-08 12:39:11 -08003152 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07003153 help="""List of all sub-test functions recursively found in
3154 all --testcase-root arguments. Note different sub-tests can share
3155 the same section name and come from different directories.
3156 The output is flattened and reports --sub-test names only,
3157 not their directories. For instance net.socket.getaddrinfo_ok
3158 and net.socket.fd_set belong to different directories.
3159 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003160
Anas Nashif434995c2019-12-01 13:55:11 -05003161 case_select.add_argument("--test-tree", action="store_true",
3162 help="""Output the testsuite in a tree form""")
3163
Anas Nashif19ca7832019-11-18 08:16:21 -08003164 case_select.add_argument("--list-test-duplicates", action="store_true",
3165 help="""List tests with duplicate identifiers.
3166 """)
3167
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003168 parser.add_argument("--export-tests", action="store",
3169 metavar="FILENAME",
3170 help="Export tests case meta-data to a file in CSV format.")
3171
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003172
Anas Nashif654ec5982019-04-11 08:38:21 -04003173 parser.add_argument("--timestamps",
3174 action="store_true",
Anas Nashifd4cef072019-12-08 11:58:00 -05003175 help="Print all messages with time stamps")
Anas Nashif654ec5982019-04-11 08:38:21 -04003176
Anas Nashif3ba1d432017-12-05 15:28:44 -05003177 parser.add_argument(
3178 "-r", "--release", action="store_true",
3179 help="Update the benchmark database with the results of this test "
3180 "run. Intended to be run by CI when tagging an official "
3181 "release. This database is used as a basis for comparison "
3182 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003183 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003184 help="Treat warning conditions as errors")
3185 parser.add_argument(
3186 "-v",
3187 "--verbose",
3188 action="count",
3189 default=0,
3190 help="Emit debugging information, call multiple times to increase "
3191 "verbosity")
3192 parser.add_argument(
3193 "-i", "--inline-logs", action="store_true",
3194 help="Upon test failure, print relevant log data to stdout "
3195 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003196 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003197 help="log also to file")
3198 parser.add_argument(
3199 "-m", "--last-metrics", action="store_true",
3200 help="Instead of comparing metrics from the last --release, "
3201 "compare with the results of the previous sanity check "
3202 "invocation")
3203 parser.add_argument(
3204 "-u",
3205 "--no-update",
3206 action="store_true",
3207 help="do not update the results of the last run of the sanity "
3208 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003209 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003210 "-F",
3211 "--load-tests",
3212 metavar="FILENAME",
3213 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003214 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003215
Marc Herbertedf17592019-03-08 12:39:11 -08003216 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003217 "-E",
3218 "--save-tests",
3219 metavar="FILENAME",
3220 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003221 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003222
Andy Doancbecadd2019-02-08 10:19:10 -06003223 test_or_build = parser.add_mutually_exclusive_group()
3224 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003225 "-b", "--build-only", action="store_true",
3226 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003227
Andy Doancbecadd2019-02-08 10:19:10 -06003228 test_or_build.add_argument(
3229 "--test-only", action="store_true",
3230 help="""Only run device tests with current artifacts, do not build
3231 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003232 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003233 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003234 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003235
3236 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003237 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003238 help="Number of jobs for building, defaults to number of CPU threads, "
3239 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003240
3241 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06003242 "--show-footprint", action="store_true",
3243 help="Show footprint statistics and deltas since last release."
3244 )
3245 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003246 "-H", "--footprint-threshold", type=float, default=5,
3247 help="When checking test case footprint sizes, warn the user if "
3248 "the new app size is greater then the specified percentage "
3249 "from the last release. Default is 5. 0 to warn on any "
3250 "increase on app size")
3251 parser.add_argument(
3252 "-D", "--all-deltas", action="store_true",
3253 help="Show all footprint deltas, positive or negative. Implies "
3254 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003255 parser.add_argument(
3256 "-O", "--outdir",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003257 default=os.path.join(os.getcwd(),"sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003258 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05003259 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003260 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003261 parser.add_argument(
3262 "-n", "--no-clean", action="store_true",
3263 help="Do not delete the outdir before building. Will result in "
3264 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08003265 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003266 "-T", "--testcase-root", action="append", default=[],
3267 help="Base directory to recursively search for test cases. All "
3268 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07003269 "called multiple times. Defaults to the 'samples/' and "
3270 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003271
Anas Nashif3ba1d432017-12-05 15:28:44 -05003272 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3273 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003274
Anas Nashif3ba1d432017-12-05 15:28:44 -05003275 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003276 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003277 help="""Directory to search for board configuration files. All .yaml
3278files in the directory will be processed. The directory should have the same
3279structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3280
Anas Nashif3ba1d432017-12-05 15:28:44 -05003281 parser.add_argument(
3282 "-z", "--size", action="append",
3283 help="Don't run sanity checks. Instead, produce a report to "
3284 "stdout detailing RAM/ROM sizes on the specified filenames. "
3285 "All other command line arguments ignored.")
3286 parser.add_argument(
3287 "-S", "--enable-slow", action="store_true",
3288 help="Execute time-consuming test cases that have been marked "
3289 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003290 parser.add_argument(
3291 "--disable-unrecognized-section-test", action="store_true",
3292 default=False,
3293 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003294 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003295 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003296 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003297 parser.add_argument("--disable-asserts", action="store_false",
3298 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003299 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003300 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003301 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003302 parser.add_argument("--enable-size-report", action="store_true",
3303 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003304
3305 parser.add_argument(
3306 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003307 help="""Extra CMake cache entries to define when building test cases.
3308 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003309 prefixed with -D before being passed to CMake.
3310
3311 E.g
3312 "sanitycheck -x=USE_CCACHE=0"
3313 will translate to
3314 "cmake -DUSE_CCACHE=0"
3315
3316 which will ultimately disable ccache.
3317 """
3318 )
Michael Scott421ce462019-06-18 09:37:46 -07003319
Andy Doan79c48842019-02-08 10:09:04 -06003320 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003321 "--device-testing", action="store_true",
3322 help="Test on device directly. Specify the serial device to "
3323 "use with the --device-serial option.")
3324
3325 parser.add_argument(
3326 "-X", "--fixture", action="append", default=[],
3327 help="Specify a fixture that a board might support")
3328 parser.add_argument(
3329 "--device-serial",
3330 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3331
3332 parser.add_argument("--generate-hardware-map",
3333 help="""Probe serial devices connected to this platform
3334 and create a hardware map file to be used with
3335 --device-testing
3336 """)
3337
3338 parser.add_argument("--hardware-map",
3339 help="""Load hardware map from a file. This will be used
3340 for testing on hardware that is listed in the file.
3341 """)
3342
3343 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003344 "--west-flash", nargs='?', const=[],
3345 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003346 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003347
Michael Scott4ca54392019-07-09 14:21:30 -07003348 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003349 --west-flash="--board-id=foobar,--erase"
3350 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003351
3352 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003353 """
3354 )
Michael Scott421ce462019-06-18 09:37:46 -07003355 parser.add_argument(
3356 "--west-runner",
3357 help="""Uses the specified west runner instead of default when running
3358 with --west-flash.
3359
3360 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3361 --west-flash --west-runner=pyocd"
3362 will translate to "west flash --runner pyocd"
3363
3364 NOTE: west-flash must be enabled to use this option.
3365 """
3366 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003367
3368 valgrind_asan_group = parser.add_mutually_exclusive_group()
3369
3370 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003371 "--enable-valgrind", action="store_true",
3372 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003373 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003374 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003375 configuration and is mutual exclusive with --enable-asan.
3376 """)
3377
3378 valgrind_asan_group.add_argument(
3379 "--enable-asan", action="store_true",
3380 help="""Enable address sanitizer to check for several memory access
3381 errors. Libasan needs to be installed on the host. This option only
3382 works with host binaries such as those generated for the native_posix
3383 configuration and is mutual exclusive with --enable-valgrind.
3384 """)
3385
3386 parser.add_argument(
3387 "--enable-lsan", action="store_true",
3388 help="""Enable leak sanitizer to check for heap memory leaks.
3389 Libasan needs to be installed on the host. This option only
3390 works with host binaries such as those generated for the native_posix
3391 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003392 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003393
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003394 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003395 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003396
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003397 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003398 help="Generate coverage reports. Implies "
Anas Nashifb846c9a2019-11-24 07:44:56 -05003399 "--enable_coverage.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003400
Andrew Boie8047a6f2019-07-02 15:43:29 -07003401 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003402 help="Plarforms to run coverage reports on. "
Andrew Boie8047a6f2019-07-02 15:43:29 -07003403 "This option may be used multiple times. "
3404 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003405
Anas Nashif83fc06a2019-06-22 11:04:10 -04003406 parser.add_argument("--gcov-tool", default=None,
3407 help="Path to the gcov tool to use for code coverage "
3408 "reports")
3409
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003410 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='lcov',
3411 help="Tool to use to generate coverage report.")
3412
Andrew Boie6acbe632015-07-17 12:03:52 -07003413 return parser.parse_args()
3414
Anas Nashif3ba1d432017-12-05 15:28:44 -05003415
Andrew Boie6acbe632015-07-17 12:03:52 -07003416def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003417 filename = os.path.relpath(os.path.realpath(filename))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003418 if options.inline_logs:
Anas Nashif7a361b82019-12-06 11:37:40 -05003419 logger.info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003420
3421 try:
3422 with open(filename) as fp:
3423 data = fp.read()
3424 except Exception as e:
3425 data = "Unable to read log data (%s)\n" % (str(e))
3426
Anas Nashif7a361b82019-12-06 11:37:40 -05003427 logger.error(data)
3428
3429 logger.info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003430 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05003431 logger.error("see: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003432
Ulf Magnussone73d2862019-10-29 11:54:02 +01003433
3434def log_info_file(instance):
3435 build_dir = instance.build_dir
3436 h_log = "{}/handler.log".format(build_dir)
3437 b_log = "{}/build.log".format(build_dir)
3438 v_log = "{}/valgrind.log".format(build_dir)
Anas Nashif33ed7aa2019-11-25 12:18:23 -05003439 d_log = "{}/device.log".format(build_dir)
Ulf Magnussone73d2862019-10-29 11:54:02 +01003440
3441 if os.path.exists(v_log) and "Valgrind" in instance.reason:
3442 log_info("{}".format(v_log))
Anas Nashif33ed7aa2019-11-25 12:18:23 -05003443 elif os.path.exists(d_log):
3444 log_info("{}".format(d_log))
Ulf Magnussone73d2862019-10-29 11:54:02 +01003445 elif os.path.exists(h_log):
3446 log_info("{}".format(h_log))
3447 else:
3448 log_info("{}".format(b_log))
3449
3450
Andrew Boiebbd670c2015-08-17 13:16:11 -07003451def size_report(sc):
Anas Nashif7a361b82019-12-06 11:37:40 -05003452 logger.info(sc.filename)
3453 logger.info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003454 for i in range(len(sc.sections)):
3455 v = sc.sections[i]
3456
Anas Nashif7a361b82019-12-06 11:37:40 -05003457 logger.info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
Andrew Boie73b4ee62015-10-07 11:33:22 -07003458 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3459 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003460
Anas Nashif7a361b82019-12-06 11:37:40 -05003461 logger.info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003462 (sc.rom_size, sc.ram_size))
Anas Nashif7a361b82019-12-06 11:37:40 -05003463 logger.info("")
Andrew Boiebbd670c2015-08-17 13:16:11 -07003464
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003465class CoverageTool:
3466 """ Base class for every supported coverage tool
3467 """
3468
3469 def __init__(self):
3470 self.gcov_tool = options.gcov_tool
3471
3472 @staticmethod
3473 def factory(tool):
3474 if tool == 'lcov':
3475 return Lcov()
3476 if tool == 'gcovr':
3477 return Gcovr()
Anas Nashif7a361b82019-12-06 11:37:40 -05003478 logger.error("Unsupported coverage tool specified: {}".format(tool))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003479
3480 @staticmethod
3481 def retrieve_gcov_data(intput_file):
3482 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003483 logger.debug("Working on %s" %intput_file)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003484 extracted_coverage_info = {}
3485 capture_data = False
3486 capture_complete = False
3487 with open(intput_file, 'r') as fp:
3488 for line in fp.readlines():
3489 if re.search("GCOV_COVERAGE_DUMP_START", line):
3490 capture_data = True
3491 continue
3492 if re.search("GCOV_COVERAGE_DUMP_END", line):
3493 capture_complete = True
3494 break
3495 # Loop until the coverage data is found.
3496 if not capture_data:
3497 continue
3498 if line.startswith("*"):
3499 sp = line.split("<")
3500 if len(sp) > 1:
3501 # Remove the leading delimiter "*"
3502 file_name = sp[0][1:]
3503 # Remove the trailing new line char
3504 hex_dump = sp[1][:-1]
3505 else:
3506 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003507 else:
3508 continue
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003509 extracted_coverage_info.update({file_name:hex_dump})
3510 if not capture_data:
3511 capture_complete = True
3512 return {'complete': capture_complete, 'data': extracted_coverage_info}
3513
3514 @staticmethod
3515 def create_gcda_files(extracted_coverage_info):
3516 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003517 logger.debug("Generating gcda files")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003518 for filename, hexdump_val in extracted_coverage_info.items():
3519 # if kobject_hash is given for coverage gcovr fails
3520 # hence skipping it problem only in gcovr v4.1
3521 if "kobject_hash" in filename:
3522 filename = (filename[:-4]) +"gcno"
3523 try:
3524 os.remove(filename)
3525 except Exception:
3526 pass
Anas Nashifdb9592a2018-10-08 10:19:41 -04003527 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003528
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003529 with open(filename, 'wb') as fp:
3530 fp.write(bytes.fromhex(hexdump_val))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003531
Anas Nashif3ba1d432017-12-05 15:28:44 -05003532
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003533 def generate(self, outdir):
3534 for filename in glob.glob("%s/**/handler.log" % outdir, recursive=True):
3535 gcov_data = self.__class__.retrieve_gcov_data(filename)
3536 capture_complete = gcov_data['complete']
3537 extracted_coverage_info = gcov_data['data']
3538 if capture_complete:
3539 self.__class__.create_gcda_files(extracted_coverage_info)
Anas Nashif7a361b82019-12-06 11:37:40 -05003540 logger.debug("Gcov data captured: {}".format(filename))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003541 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003542 logger.error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003543
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003544 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3545 ret = self._generate(outdir, coveragelog)
3546 if ret == 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05003547 logger.info("HTML report generated: {}".format(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003548 os.path.join(outdir, "coverage", "index.html")))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003549
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003550
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003551class Lcov(CoverageTool):
3552
3553 def __init__(self):
3554 super().__init__()
3555 self.ignores = []
3556
3557 def add_ignore_file(self, pattern):
3558 self.ignores.append('*' + pattern + '*')
3559
3560 def add_ignore_directory(self, pattern):
3561 self.ignores.append(pattern + '/*')
3562
3563 def _generate(self, outdir, coveragelog):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003564 coveragefile = os.path.join(outdir, "coverage.info")
3565 ztestfile = os.path.join(outdir, "ztest.info")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003566 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool,
3567 "--capture", "--directory", outdir,
3568 "--rc", "lcov_branch_coverage=1",
3569 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003570 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003571 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--extract",
3572 coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003573 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003574 "--output-file", ztestfile,
3575 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3576
Anas Nashif3cbffef2018-11-07 23:50:54 -05003577 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003578 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3579 ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003580 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3581 "--output-file", ztestfile,
3582 "--rc", "lcov_branch_coverage=1"],
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003583 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003584 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003585 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003586 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003587
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003588 for i in self.ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003589 subprocess.call(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003590 ["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3591 coveragefile, i, "--output-file",
3592 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003593 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003594
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003595 # The --ignore-errors source option is added to avoid it exiting due to
3596 # samples/application_development/external_lib/
3597 return subprocess.call(["genhtml", "--legend", "--branch-coverage",
3598 "--ignore-errors", "source",
3599 "-output-directory",
3600 os.path.join(outdir, "coverage")] + files,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003601 stdout=coveragelog)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003602
3603
3604class Gcovr(CoverageTool):
3605
3606 def __init__(self):
3607 super().__init__()
3608 self.ignores = []
3609
3610 def add_ignore_file(self, pattern):
3611 self.ignores.append('.*' + pattern + '.*')
3612
3613 def add_ignore_directory(self, pattern):
3614 self.ignores.append(pattern + '/.*')
3615
3616 @staticmethod
3617 def _interleave_list(prefix, list):
3618 tuple_list = [(prefix, item) for item in list]
3619 return [item for sublist in tuple_list for item in sublist]
3620
3621 def _generate(self, outdir, coveragelog):
3622 coveragefile = os.path.join(outdir, "coverage.json")
3623 ztestfile = os.path.join(outdir, "ztest.json")
3624
3625 excludes = Gcovr._interleave_list("-e", self.ignores)
3626
3627 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
3628 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3629 self.gcov_tool, "-e", "tests/*"] + excludes +
3630 ["--json", "-o", coveragefile, outdir],
3631 stdout=coveragelog)
3632
3633 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3634 self.gcov_tool, "-f", "tests/ztest", "-e",
3635 "tests/ztest/test/*", "--json", "-o", ztestfile,
3636 outdir], stdout=coveragelog)
3637
3638 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
3639 files = [coveragefile, ztestfile]
3640 else:
3641 files = [coveragefile]
3642
3643 subdir = os.path.join(outdir, "coverage")
3644 os.makedirs(subdir, exist_ok=True)
3645
3646 tracefiles = self._interleave_list("--add-tracefile", files)
3647
3648 return subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--html",
3649 "--html-details"] + tracefiles +
3650 ["-o", os.path.join(subdir, "index.html")],
3651 stdout=coveragelog)
3652
Anas Nashif3ba1d432017-12-05 15:28:44 -05003653
Anas Nashif83fc06a2019-06-22 11:04:10 -04003654def get_generator():
3655 if options.ninja:
3656 generator_cmd = "ninja"
3657 generator = "Ninja"
3658 else:
3659 generator_cmd = "make"
3660 generator = "Unix Makefiles"
3661 return generator_cmd, generator
3662
3663
3664def export_tests(filename, tests):
3665 with open(filename, "wt") as csvfile:
3666 fieldnames = ['section', 'subsection', 'title', 'reference']
3667 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3668 for test in tests:
3669 data = test.split(".")
Anas Nashif19ca7832019-11-18 08:16:21 -08003670 if len(data) > 1:
3671 subsec = " ".join(data[1].split("_")).title()
3672 rowdict = {
3673 "section": data[0].capitalize(),
3674 "subsection": subsec,
3675 "title": test,
3676 "reference": test
3677 }
3678 cw.writerow(rowdict)
3679 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003680 logger.info("{} can't be exported".format(test))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003681
Anas Nashif83fc06a2019-06-22 11:04:10 -04003682
3683def native_and_unit_first(a, b):
3684 if a[0].startswith('unit_testing'):
3685 return -1
3686 if b[0].startswith('unit_testing'):
3687 return 1
3688 if a[0].startswith('native_posix'):
3689 return -1
3690 if b[0].startswith('native_posix'):
3691 return 1
3692 if a[0].split("/",1)[0].endswith("_bsim"):
3693 return -1
3694 if b[0].split("/",1)[0].endswith("_bsim"):
3695 return 1
3696
3697 return (a > b) - (a < b)
3698
3699
Anas Nashif5f908822019-11-25 08:19:25 -05003700class HardwareMap:
3701 manufacturer = [
3702 'ARM',
3703 'SEGGER',
3704 'MBED',
3705 'STMicroelectronics',
3706 'Atmel Corp.',
3707 'Texas Instruments',
3708 'Silicon Labs',
3709 'NXP Semiconductors'
3710 ]
3711
3712 runner_mapping = {
3713 'pyocd': [
3714 'DAPLink CMSIS-DAP',
3715 'MBED CMSIS-DAP'
3716 ],
3717 'jlink': [
3718 'J-Link',
3719 'J-Link OB'
3720 ],
3721 'openocd': [
3722 'STM32 STLink', '^XDS110.*'
3723 ]
3724 }
3725
3726 def __init__(self):
3727 self.detected = []
3728 self.connected_hardware = []
3729
3730 def load_device_from_cmdline(self, serial, platform):
3731 device = {
3732 "serial": serial,
3733 "platform": platform,
3734 "counter": 0,
3735 "available": True,
3736 "connected": True
3737 }
3738 self.connected_hardware.append(device)
3739
3740 def load_hardware_map(self, map_file):
3741 with open(map_file, 'r') as stream:
3742 try:
3743 self.connected_hardware = yaml.safe_load(stream)
3744 except yaml.YAMLError as exc:
3745 print(exc)
3746 for i in self.connected_hardware:
3747 i['counter'] = 0
3748
3749 def scan_hw(self):
3750 from serial.tools import list_ports
3751
3752 serial_devices = list_ports.comports()
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003753 logger.info("Scanning connected hardware...")
Anas Nashif5f908822019-11-25 08:19:25 -05003754 for d in serial_devices:
3755 if d.manufacturer in self.manufacturer:
3756
3757 # TI XDS110 can have multiple serial devices for a single board
3758 # assume endpoint 0 is the serial, skip all others
3759 if d.manufacturer == 'Texas Instruments' and not d.location.endswith('0'):
3760 continue
3761 s_dev = {}
3762 s_dev['platform'] = "unknown"
3763 s_dev['id'] = d.serial_number
3764 s_dev['serial'] = d.device
3765 s_dev['product'] = d.product
3766 s_dev['runner'] = 'unknown'
Anas Nashif11ee5252019-12-04 12:59:10 -05003767 for runner,_ in self.runner_mapping.items():
Anas Nashif5f908822019-11-25 08:19:25 -05003768 products = self.runner_mapping.get(runner)
3769 if d.product in products:
3770 s_dev['runner'] = runner
3771 continue
3772 # Try regex matching
3773 for p in products:
3774 if re.match(p, d.product):
3775 s_dev['runner'] = runner
3776
3777 s_dev['available'] = True
3778 s_dev['connected'] = True
3779 self.detected.append(s_dev)
3780 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003781 logger.warning("Unsupported device (%s): %s" %(d.manufacturer, d))
Anas Nashif5f908822019-11-25 08:19:25 -05003782
3783 def write_map(self, hwm_file):
3784 # use existing map
3785 if os.path.exists(hwm_file):
3786 with open(hwm_file, 'r') as yaml_file:
3787 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3788 # disconnect everything
3789 for h in hwm:
3790 h['connected'] = False
3791 h['serial'] = None
3792
3793 for d in self.detected:
3794 for h in hwm:
3795 if d['id'] == h['id'] and d['product'] == h['product']:
Anas Nashif5f908822019-11-25 08:19:25 -05003796 h['connected'] = True
3797 h['serial'] = d['serial']
3798 d['match'] = True
3799
3800 new = list(filter(lambda n: not n.get('match', False), self.detected))
3801 hwm = hwm + new
3802
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003803 logger.info("Registered devices:")
3804 print("")
Anas Nashife5006d12019-12-01 11:41:22 -05003805 table = []
3806 header = ["Platform", "ID", "Serial device"]
3807 for p in sorted(hwm, key = lambda i: i['platform']):
3808 platform = p.get('platform')
3809 table.append([platform, p.get('id', None), p.get('serial')])
3810 print(tabulate(table, headers=header, tablefmt="github"))
3811
3812
Anas Nashif5f908822019-11-25 08:19:25 -05003813 with open(hwm_file, 'w') as yaml_file:
3814 yaml.dump(hwm, yaml_file, default_flow_style=False)
3815
3816 else:
3817 # create new file
3818 with open(hwm_file, 'w') as yaml_file:
3819 yaml.dump(self.detected, yaml_file, default_flow_style=False)
3820
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003821run_individual_tests = None
3822options = None
Anas Nashif5f908822019-11-25 08:19:25 -05003823
Andrew Boie6acbe632015-07-17 12:03:52 -07003824def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003825 start_time = time.time()
Anas Nashif7a361b82019-12-06 11:37:40 -05003826 global VERBOSE
Anas Nashife10b6512017-12-30 13:01:45 -05003827 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003828 global run_individual_tests
Andrew Boie1578ef72019-07-03 10:19:29 -07003829
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003830 options = parse_arguments()
Anas Nashif7a361b82019-12-06 11:37:40 -05003831
3832 # Cleanup
3833 if options.no_clean or options.only_failed or options.test_only:
3834 if os.path.exists(options.outdir):
3835 logger.info("Keeping artifacts untouched")
3836 elif os.path.exists(options.outdir):
3837 for i in range(1,100):
3838 new_out = options.outdir + ".{}".format(i)
3839 if not os.path.exists(new_out):
3840 logger.info("Renaming output directory to {}".format(new_out))
3841 shutil.move(options.outdir, new_out)
3842 break
3843
3844 os.makedirs(options.outdir, exist_ok=True)
3845
3846 # create file handler which logs even debug messages
3847 if options.log_file:
3848 fh = logging.FileHandler(options.log_file)
3849 else:
3850 fh = logging.FileHandler(os.path.join(options.outdir, "sanitycheck.log"))
3851
3852 fh.setLevel(logging.DEBUG)
3853
3854 # create console handler with a higher log level
3855 ch = logging.StreamHandler()
3856
Anas Nashif7a361b82019-12-06 11:37:40 -05003857 VERBOSE += options.verbose
3858 if VERBOSE > 1:
3859 ch.setLevel(logging.DEBUG)
3860 else:
3861 ch.setLevel(logging.INFO)
3862
3863
3864 # create formatter and add it to the handlers
Anas Nashifd4cef072019-12-08 11:58:00 -05003865 if options.timestamps:
3866 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
3867 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003868 formatter = logging.Formatter('%(levelname)-7s - %(message)s')
Anas Nashifd4cef072019-12-08 11:58:00 -05003869
Anas Nashif7a361b82019-12-06 11:37:40 -05003870 formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
3871 ch.setFormatter(formatter)
3872 fh.setFormatter(formatter_file)
3873
3874 # add the handlers to logger
3875 logger.addHandler(ch)
3876 logger.addHandler(fh)
Andrew Boiebbd670c2015-08-17 13:16:11 -07003877
Anas Nashif5f908822019-11-25 08:19:25 -05003878 hwm = HardwareMap()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003879 if options.generate_hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05003880 hwm.scan_hw()
3881 hwm.write_map(options.generate_hardware_map)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003882 return
3883
Anas Nashif5f908822019-11-25 08:19:25 -05003884 if not options.device_testing and options.hardware_map:
3885 hwm.load_hardware_map(options.hardware_map)
3886
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003887 logger.info("Available devices:")
3888 print("")
Anas Nashif5f908822019-11-25 08:19:25 -05003889 table = []
3890 header = ["Platform", "ID", "Serial device"]
3891 for p in hwm.connected_hardware:
3892 platform = p.get('platform')
3893 if p['connected']:
3894 table.append([platform, p.get('id', None), p['serial']])
3895 print(tabulate(table, headers=header, tablefmt="github"))
3896 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04003897
Michael Scott421ce462019-06-18 09:37:46 -07003898 if options.west_runner and not options.west_flash:
Anas Nashif7a361b82019-12-06 11:37:40 -05003899 logger.error("west-runner requires west-flash to be enabled")
Michael Scott421ce462019-06-18 09:37:46 -07003900 sys.exit(1)
3901
Michael Scott4ca54392019-07-09 14:21:30 -07003902 if options.west_flash and not options.device_testing:
Anas Nashif7a361b82019-12-06 11:37:40 -05003903 logger.error("west-flash requires device-testing to be enabled")
Michael Scott4ca54392019-07-09 14:21:30 -07003904 sys.exit(1)
3905
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003906 if options.coverage:
3907 options.enable_coverage = True
Christian Taedckec415a4e2019-11-23 23:25:36 +01003908
3909 if not options.coverage_platform:
3910 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003911
Anas Nashife10b6512017-12-30 13:01:45 -05003912 if options.size:
3913 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003914 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003915 sys.exit(0)
3916
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003917
Anas Nashife10b6512017-12-30 13:01:45 -05003918 if options.subset:
3919 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003920 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif7a361b82019-12-06 11:37:40 -05003921 logger.info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003922 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003923 logger.error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003924 return
3925
Andrew Boie6acbe632015-07-17 12:03:52 -07003926
Anas Nashife10b6512017-12-30 13:01:45 -05003927 if not options.testcase_root:
3928 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003929 os.path.join(ZEPHYR_BASE, "samples")]
3930
Anas Nashife0d931f2019-12-09 15:23:43 -05003931 if options.show_footprint or options.compare_report or options.release:
3932 options.enable_size_report = True
3933
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003934 suite = TestSuite(options.board_root,
3935 options.testcase_root,
3936 options.outdir)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05003937
3938 # Set number of jobs
3939 if options.jobs:
3940 suite.jobs = options.jobs
3941 elif options.build_only:
3942 suite.jobs = multiprocessing.cpu_count() * 2
3943 else:
3944 suite.jobs = multiprocessing.cpu_count()
Anas Nashif7a361b82019-12-06 11:37:40 -05003945 logger.info("JOBS: %d" % suite.jobs)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05003946
Anas Nashif83fc06a2019-06-22 11:04:10 -04003947 suite.add_testcases()
3948 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04003949
Anas Nashif83fc06a2019-06-22 11:04:10 -04003950 if options.device_testing:
3951 if options.hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05003952 hwm.load_hardware_map(options.hardware_map)
3953 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04003954 if not options.platform:
3955 options.platform = []
Anas Nashif5f908822019-11-25 08:19:25 -05003956 for platform in hwm.connected_hardware:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003957 if platform['connected']:
3958 options.platform.append(platform['platform'])
3959
3960 elif options.device_serial: #back-ward compatibility
3961 if options.platform and len(options.platform) == 1:
Anas Nashif5f908822019-11-25 08:19:25 -05003962 hwm.load_device_from_cmdline(options.device_serial, options.platform[0])
Anas Nashif83fc06a2019-06-22 11:04:10 -04003963 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003964 logger.error("""When --device-testing is used with --device-serial, only one
Anas Nashif83fc06a2019-06-22 11:04:10 -04003965 platform is allowed""")
3966
3967
Anas Nashif83fc06a2019-06-22 11:04:10 -04003968 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05003969 sys.exit(1)
3970
Anas Nashif75547e22018-02-24 08:32:14 -06003971 if options.list_tags:
3972 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003973 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06003974 tags = tags.union(tc.tags)
3975
3976 for t in tags:
3977 print("- {}".format(t))
3978
3979 return
3980
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003981 if options.export_tests:
3982 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08003983 tests = suite.get_all_tests()
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003984 export_tests(options.export_tests, tests)
3985 return
3986
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003987 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003988
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003989 if options.test:
3990 run_individual_tests = options.test
3991
Anas Nashif434995c2019-12-01 13:55:11 -05003992 if options.list_tests or options.test_tree or options.list_test_duplicates or options.sub_test:
Anas Nashif49b22d42019-06-14 13:45:34 -04003993 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08003994 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04003995
Anas Nashif19ca7832019-11-18 08:16:21 -08003996 if options.list_test_duplicates:
3997 import collections
3998 dupes = [item for item, count in collections.Counter(all_tests).items() if count > 1]
3999 if dupes:
4000 print("Tests with duplicate identifiers:")
4001 for dupe in dupes:
4002 print("- {}".format(dupe))
4003 for dc in suite.get_testcase(dupe):
4004 print(" - {}".format(dc))
4005 else:
4006 print("No duplicates found.")
4007 return
4008
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004009 if options.sub_test:
Anas Nashifd11fd782019-11-18 10:22:56 -08004010 for st in options.sub_test:
4011 subtests = suite.get_testcase(st)
4012 for sti in subtests:
4013 run_individual_tests.append(sti.name)
4014
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004015 if run_individual_tests:
Anas Nashif7a361b82019-12-06 11:37:40 -05004016 logger.info("Running the following tests:")
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004017 for test in run_individual_tests:
4018 print(" - {}".format(test))
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004019 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004020 logger.info("Tests not found")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004021 return
4022
Anas Nashif434995c2019-12-01 13:55:11 -05004023 elif options.list_tests or options.test_tree:
4024 if options.test_tree:
4025 testsuite = Node("Testsuite")
4026 samples = Node("Samples", parent=testsuite)
4027 tests = Node("Tests", parent=testsuite)
4028
Anas Nashifc1c3cc62019-12-01 13:24:33 -05004029 for test in sorted(all_tests):
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004030 cnt = cnt + 1
Anas Nashif434995c2019-12-01 13:55:11 -05004031 if options.list_tests:
4032 print(" - {}".format(test))
4033
4034 if options.test_tree:
4035 if test.startswith("sample."):
4036 sec = test.split(".")
4037 area = find(samples, lambda node: node.name == sec[1] and node.parent == samples)
4038 if not area:
4039 area = Node(sec[1], parent=samples)
4040
4041 t = Node(test, parent=area)
4042 else:
4043 sec = test.split(".")
4044 area = find(tests, lambda node: node.name == sec[0] and node.parent == tests)
4045 if not area:
4046 area = Node(sec[0], parent=tests)
4047
4048 if area and len(sec) > 2:
4049 subarea = find(area, lambda node: node.name == sec[1] and node.parent == area)
4050 if not subarea:
4051 subarea = Node(sec[1], parent=area)
4052
4053 t = Node(test, parent=subarea)
4054
4055 if options.list_tests:
4056 print("{} total.".format(cnt))
4057
4058 if options.test_tree:
4059 for pre, _, node in RenderTree(testsuite):
4060 print("%s%s" % (pre, node.name))
4061
4062
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004063 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05004064
Anas Nashifbd166f42017-09-02 12:32:08 -04004065 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04004066
4067 if options.only_failed:
4068 suite.get_last_failed()
Anas Nashif5f908822019-11-25 08:19:25 -05004069 suite.selected_platforms = set(p.platform.name for p in suite.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04004070 elif options.load_tests:
4071 suite.load_from_file(options.load_tests)
4072 elif options.test_only:
4073 last_run = os.path.join(options.outdir, "sanitycheck.csv")
4074 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04004075 else:
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004076 discards = suite.apply_filters(
4077 build_only=options.build_only,
4078 enable_slow=options.enable_slow,
4079 platform=options.platform,
4080 arch=options.arch,
4081 tag=options.tag,
4082 exclude_tag=options.exclude_tag,
4083 force_toolchain=options.force_toolchain,
4084 all=options.all,
4085 run_individual_tests=run_individual_tests,
4086 device_testing=options.device_testing
4087
4088 )
Andrew Boie6acbe632015-07-17 12:03:52 -07004089
Anas Nashif30551f42018-01-12 21:56:59 -05004090 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004091 # if we are using command line platform filter, no need to list every
4092 # other platform as excluded, we know that already.
4093 # Show only the discards that apply to the selected platforms on the
4094 # command line
4095
Andrew Boie08ce5a52016-02-22 13:28:10 -08004096 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004097 if options.platform and i.platform.name not in options.platform:
4098 continue
Anas Nashif7a361b82019-12-06 11:37:40 -05004099 logger.debug(
Anas Nashif3ba1d432017-12-05 15:28:44 -05004100 "{:<25} {:<50} {}SKIPPED{}: {}".format(
4101 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04004102 i.testcase.name,
Anas Nashif3ba1d432017-12-05 15:28:44 -05004103 COLOR_YELLOW,
4104 COLOR_NORMAL,
4105 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07004106
Anas Nashif49b22d42019-06-14 13:45:34 -04004107 if options.report_excluded:
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004108 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004109 to_be_run = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004110 for i,p in suite.instances.items():
4111 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04004112
Anas Nashif83fc06a2019-06-22 11:04:10 -04004113 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004114 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004115 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004116 print("- {}".format(not_run))
4117
4118 return
4119
Anas Nashife10b6512017-12-30 13:01:45 -05004120 if options.subset:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004121 #suite.instances = OrderedDict(sorted(suite.instances.items(),
4122 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05004123 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004124 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004125 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05004126 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04004127 if subset == sets:
4128 end = total
4129 else:
4130 end = start + per_set
4131
Anas Nashif83fc06a2019-06-22 11:04:10 -04004132 sliced_instances = islice(suite.instances.items(), start, end)
4133 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004134
Andrew Boie6acbe632015-07-17 12:03:52 -07004135
Anas Nashif83fc06a2019-06-22 11:04:10 -04004136 if options.save_tests:
4137 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07004138 return
4139
Anas Nashif7a361b82019-12-06 11:37:40 -05004140 logger.info("%d test configurations selected, %d configurations discarded due to filters." %
Anas Nashif83fc06a2019-06-22 11:04:10 -04004141 (len(suite.instances), len(discards)))
4142
Peter Bigot3d46ea52019-11-21 12:00:18 -06004143 if options.device_testing:
4144 print("\nDevice testing on:")
Anas Nashif5f908822019-11-25 08:19:25 -05004145 table = []
4146 header = ["Platform", "ID", "Serial device"]
Peter Bigot3d46ea52019-11-21 12:00:18 -06004147 for p in suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -05004148 platform = p.get('platform')
4149 if p['connected'] and platform in suite.selected_platforms:
4150 table.append([platform, p.get('id', None), p['serial']])
4151 print(tabulate(table, headers=header, tablefmt="github"))
4152 print("")
Peter Bigot3d46ea52019-11-21 12:00:18 -06004153
Anas Nashif83fc06a2019-06-22 11:04:10 -04004154 if options.dry_run:
4155 duration = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -05004156 logger.info("Completed in %d seconds" % (duration))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004157 return
4158
4159 retries = options.retry_failed + 1
4160 completed = 0
4161
4162 suite.update()
4163 suite.start_time = start_time
4164
4165 while True:
4166 completed += 1
4167
4168 if completed > 1:
Anas Nashif7a361b82019-12-06 11:37:40 -05004169 logger.info("%d Iteration:" %(completed ))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004170 time.sleep(60) # waiting for the system to settle down
4171 suite.total_done = suite.total_tests - suite.total_failed
4172 suite.total_failed = 0
4173
4174 suite.execute()
Anas Nashif7a361b82019-12-06 11:37:40 -05004175 print("")
Andrew Boie6acbe632015-07-17 12:03:52 -07004176
Anas Nashif83fc06a2019-06-22 11:04:10 -04004177 retries = retries - 1
4178 if retries == 0 or suite.total_failed == 0:
4179 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06004180
Anas Nashif83fc06a2019-06-22 11:04:10 -04004181 suite.misc_reports(options.compare_report, options.show_footprint,
4182 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07004183
Anas Nashif83a98e52019-11-24 07:42:06 -05004184 suite.duration = time.time() - start_time
4185 suite.summary(options.disable_unrecognized_section_test)
4186
Anas Nashife10b6512017-12-30 13:01:45 -05004187 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004188 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004189 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07004190
4191 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004192 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004193 if ts_plat and (ts_plat.type in {"native", "unit"}):
4194 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07004195
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004196 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07004197 options.gcov_tool = "gcov"
4198 else:
4199 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
4200 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
4201
Anas Nashif7a361b82019-12-06 11:37:40 -05004202 logger.info("Generating coverage files...")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01004203 coverage_tool = CoverageTool.factory(options.coverage_tool)
4204 coverage_tool.add_ignore_file('generated')
4205 coverage_tool.add_ignore_directory('tests')
4206 coverage_tool.add_ignore_directory('samples')
4207 coverage_tool.generate(options.outdir)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03004208
Anas Nashif83fc06a2019-06-22 11:04:10 -04004209 if options.device_testing:
4210 print("\nHardware distribution summary:\n")
Anas Nashif5f908822019-11-25 08:19:25 -05004211 table = []
4212 header = ['Board', 'ID', 'Counter']
4213 for p in hwm.connected_hardware:
4214 if p['connected'] and p['platform'] in suite.selected_platforms:
4215 row = [p['platform'], p.get('id', None), p['counter']]
4216 table.append(row)
4217 print(tabulate(table, headers=header, tablefmt="github"))
4218
Anas Nashif83fc06a2019-06-22 11:04:10 -04004219
Flavio Ceolinca1feea2019-10-22 14:03:48 -07004220 suite.save_reports()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004221 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07004222 sys.exit(1)
4223
4224if __name__ == "__main__":
4225 main()