blob: b433c3170bee901edd68a19a6a55cca446161f92 [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
185import csv
Anas Nashif83fc06a2019-06-22 11:04:10 -0400186import yaml
Andrew Boie5d4eb782015-10-02 10:04:56 -0700187import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600188import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700189import concurrent
Anas Nashifb3311ed2017-04-13 14:44:48 -0400190import xml.etree.ElementTree as ET
Anas Nashif7a361b82019-12-06 11:37:40 -0500191import logging
Anas Nashif97445682019-12-16 09:36:40 -0500192from colorama import Fore
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 Nashifd9882382019-12-12 09:58:28 -0500197
Anas Nashif434995c2019-12-01 13:55:11 -0500198try:
199 from anytree import Node, RenderTree, find
200except ImportError:
201 print("Install the anytree module to use the --test-tree option")
202
Anas Nashif5f908822019-11-25 08:19:25 -0500203try:
204 from tabulate import tabulate
205except ImportError:
206 print("Install tabulate python module with pip to use --device-testing option.")
Andrew Boie6acbe632015-07-17 12:03:52 -0700207
Kumar Gala7733b942019-09-12 17:08:43 -0500208ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
209if not ZEPHYR_BASE:
210 sys.exit("$ZEPHYR_BASE environment variable undefined")
211
212sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "dts"))
213import edtlib
Anas Nashif83fc06a2019-06-22 11:04:10 -0400214
Anas Nashif83fc06a2019-06-22 11:04:10 -0400215hw_map_local = threading.Lock()
Anas Nashifc1ea4522019-10-11 07:32:45 -0700216report_lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400217
Marc Herbert1c8632c2019-04-15 17:58:45 -0700218# Use this for internal comparisons; that's what canonicalization is
219# for. Don't use it when invoking other components of the build system
220# to avoid confusing and hard to trace inconsistencies in error messages
221# and logs, generated Makefiles, etc. compared to when users invoke these
222# components directly.
223# Note "normalization" is different from canonicalization, see os.path.
224canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
225
Andrew Boie3ea78922016-03-24 14:46:00 -0700226sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
227
Anas Nashif83fc06a2019-06-22 11:04:10 -0400228from sanity_chk import scl
229from sanity_chk import expr_parser
230
Andrew Boie6acbe632015-07-17 12:03:52 -0700231VERBOSE = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400232
Andrew Boie6acbe632015-07-17 12:03:52 -0700233RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
234 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700235
Andrew Boie6acbe632015-07-17 12:03:52 -0700236
Anas Nashif7a361b82019-12-06 11:37:40 -0500237logger = logging.getLogger('sanitycheck')
Anas Nashif7a361b82019-12-06 11:37:40 -0500238logger.setLevel(logging.DEBUG)
239
240
Anas Nashif45a97862019-01-09 08:46:42 -0500241class CMakeCacheEntry:
242 '''Represents a CMake cache entry.
243
244 This class understands the type system in a CMakeCache.txt, and
245 converts the following cache types to Python types:
246
247 Cache Type Python type
248 ---------- -------------------------------------------
249 FILEPATH str
250 PATH str
251 STRING str OR list of str (if ';' is in the value)
252 BOOL bool
253 INTERNAL str OR list of str (if ';' is in the value)
254 ---------- -------------------------------------------
255 '''
256
257 # Regular expression for a cache entry.
258 #
259 # CMake variable names can include escape characters, allowing a
260 # wider set of names than is easy to match with a regular
261 # expression. To be permissive here, use a non-greedy match up to
262 # the first colon (':'). This breaks if the variable name has a
263 # colon inside, but it's good enough.
264 CACHE_ENTRY = re.compile(
265 r'''(?P<name>.*?) # name
266 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
267 =(?P<value>.*) # value
268 ''', re.X)
269
270 @classmethod
271 def _to_bool(cls, val):
272 # Convert a CMake BOOL string into a Python bool.
273 #
274 # "True if the constant is 1, ON, YES, TRUE, Y, or a
275 # non-zero number. False if the constant is 0, OFF, NO,
276 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
277 # the suffix -NOTFOUND. Named boolean constants are
278 # case-insensitive. If the argument is not one of these
279 # constants, it is treated as a variable."
280 #
281 # https://cmake.org/cmake/help/v3.0/command/if.html
282 val = val.upper()
283 if val in ('ON', 'YES', 'TRUE', 'Y'):
284 return 1
285 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
286 return 0
287 elif val.endswith('-NOTFOUND'):
288 return 0
289 else:
290 try:
291 v = int(val)
292 return v != 0
293 except ValueError as exc:
294 raise ValueError('invalid bool {}'.format(val)) from exc
295
296 @classmethod
297 def from_line(cls, line, line_no):
298 # Comments can only occur at the beginning of a line.
299 # (The value of an entry could contain a comment character).
300 if line.startswith('//') or line.startswith('#'):
301 return None
302
303 # Whitespace-only lines do not contain cache entries.
304 if not line.strip():
305 return None
306
307 m = cls.CACHE_ENTRY.match(line)
308 if not m:
309 return None
310
311 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
312 if type_ == 'BOOL':
313 try:
314 value = cls._to_bool(value)
315 except ValueError as exc:
316 args = exc.args + ('on line {}: {}'.format(line_no, line),)
317 raise ValueError(args) from exc
Anas Nashifd9882382019-12-12 09:58:28 -0500318 elif type_ in ['STRING', 'INTERNAL']:
Anas Nashif45a97862019-01-09 08:46:42 -0500319 # If the value is a CMake list (i.e. is a string which
320 # contains a ';'), convert to a Python list.
321 if ';' in value:
322 value = value.split(';')
323
324 return CMakeCacheEntry(name, value)
325
326 def __init__(self, name, value):
327 self.name = name
328 self.value = value
329
330 def __str__(self):
331 fmt = 'CMakeCacheEntry(name={}, value={})'
332 return fmt.format(self.name, self.value)
333
334
335class CMakeCache:
336 '''Parses and represents a CMake cache file.'''
337
338 @staticmethod
339 def from_file(cache_file):
340 return CMakeCache(cache_file)
341
342 def __init__(self, cache_file):
343 self.cache_file = cache_file
344 self.load(cache_file)
345
346 def load(self, cache_file):
347 entries = []
348 with open(cache_file, 'r') as cache:
349 for line_no, line in enumerate(cache):
350 entry = CMakeCacheEntry.from_line(line, line_no)
351 if entry:
352 entries.append(entry)
353 self._entries = OrderedDict((e.name, e) for e in entries)
354
355 def get(self, name, default=None):
356 entry = self._entries.get(name)
357 if entry is not None:
358 return entry.value
359 else:
360 return default
361
362 def get_list(self, name, default=None):
363 if default is None:
364 default = []
365 entry = self._entries.get(name)
366 if entry is not None:
367 value = entry.value
368 if isinstance(value, list):
369 return value
370 elif isinstance(value, str):
371 return [value] if value else []
372 else:
373 msg = 'invalid value {} type {}'
374 raise RuntimeError(msg.format(value, type(value)))
375 else:
376 return default
377
378 def __contains__(self, name):
379 return name in self._entries
380
381 def __getitem__(self, name):
382 return self._entries[name].value
383
384 def __setitem__(self, name, entry):
385 if not isinstance(entry, CMakeCacheEntry):
386 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
387 raise TypeError(msg.format(type(entry), entry))
388 self._entries[name] = entry
389
390 def __delitem__(self, name):
391 del self._entries[name]
392
393 def __iter__(self):
394 return iter(self._entries.values())
395
Anas Nashif11ee5252019-12-04 12:59:10 -0500396
Andrew Boie6acbe632015-07-17 12:03:52 -0700397class SanityCheckException(Exception):
398 pass
399
Anas Nashif3ba1d432017-12-05 15:28:44 -0500400
Andrew Boie6acbe632015-07-17 12:03:52 -0700401class SanityRuntimeError(SanityCheckException):
402 pass
403
Anas Nashif11ee5252019-12-04 12:59:10 -0500404
Andrew Boie6acbe632015-07-17 12:03:52 -0700405class ConfigurationError(SanityCheckException):
406 def __init__(self, cfile, message):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400407 SanityCheckException.__init__(self, cfile + ": " + message)
Andrew Boie6acbe632015-07-17 12:03:52 -0700408
Anas Nashif11ee5252019-12-04 12:59:10 -0500409
Anas Nashif83fc06a2019-06-22 11:04:10 -0400410class BuildError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700411 pass
412
Anas Nashif3ba1d432017-12-05 15:28:44 -0500413
Anas Nashif83fc06a2019-06-22 11:04:10 -0400414class ExecutionError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700415 pass
416
Anas Nashif11ee5252019-12-04 12:59:10 -0500417
Anas Nashif576be982017-12-23 20:20:27 -0500418class HarnessImporter:
419
420 def __init__(self, name):
421 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
422 module = __import__("harness")
423 if name:
424 my_class = getattr(module, name)
425 else:
426 my_class = getattr(module, "Test")
427
428 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500429
Anas Nashif11ee5252019-12-04 12:59:10 -0500430
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300431class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400432 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300433 """Constructor
434
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300435 """
436 self.lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400437
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300438 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500439 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -0400440 self.duration = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400441 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300442
Anas Nashifdf7ee612018-07-07 06:09:01 -0500443 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100444 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500445 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500446
Anas Nashifd3384fb2018-02-22 06:44:16 -0600447 self.name = instance.name
448 self.instance = instance
Anas Nashif83fc06a2019-06-22 11:04:10 -0400449 self.timeout = instance.testcase.timeout
450 self.sourcedir = instance.testcase.source_dir
451 self.build_dir = instance.build_dir
452 self.log = os.path.join(self.build_dir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600453 self.returncode = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400454 self.set_state("running", self.duration)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600455
Anas Nashif83fc06a2019-06-22 11:04:10 -0400456 self.args = []
457
458 def set_state(self, state, duration):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300459 self.lock.acquire()
460 self.state = state
Anas Nashif83fc06a2019-06-22 11:04:10 -0400461 self.duration = duration
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300462 self.lock.release()
463
464 def get_state(self):
465 self.lock.acquire()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400466 ret = (self.state, self.duration)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300467 self.lock.release()
468 return ret
469
Anas Nashif83fc06a2019-06-22 11:04:10 -0400470 def record(self, harness):
471 if harness.recording:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -0500472 filename = os.path.join(self.build_dir, "recording.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -0400473 with open(filename, "at") as csvfile:
474 cw = csv.writer(csvfile, harness.fieldnames, lineterminator=os.linesep)
475 cw.writerow(harness.fieldnames)
476 for instance in harness.recording:
477 cw.writerow(instance)
478
Anas Nashif11ee5252019-12-04 12:59:10 -0500479
Anas Nashifdf7ee612018-07-07 06:09:01 -0500480class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400481 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500482 """Constructor
483
484 @param instance Test Instance
485 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400486 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500487
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100488 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500489
Anas Nashif6c0e1702019-12-05 15:24:52 -0500490 # Tool options
491 self.valgrind = False
492 self.lsan = False
493 self.asan = False
494 self.coverage = False
495
Jan Kowalewski265895b2019-01-07 16:40:24 +0100496 def try_kill_process_by_pid(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400497 if self.pid_fn:
Jan Kowalewski265895b2019-01-07 16:40:24 +0100498 pid = int(open(self.pid_fn).read())
499 os.unlink(self.pid_fn)
500 self.pid_fn = None # clear so we don't try to kill the binary twice
501 try:
502 os.kill(pid, signal.SIGTERM)
503 except ProcessLookupError:
504 pass
505
Kumar Gala34b1ef82019-12-12 04:38:42 -0600506 def terminate(self, proc):
507 # encapsulate terminate functionality so we do it consistently where ever
508 # we might want to terminate the proc. We need try_kill_process_by_pid
509 # because of both how newer ninja (1.6.0 or greater) and .NET / renode
510 # work. Newer ninja's don't seem to pass SIGTERM down to the children
511 # so we need to use try_kill_process_by_pid.
512 self.try_kill_process_by_pid()
513 proc.terminate()
514 self.terminated = True
515
Anas Nashifdf7ee612018-07-07 06:09:01 -0500516 def _output_reader(self, proc, harness):
517 log_out_fp = open(self.log, "wt")
518 for line in iter(proc.stdout.readline, b''):
Anas Nashif7a361b82019-12-06 11:37:40 -0500519 logger.debug("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
Anas Nashifdf7ee612018-07-07 06:09:01 -0500520 log_out_fp.write(line.decode('utf-8'))
521 log_out_fp.flush()
522 harness.handle(line.decode('utf-8').rstrip())
523 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100524 try:
Anas Nashifd9882382019-12-12 09:58:28 -0500525 # POSIX arch based ztests end on their own,
526 # so let's give it up to 100ms to do so
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100527 proc.wait(0.1)
528 except subprocess.TimeoutExpired:
Kumar Gala34b1ef82019-12-12 04:38:42 -0600529 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500530 break
531
532 log_out_fp.close()
533
534 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500535
Anas Nashif83fc06a2019-06-22 11:04:10 -0400536 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500537 harness_import = HarnessImporter(harness_name)
538 harness = harness_import.instance
539 harness.configure(self.instance)
540
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500541 if self.call_make_run:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400542 command = [get_generator()[0], "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500543 else:
544 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500545
Anas Nashifc1ea4522019-10-11 07:32:45 -0700546 run_valgrind = False
Anas Nashif6c0e1702019-12-05 15:24:52 -0500547 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500548 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100549 "--leak-check=full",
Anas Nashifd9882382019-12-12 09:58:28 -0500550 "--suppressions=" + ZEPHYR_BASE + "/scripts/valgrind.supp",
551 "--log-file=" + self.build_dir + "/valgrind.log"
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100552 ] + command
Anas Nashifc1ea4522019-10-11 07:32:45 -0700553 run_valgrind = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500554
Anas Nashif7a361b82019-12-06 11:37:40 -0500555 logger.debug("Spawning process: " +
Anas Nashifd9882382019-12-12 09:58:28 -0500556 " ".join(shlex.quote(word) for word in command) + os.linesep +
557 "in directory: " + self.build_dir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200558
Anas Nashif83fc06a2019-06-22 11:04:10 -0400559 start_time = time.time()
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200560
Anas Nashif89c83042019-11-05 05:55:39 -0800561 env = os.environ.copy()
Anas Nashif6c0e1702019-12-05 15:24:52 -0500562 if self.asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200563 env["ASAN_OPTIONS"] = "log_path=stdout:" + \
Anas Nashifd9882382019-12-12 09:58:28 -0500564 env.get("ASAN_OPTIONS", "")
Anas Nashif6c0e1702019-12-05 15:24:52 -0500565 if not self.lsan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200566 env["ASAN_OPTIONS"] += "detect_leaks=0"
567 with subprocess.Popen(command, stdout=subprocess.PIPE,
Anas Nashifd9882382019-12-12 09:58:28 -0500568 stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -0500569 logger.debug("Spawning BinaryHandler Thread for %s" % self.name)
Anas Nashifd9882382019-12-12 09:58:28 -0500570 t = threading.Thread(target=self._output_reader, args=(proc, harness,), daemon=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500571 t.start()
572 t.join(self.timeout)
573 if t.is_alive():
Kumar Gala34b1ef82019-12-12 04:38:42 -0600574 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500575 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500576 proc.wait()
577 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500578
Anas Nashif83fc06a2019-06-22 11:04:10 -0400579 handler_time = time.time() - start_time
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200580
Anas Nashif6c0e1702019-12-05 15:24:52 -0500581 if self.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400582 subprocess.call(["GCOV_PREFIX=" + self.build_dir,
Anas Nashifd9882382019-12-12 09:58:28 -0500583 "gcov", self.sourcedir, "-b", "-s", self.build_dir], shell=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500584
Jan Kowalewski265895b2019-01-07 16:40:24 +0100585 self.try_kill_process_by_pid()
586
Anas Nashif83fc06a2019-06-22 11:04:10 -0400587 # FIXME: This is needed when killing the simulator, the console is
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500588 # garbled and needs to be reset. Did not find a better way to do that.
589
590 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500591 self.instance.results = harness.tests
Anas Nashifc1ea4522019-10-11 07:32:45 -0700592
Anas Nashif83fc06a2019-06-22 11:04:10 -0400593 if not self.terminated and self.returncode != 0:
Anas Nashifd9882382019-12-12 09:58:28 -0500594 # When a process is killed, the default handler returns 128 + SIGTERM
595 # so in that case the return code itself is not meaningful
Anas Nashiff72d1902019-10-18 17:20:44 -0400596 self.set_state("failed", handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500597 self.instance.reason = "Failed"
Anas Nashifc1ea4522019-10-11 07:32:45 -0700598 elif run_valgrind and self.returncode == 2:
599 self.set_state("failed", handler_time)
600 self.instance.reason = "Valgrind error"
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100601 elif harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400602 self.set_state(harness.state, handler_time)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500603 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400604 self.set_state("timeout", handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500605 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400606
607 self.record(harness)
Anas Nashif73440ea2018-02-19 10:57:03 -0600608
Anas Nashif11ee5252019-12-04 12:59:10 -0500609
Anas Nashif73440ea2018-02-19 10:57:03 -0600610class DeviceHandler(Handler):
611
Anas Nashifd18ec532019-04-11 23:20:39 -0400612 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600613 """Constructor
614
615 @param instance Test Instance
616 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400617 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600618
Anas Nashif83fc06a2019-06-22 11:04:10 -0400619 self.suite = None
620
Marti Bolivar5591ca22019-02-07 15:53:39 -0700621 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500622 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600623
Marti Bolivar5591ca22019-02-07 15:53:39 -0700624 ser_fileno = ser.fileno()
625 readlist = [halt_fileno, ser_fileno]
626
Anas Nashif73440ea2018-02-19 10:57:03 -0600627 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700628 readable, _, _ = select.select(readlist, [], [], self.timeout)
629
630 if halt_fileno in readable:
Anas Nashif7a361b82019-12-06 11:37:40 -0500631 logger.debug('halted')
Marti Bolivar5591ca22019-02-07 15:53:39 -0700632 ser.close()
633 break
634 if ser_fileno not in readable:
Anas Nashifd9882382019-12-12 09:58:28 -0500635 continue # Timeout.
Marti Bolivar5591ca22019-02-07 15:53:39 -0700636
637 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500638 try:
639 serial_line = ser.readline()
640 except TypeError:
641 pass
Anas Nashif83fc06a2019-06-22 11:04:10 -0400642 except serial.SerialException:
Anas Nashif59237602019-03-03 10:36:35 -0500643 ser.close()
644 break
Anas Nashif61e21632018-04-08 13:30:16 -0500645
Marti Bolivar5591ca22019-02-07 15:53:39 -0700646 # Just because ser_fileno has data doesn't mean an entire line
647 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600648 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600649 sl = serial_line.decode('utf-8', 'ignore')
Anas Nashif7a361b82019-12-06 11:37:40 -0500650 logger.debug("DEVICE: {0}".format(sl.rstrip()))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600651
652 log_out_fp.write(sl)
653 log_out_fp.flush()
654 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700655
Anas Nashif73440ea2018-02-19 10:57:03 -0600656 if harness.state:
657 ser.close()
658 break
659
660 log_out_fp.close()
661
Anas Nashif83fc06a2019-06-22 11:04:10 -0400662 def device_is_available(self, device):
663 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500664 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400665 return True
666
667 return False
668
669 def get_available_device(self, device):
670 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500671 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400672 i['available'] = False
673 i['counter'] += 1
674 return i
675
676 return None
677
678 def make_device_available(self, serial):
679 with hw_map_local:
680 for i in self.suite.connected_hardware:
681 if i['serial'] == serial:
682 i['available'] = True
683
Anas Nashif73440ea2018-02-19 10:57:03 -0600684 def handle(self):
685 out_state = "failed"
686
Anas Nashif83fc06a2019-06-22 11:04:10 -0400687 if options.west_flash:
688 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
689 if options.west_runner:
Michael Scott421ce462019-06-18 09:37:46 -0700690 command.append("--runner")
691 command.append(options.west_runner)
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200692 # There are three ways this option is used.
Andy Doan79c48842019-02-08 10:09:04 -0600693 # 1) bare: --west-flash
694 # This results in options.west_flash == []
695 # 2) with a value: --west-flash="--board-id=42"
696 # This results in options.west_flash == "--board-id=42"
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200697 # 3) Multiple values: --west-flash="--board-id=42,--erase"
698 # This results in options.west_flash == "--board-id=42 --erase"
Andy Doan79c48842019-02-08 10:09:04 -0600699 if options.west_flash != []:
700 command.append('--')
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200701 command.extend(options.west_flash.split(','))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600702 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400703 command = [get_generator()[0], "-C", self.build_dir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600704
Anas Nashif83fc06a2019-06-22 11:04:10 -0400705 while not self.device_is_available(self.instance.platform.name):
706 time.sleep(1)
707
708 hardware = self.get_available_device(self.instance.platform.name)
709
710 runner = hardware.get('runner', None)
711 if runner:
Peter Bigotda738482019-11-21 11:55:26 -0600712 board_id = hardware.get("probe_id", hardware.get("id", None))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400713 product = hardware.get("product", None)
714 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
715 command.append("--runner")
716 command.append(hardware.get('runner', None))
717 if runner == "pyocd":
718 command.append("--board-id")
719 command.append(board_id)
720 elif runner == "nrfjprog":
721 command.append('--')
722 command.append("--snr")
723 command.append(board_id)
724 elif runner == "openocd" and product == "STM32 STLink":
725 command.append('--')
726 command.append("--cmd-pre-init")
Anas Nashifd9882382019-12-12 09:58:28 -0500727 command.append("hla_serial %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400728 elif runner == "openocd" and product == "EDBG CMSIS-DAP":
729 command.append('--')
730 command.append("--cmd-pre-init")
Anas Nashifd9882382019-12-12 09:58:28 -0500731 command.append("cmsis_dap_serial %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400732 elif runner == "jlink":
Anas Nashifd9882382019-12-12 09:58:28 -0500733 command.append("--tool-opt=-SelectEmuBySN %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400734
735 serial_device = hardware['serial']
736
737 try:
738 ser = serial.Serial(
Anas Nashifd9882382019-12-12 09:58:28 -0500739 serial_device,
740 baudrate=115200,
741 parity=serial.PARITY_NONE,
742 stopbits=serial.STOPBITS_ONE,
743 bytesize=serial.EIGHTBITS,
744 timeout=self.timeout
745 )
Anas Nashif83fc06a2019-06-22 11:04:10 -0400746 except serial.SerialException as e:
747 self.set_state("failed", 0)
Anas Nashif17d066b2019-12-17 14:37:16 -0500748 self.instance.reason = "Failed"
Anas Nashifd9882382019-12-12 09:58:28 -0500749 logger.error("Serial device error: %s" % (str(e)))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400750 self.make_device_available(serial_device)
751 return
Anas Nashif73440ea2018-02-19 10:57:03 -0600752
753 ser.flush()
754
Anas Nashif83fc06a2019-06-22 11:04:10 -0400755 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashif73440ea2018-02-19 10:57:03 -0600756 harness_import = HarnessImporter(harness_name)
757 harness = harness_import.instance
758 harness.configure(self.instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400759 read_pipe, write_pipe = os.pipe()
760 start_time = time.time()
Anas Nashif73440ea2018-02-19 10:57:03 -0600761
Marti Bolivar5591ca22019-02-07 15:53:39 -0700762 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashifd9882382019-12-12 09:58:28 -0500763 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600764 t.start()
765
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500766 d_log = "{}/device.log".format(self.instance.build_dir)
Anas Nashif7a361b82019-12-06 11:37:40 -0500767 logger.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500768 try:
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500769 stdout = stderr = None
770 with subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
771 try:
772 (stdout, stderr) = proc.communicate(timeout=30)
Anas Nashiffa8085e2019-12-09 16:42:58 -0500773 logger.debug(stdout.decode())
774
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500775 if proc.returncode != 0:
776 self.instance.reason = "Device issue (Flash?)"
777 with open(d_log, "w") as dlog_fp:
778 dlog_fp.write(stderr.decode())
779 except subprocess.TimeoutExpired:
780 proc.kill()
Anas Nashifd9882382019-12-12 09:58:28 -0500781 (stdout, stderr) = proc.communicate()
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500782 self.instance.reason = "Device issue (Timeout)"
783
784 with open(d_log, "w") as dlog_fp:
785 dlog_fp.write(stderr.decode())
Anas Nashif83fc06a2019-06-22 11:04:10 -0400786
Anas Nashif61e21632018-04-08 13:30:16 -0500787 except subprocess.CalledProcessError:
Anas Nashifd9882382019-12-12 09:58:28 -0500788 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600789
790 t.join(self.timeout)
791 if t.is_alive():
792 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600793
794 if ser.isOpen():
795 ser.close()
796
Anas Nashif17d066b2019-12-17 14:37:16 -0500797 handler_time = time.time() - start_time
798
Anas Nashifd3384fb2018-02-22 06:44:16 -0600799 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400800 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600801 if c not in harness.tests:
802 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500803
Anas Nashif17d066b2019-12-17 14:37:16 -0500804 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400805
Anas Nashif61e21632018-04-08 13:30:16 -0500806 self.instance.results = harness.tests
Anas Nashif17d066b2019-12-17 14:37:16 -0500807
Anas Nashif73440ea2018-02-19 10:57:03 -0600808 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400809 self.set_state(harness.state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500810 if harness.state == "failed":
811 self.instance.reason = "Failed"
Anas Nashif73440ea2018-02-19 10:57:03 -0600812 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400813 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600814
Anas Nashif83fc06a2019-06-22 11:04:10 -0400815 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500816
Anas Nashif3a00d0c2019-11-12 11:07:15 -0500817 self.record(harness)
818
Anas Nashif11ee5252019-12-04 12:59:10 -0500819
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300820class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700821 """Spawns a thread to monitor QEMU output from pipes
822
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400823 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700824 We need to do this as once qemu starts, it runs forever until killed.
825 Test cases emit special messages to the console as they run, we check
826 for these to collect whether the test passed or failed.
827 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700828
Anas Nashif83fc06a2019-06-22 11:04:10 -0400829 def __init__(self, instance, type_str):
830 """Constructor
831
832 @param instance Test instance
833 """
834
835 super().__init__(instance, type_str)
836 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
837
838 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
839
Andrew Boie6acbe632015-07-17 12:03:52 -0700840 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500841 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700842 fifo_in = fifo_fn + ".in"
843 fifo_out = fifo_fn + ".out"
844
845 # These in/out nodes are named from QEMU's perspective, not ours
846 if os.path.exists(fifo_in):
847 os.unlink(fifo_in)
848 os.mkfifo(fifo_in)
849 if os.path.exists(fifo_out):
850 os.unlink(fifo_out)
851 os.mkfifo(fifo_out)
852
853 # We don't do anything with out_fp but we need to open it for
854 # writing so that QEMU doesn't block, due to the way pipes work
855 out_fp = open(fifo_in, "wb")
856 # Disable internal buffering, we don't
857 # want read() or poll() to ever block if there is data in there
858 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800859 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700860
861 start_time = time.time()
862 timeout_time = start_time + timeout
863 p = select.poll()
864 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400865 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700866
Andrew Boie6acbe632015-07-17 12:03:52 -0700867 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500868 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700869 while True:
870 this_timeout = int((timeout_time - time.time()) * 1000)
871 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400872 if not out_state:
873 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700874 break
875
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500876 try:
877 c = in_fp.read(1).decode("utf-8")
878 except UnicodeDecodeError:
879 # Test is writing something weird, fail
880 out_state = "unexpected byte"
881 break
882
Andrew Boie6acbe632015-07-17 12:03:52 -0700883 if c == "":
884 # EOF, this shouldn't happen unless QEMU crashes
885 out_state = "unexpected eof"
886 break
887 line = line + c
888 if c != "\n":
889 continue
890
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300891 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700892 log_out_fp.write(line)
893 log_out_fp.flush()
894 line = line.strip()
Anas Nashif7a361b82019-12-06 11:37:40 -0500895 logger.debug("QEMU: %s" % line)
Andrew Boie6acbe632015-07-17 12:03:52 -0700896
Anas Nashif576be982017-12-23 20:20:27 -0500897 harness.handle(line)
898 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400899 # if we have registered a fail make sure the state is not
900 # overridden by a false success message coming from the
901 # testsuite
902 if out_state != 'failed':
903 out_state = harness.state
904
905 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700906 # the timeout and wait for 2 more seconds to catch anything
907 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700908 # coverage is enabled since dumping this information can
909 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500910 if not timeout_extended or harness.capture_coverage:
Anas Nashifd9882382019-12-12 09:58:28 -0500911 timeout_extended = True
Anas Nashiff29087e2019-01-25 09:37:38 -0500912 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700913 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500914 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500915 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700916 line = ""
917
Anas Nashif83fc06a2019-06-22 11:04:10 -0400918 handler.record(harness)
919
920 handler_time = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -0500921 logger.debug("QEMU complete (%s) after %f seconds" %
Anas Nashifd9882382019-12-12 09:58:28 -0500922 (out_state, handler_time))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400923 handler.set_state(out_state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500924 if out_state == "timeout":
925 handler.instance.reason = "Timeout"
926 elif out_state == "failed":
927 handler.instance.reason = "Failed"
Andrew Boie6acbe632015-07-17 12:03:52 -0700928
929 log_out_fp.close()
930 out_fp.close()
931 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400932 if os.path.exists(pid_fn):
933 pid = int(open(pid_fn).read())
934 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700935
Anas Nashifd6476ee2019-04-11 11:40:09 -0400936 try:
937 if pid:
938 os.kill(pid, signal.SIGTERM)
939 except ProcessLookupError:
940 # Oh well, as long as it's dead! User probably sent Ctrl-C
941 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800942
Andrew Boie6acbe632015-07-17 12:03:52 -0700943 os.unlink(fifo_in)
944 os.unlink(fifo_out)
945
Anas Nashif83fc06a2019-06-22 11:04:10 -0400946 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700947 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500948 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700949
950 # We pass this to QEMU which looks for fifos with .in and .out
951 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400952 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700953
Anas Nashif83fc06a2019-06-22 11:04:10 -0400954 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700955 if os.path.exists(self.pid_fn):
956 os.unlink(self.pid_fn)
957
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500958 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500959
Anas Nashif83fc06a2019-06-22 11:04:10 -0400960 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500961 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600962 harness.configure(self.instance)
963 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400964 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300965 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500966 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600967
968 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700969 self.thread.daemon = True
Anas Nashif7a361b82019-12-06 11:37:40 -0500970 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700971 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400972 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700973
Anas Nashifd9882382019-12-12 09:58:28 -0500974 logger.debug("Running %s (%s)" % (self.name, self.type_str))
Stephanos Ioannidise1827c02019-11-12 14:11:31 +0900975 command = [get_generator()[0]]
976 command += ["-C", self.build_dir, "run"]
977
978 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -0500979 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Stephanos Ioannidise1827c02019-11-12 14:11:31 +0900980 proc.wait()
981 self.returncode = proc.returncode
982
983 if self.returncode != 0:
984 self.set_state("failed", 0)
985 self.instance.reason = "Exited with {}".format(self.returncode)
986
Andrew Boie6acbe632015-07-17 12:03:52 -0700987 def get_fifo(self):
988 return self.fifo_fn
989
Anas Nashif11ee5252019-12-04 12:59:10 -0500990
Andrew Boie6acbe632015-07-17 12:03:52 -0700991class SizeCalculator:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400992 alloc_sections = [
993 "bss",
994 "noinit",
995 "app_bss",
996 "app_noinit",
997 "ccm_bss",
998 "ccm_noinit"
Anas Nashifd9882382019-12-12 09:58:28 -0500999 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001000
1001 rw_sections = [
1002 "datas",
1003 "initlevel",
1004 "exceptions",
1005 "initshell",
1006 "_static_thread_area",
1007 "_k_timer_area",
1008 "_k_mem_slab_area",
1009 "_k_mem_pool_area",
1010 "sw_isr_table",
1011 "_k_sem_area",
1012 "_k_mutex_area",
1013 "app_shmem_regions",
1014 "_k_fifo_area",
1015 "_k_lifo_area",
1016 "_k_stack_area",
1017 "_k_msgq_area",
1018 "_k_mbox_area",
1019 "_k_pipe_area",
1020 "net_if",
1021 "net_if_dev",
1022 "net_stack",
1023 "net_l2_data",
1024 "_k_queue_area",
1025 "_net_buf_pool_area",
1026 "app_datas",
1027 "kobject_data",
1028 "mmu_tables",
1029 "app_pad",
1030 "priv_stacks",
1031 "ccm_data",
1032 "usb_descriptor",
1033 "usb_data", "usb_bos_desc",
1034 'log_backends_sections',
1035 'log_dynamic_sections',
1036 'log_const_sections',
1037 "app_smem",
1038 'shell_root_cmds_sections',
1039 'log_const_sections',
1040 "font_entry_sections",
1041 "priv_stacks_noinit",
1042 "_TEXT_SECTION_NAME_2",
1043 "_GCOV_BSS_SECTION_NAME",
1044 "gcov",
1045 "nocache"
Anas Nashifd9882382019-12-12 09:58:28 -05001046 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001047
Andrew Boie73b4ee62015-10-07 11:33:22 -07001048 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001049 ro_sections = [
1050 "text",
1051 "ctors",
1052 "init_array",
1053 "reset",
1054 "object_access",
1055 "rodata",
1056 "devconfig",
1057 "net_l2",
1058 "vector",
1059 "sw_isr_table",
1060 "_settings_handlers_area",
1061 "_bt_channels_area",
1062 "_bt_br_channels_area",
1063 "_bt_services_area",
1064 "vectors",
1065 "net_socket_register",
1066 "net_ppp_proto"
Anas Nashifd9882382019-12-12 09:58:28 -05001067 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001068
Andrew Boie52fef672016-11-29 12:21:59 -08001069 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001070 """Constructor
1071
Andrew Boiebbd670c2015-08-17 13:16:11 -07001072 @param filename Path to the output binary
1073 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001074 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001075 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001076 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001077 magic = f.read(4)
1078
Anas Nashifb4bdd662018-08-15 17:12:28 -05001079 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001080 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001081 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1082 except Exception as e:
1083 print(str(e))
1084 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001085
1086 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001087 # GREP can not be used as it returns an error if the symbol is not
1088 # found.
1089 is_xip_command = "nm " + filename + \
Anas Nashifd9882382019-12-12 09:58:28 -05001090 " | awk '/CONFIG_XIP/ { print $3 }'"
Anas Nashif3ba1d432017-12-05 15:28:44 -05001091 is_xip_output = subprocess.check_output(
1092 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1093 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001094 try:
1095 if is_xip_output.endswith("no symbols"):
1096 raise SanityRuntimeError("%s has no symbol information" % filename)
1097 except Exception as e:
1098 print(str(e))
1099 sys.exit(2)
1100
Andrew Boie6acbe632015-07-17 12:03:52 -07001101 self.is_xip = (len(is_xip_output) != 0)
1102
Andrew Boiebbd670c2015-08-17 13:16:11 -07001103 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001104 self.sections = []
1105 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001106 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001107 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001108
1109 self._calculate_sizes()
1110
1111 def get_ram_size(self):
1112 """Get the amount of RAM the application will use up on the device
1113
1114 @return amount of RAM, in bytes
1115 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001116 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001117
1118 def get_rom_size(self):
1119 """Get the size of the data that this application uses on device's flash
1120
1121 @return amount of ROM, in bytes
1122 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001123 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001124
1125 def unrecognized_sections(self):
1126 """Get a list of sections inside the binary that weren't recognized
1127
David B. Kinder29963c32017-06-16 12:32:42 -07001128 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001129 """
1130 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001131 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001132 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001133 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001134 return slist
1135
1136 def _calculate_sizes(self):
1137 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001138 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001139 objdump_output = subprocess.check_output(
1140 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001141
1142 for line in objdump_output:
1143 words = line.split()
1144
Anas Nashifd9882382019-12-12 09:58:28 -05001145 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001146 continue
1147
1148 index = words[0]
Anas Nashifd9882382019-12-12 09:58:28 -05001149 if not index[0].isdigit(): # Skip lines that do not start
1150 continue # with a digit
Andrew Boie6acbe632015-07-17 12:03:52 -07001151
Anas Nashifd9882382019-12-12 09:58:28 -05001152 name = words[1] # Skip lines with section names
1153 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001154 continue
1155
Andrew Boie73b4ee62015-10-07 11:33:22 -07001156 # TODO this doesn't actually reflect the size in flash or RAM as
1157 # it doesn't include linker-imposed padding between sections.
1158 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001159 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001160 if size == 0:
1161 continue
1162
Andrew Boie73b4ee62015-10-07 11:33:22 -07001163 load_addr = int(words[4], 16)
1164 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001165
1166 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001167 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001169 if name in SizeCalculator.alloc_sections:
1170 self.ram_size += size
1171 stype = "alloc"
1172 elif name in SizeCalculator.rw_sections:
1173 self.ram_size += size
1174 self.rom_size += size
1175 stype = "rw"
1176 elif name in SizeCalculator.ro_sections:
1177 self.rom_size += size
1178 if not self.is_xip:
1179 self.ram_size += size
1180 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001181 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001182 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001183 if name not in self.extra_sections:
1184 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001185
Anas Nashif3ba1d432017-12-05 15:28:44 -05001186 self.sections.append({"name": name, "load_addr": load_addr,
1187 "size": size, "virt_addr": virt_addr,
1188 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001189
1190
Andrew Boie6acbe632015-07-17 12:03:52 -07001191# "list" - List of strings
1192# "list:<type>" - List of <type>
1193# "set" - Set of unordered, unique strings
1194# "set:<type>" - Set of <type>
1195# "float" - Floating point
1196# "int" - Integer
1197# "bool" - Boolean
1198# "str" - String
1199
1200# XXX Be sure to update __doc__ if you change any of this!!
1201
Anas Nashif83fc06a2019-06-22 11:04:10 -04001202platform_valid_keys = {
Anas Nashifd9882382019-12-12 09:58:28 -05001203 "supported_toolchains": {"type": "list", "default": []},
1204 "env": {"type": "list", "default": []}
1205}
Andrew Boie6acbe632015-07-17 12:03:52 -07001206
Anas Nashif3ba1d432017-12-05 15:28:44 -05001207testcase_valid_keys = {"tags": {"type": "set", "required": False},
1208 "type": {"type": "str", "default": "integration"},
1209 "extra_args": {"type": "list"},
1210 "extra_configs": {"type": "list"},
1211 "build_only": {"type": "bool", "default": False},
1212 "build_on_all": {"type": "bool", "default": False},
1213 "skip": {"type": "bool", "default": False},
1214 "slow": {"type": "bool", "default": False},
1215 "timeout": {"type": "int", "default": 60},
1216 "min_ram": {"type": "int", "default": 8},
1217 "depends_on": {"type": "set"},
1218 "min_flash": {"type": "int", "default": 32},
1219 "arch_whitelist": {"type": "set"},
1220 "arch_exclude": {"type": "set"},
1221 "extra_sections": {"type": "list", "default": []},
1222 "platform_exclude": {"type": "set"},
1223 "platform_whitelist": {"type": "set"},
1224 "toolchain_exclude": {"type": "set"},
1225 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001226 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001227 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301228 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001229 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001230
Anas Nashif11ee5252019-12-04 12:59:10 -05001231
Andrew Boie6acbe632015-07-17 12:03:52 -07001232class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001233 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001234 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001235
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001236 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001237 """Instantiate a new SanityConfigParser object
1238
Anas Nashifa792a3d2017-04-04 18:47:49 -04001239 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001240 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001241 self.data = {}
1242 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001243 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001244 self.tests = {}
1245 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001246
1247 def load(self):
1248 self.data = scl.yaml_load_verify(self.filename, self.schema)
1249
Anas Nashif255625b2017-12-05 15:08:26 -05001250 if 'tests' in self.data:
1251 self.tests = self.data['tests']
1252 if 'common' in self.data:
1253 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001254
1255 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001256 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001257 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001258 if typestr == "str":
1259 return v
1260
1261 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001262 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001263
1264 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001265 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001266
1267 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001268 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001269
Anas Nashif3ba1d432017-12-05 15:28:44 -05001270 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001271 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001272 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 vs = v.split()
1274 if len(typestr) > 4 and typestr[4] == ":":
1275 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1276 else:
1277 return vs
1278
1279 elif typestr.startswith("set"):
1280 vs = v.split()
1281 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001282 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001283 else:
1284 return set(vs)
1285
Anas Nashif576be982017-12-23 20:20:27 -05001286 elif typestr.startswith("map"):
1287 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001288 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001289 raise ConfigurationError(
1290 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001291
Anas Nashifb4754ed2017-12-05 17:27:58 -05001292 def get_test(self, name, valid_keys):
1293 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001294
Anas Nashifb4754ed2017-12-05 17:27:58 -05001295 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001296 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001297 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001298 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001299 here, it will generate an error. Each value in this dictionary
1300 is another dictionary containing metadata:
1301
1302 "default" - Default value if not given
1303 "type" - Data type to convert the text value to. Simple types
1304 supported are "str", "float", "int", "bool" which will get
1305 converted to respective Python data types. "set" and "list"
1306 may also be specified which will split the value by
1307 whitespace (but keep the elements as strings). finally,
1308 "list:<type>" and "set:<type>" may be given which will
1309 perform a type conversion after splitting the value up.
1310 "required" - If true, raise an error if not defined. If false
1311 and "default" isn't specified, a type conversion will be
1312 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001313 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001314 type conversion and default values filled in per valid_keys
1315 """
1316
1317 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001318 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001319 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001320
Anas Nashifb4754ed2017-12-05 17:27:58 -05001321 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001322 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001323 raise ConfigurationError(
1324 self.filename,
1325 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001326 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001327
Anas Nashiffa695d22017-10-04 16:14:27 -04001328 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001329 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001330 # By default, we just concatenate string values of keys
1331 # which appear both in "common" and per-test sections,
1332 # but some keys are handled in adhoc way based on their
1333 # semantics.
1334 if k == "filter":
1335 d[k] = "(%s) and (%s)" % (d[k], v)
1336 else:
1337 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001338 else:
1339 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001340
Andrew Boie08ce5a52016-02-22 13:28:10 -08001341 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001342 if k not in d:
1343 if "required" in kinfo:
1344 required = kinfo["required"]
1345 else:
1346 required = False
1347
1348 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001349 raise ConfigurationError(
1350 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001351 "missing required value for '%s' in test '%s'" %
1352 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001353 else:
1354 if "default" in kinfo:
1355 default = kinfo["default"]
1356 else:
1357 default = self._cast_value("", kinfo["type"])
1358 d[k] = default
1359 else:
1360 try:
1361 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001362 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001363 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001364 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
Anas Nashifd9882382019-12-12 09:58:28 -05001365 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001366
1367 return d
1368
1369
1370class Platform:
1371 """Class representing metadata for a particular platform
1372
Anas Nashifc7406082015-12-13 15:00:31 -05001373 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001374
Anas Nashif83fc06a2019-06-22 11:04:10 -04001375 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
Anas Nashifd9882382019-12-12 09:58:28 -05001376 "scripts", "sanity_chk", "platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001377
Anas Nashif83fc06a2019-06-22 11:04:10 -04001378 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001379 """Constructor.
1380
Andrew Boie6acbe632015-07-17 12:03:52 -07001381 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001382
1383 self.name = ""
1384 self.sanitycheck = True
1385 # if no RAM size is specified by the board, take a default of 128K
1386 self.ram = 128
1387
1388 self.ignore_tags = []
1389 self.default = False
1390 # if no flash size is specified by the board, take a default of 512K
1391 self.flash = 512
1392 self.supported = set()
1393
1394 self.arch = ""
1395 self.type = "na"
1396 self.simulation = "na"
1397 self.supported_toolchains = []
1398 self.env = []
1399 self.env_satisfied = True
1400 self.filter_data = dict()
1401
1402 def load(self, platform_file):
1403 scp = SanityConfigParser(platform_file, self.platform_schema)
1404 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001405 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001406
Anas Nashif255625b2017-12-05 15:08:26 -05001407 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001408 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001409 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001410 self.ram = data.get("ram", 128)
1411 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001412 self.ignore_tags = testing.get("ignore_tags", [])
1413 self.default = testing.get("default", False)
1414 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001415 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001416 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001417 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001418 for item in supp_feature.split(":"):
1419 self.supported.add(item)
1420
Anas Nashif255625b2017-12-05 15:08:26 -05001421 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001422 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001423 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001424 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001425 self.env = data.get("env", [])
1426 self.env_satisfied = True
1427 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001428 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001429 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001430
Andrew Boie6acbe632015-07-17 12:03:52 -07001431 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001432 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001433
Anas Nashif11ee5252019-12-04 12:59:10 -05001434
Anas Nashif83fc06a2019-06-22 11:04:10 -04001435class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001436 """Class representing a test application
1437 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001438
Anas Nashif83fc06a2019-06-22 11:04:10 -04001439 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001440 """TestCase constructor.
1441
Anas Nashif877d3ca2017-12-05 17:39:29 -05001442 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001443 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001444 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001445
Andrew Boie6acbe632015-07-17 12:03:52 -07001446 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001447 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001448 the test case is <workdir>/<name>.
1449
Marc Herbert1c8632c2019-04-15 17:58:45 -07001450 @param testcase_root os.path.abspath() of one of the --testcase-root
1451 @param workdir Sub-directory of testcase_root where the
1452 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001453 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001454 in the test case configuration file. For many test cases that just
1455 define one test, can be anything and is usually "test". This is
1456 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001457 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001458 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001459 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001460 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001461
Anas Nashif83fc06a2019-06-22 11:04:10 -04001462 self.id = ""
1463 self.source_dir = ""
1464 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001465 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001466 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001467
Anas Nashif83fc06a2019-06-22 11:04:10 -04001468 self.type = None
1469 self.tags = None
1470 self.extra_args = None
1471 self.extra_configs = None
1472 self.arch_whitelist = None
1473 self.arch_exclude = None
1474 self.skip = None
1475 self.platform_exclude = None
1476 self.platform_whitelist = None
1477 self.toolchain_exclude = None
1478 self.toolchain_whitelist = None
1479 self.tc_filter = None
1480 self.timeout = 60
1481 self.harness = ""
1482 self.harness_config = {}
1483 self.build_only = True
1484 self.build_on_all = False
1485 self.slow = False
1486 self.min_ram = None
1487 self.depends_on = None
1488 self.min_flash = None
1489 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001490
Anas Nashif83fc06a2019-06-22 11:04:10 -04001491 @staticmethod
1492 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001493
Marc Herbert1c8632c2019-04-15 17:58:45 -07001494 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001495 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001496 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001497 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001498 relative_tc_root = os.path.relpath(canonical_testcase_root,
Anas Nashif11ee5252019-12-04 12:59:10 -05001499 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001500 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001501 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001502
Marc Herbert1c8632c2019-04-15 17:58:45 -07001503 # workdir can be "."
1504 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001505 return unique
1506
Anas Nashif83fc06a2019-06-22 11:04:10 -04001507 @staticmethod
1508 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001509 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001510 # do not match until end-of-line, otherwise we won't allow
1511 # stc_regex below to catch the ones that are declared in the same
1512 # line--as we only search starting the end of this match
1513 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001514 re.MULTILINE)
1515 stc_regex = re.compile(
Anas Nashifd9882382019-12-12 09:58:28 -05001516 br"^\s*" # empy space at the beginning is ok
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001517 # catch the case where it is declared in the same sentence, e.g:
1518 #
1519 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1520 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1521 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
Anas Nashif9091a012019-11-24 09:22:22 -05001522 br"ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?"
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001523 # Consume the argument that becomes the extra testcse
1524 br"\(\s*"
1525 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1526 # _setup_teardown() variant has two extra arguments that we ignore
1527 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1528 br"\s*\)",
1529 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001530 re.MULTILINE)
1531 suite_run_regex = re.compile(
1532 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1533 re.MULTILINE)
1534 achtung_regex = re.compile(
1535 br"(#ifdef|#endif)",
1536 re.MULTILINE)
1537 warnings = None
1538
1539 with open(inf_name) as inf:
Anas Nashif19d67e42019-11-21 11:33:12 -05001540 if os.name == 'nt':
Anas Nashifd9882382019-12-12 09:58:28 -05001541 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
Anas Nashif19d67e42019-11-21 11:33:12 -05001542 else:
Anas Nashifd9882382019-12-12 09:58:28 -05001543 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
1544 'offset': 0}
Anas Nashif19d67e42019-11-21 11:33:12 -05001545
1546 with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001547 # contextlib makes pylint think main_c isn't subscriptable
1548 # pylint: disable=unsubscriptable-object
1549
Anas Nashifaae71d72018-04-21 22:26:48 -05001550 suite_regex_match = suite_regex.search(main_c)
1551 if not suite_regex_match:
1552 # can't find ztest_test_suite, maybe a client, because
1553 # it includes ztest.h
1554 return None, None
1555
1556 suite_run_match = suite_run_regex.search(main_c)
1557 if not suite_run_match:
1558 raise ValueError("can't find ztest_run_test_suite")
1559
1560 achtung_matches = re.findall(
1561 achtung_regex,
1562 main_c[suite_regex_match.end():suite_run_match.start()])
1563 if achtung_matches:
1564 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001565 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001566 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001567 stc_regex,
1568 main_c[suite_regex_match.end():suite_run_match.start()])
Anas Nashifd9882382019-12-12 09:58:28 -05001569 matches = [match.decode().replace("test_", "") for match in _matches]
Anas Nashifaae71d72018-04-21 22:26:48 -05001570 return matches, warnings
1571
1572 def scan_path(self, path):
1573 subcases = []
1574 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1575 try:
1576 _subcases, warnings = self.scan_file(filename)
1577 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001578 logger.error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001579 if _subcases:
1580 subcases += _subcases
1581 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001582 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashif434995c2019-12-01 13:55:11 -05001583 for filename in glob.glob(os.path.join(path, "*.c")):
1584 try:
1585 _subcases, warnings = self.scan_file(filename)
1586 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001587 logger.error("%s: %s" % (filename, warnings))
Anas Nashif434995c2019-12-01 13:55:11 -05001588 if _subcases:
1589 subcases += _subcases
1590 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001591 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001592 return subcases
1593
Anas Nashif83fc06a2019-06-22 11:04:10 -04001594 def parse_subcases(self, test_path):
Anas Nashif9091a012019-11-24 09:22:22 -05001595 results = self.scan_path(test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001596 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001597 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001598 self.cases.append(name)
1599
Anas Nashiff16e92c2019-03-31 16:58:12 -04001600 if not results:
1601 self.cases.append(self.id)
1602
Anas Nashif75547e22018-02-24 08:32:14 -06001603 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001604 return self.name
1605
1606
Andrew Boie6acbe632015-07-17 12:03:52 -07001607class TestInstance:
1608 """Class representing the execution of a particular TestCase on a platform
1609
1610 @param test The TestCase object we want to build/execute
1611 @param platform Platform object that we want to build and run against
1612 @param base_outdir Base directory for all test results. The actual
1613 out directory used is <outdir>/<platform>/<test case name>
1614 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001615
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001616 def __init__(self, testcase, platform, outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001617
Anas Nashif83fc06a2019-06-22 11:04:10 -04001618 self.testcase = testcase
1619 self.platform = platform
1620
1621 self.status = None
1622 self.reason = "N/A"
1623 self.metrics = dict()
1624 self.handler = None
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001625 self.outdir = outdir
Anas Nashif83fc06a2019-06-22 11:04:10 -04001626
1627 self.name = os.path.join(platform.name, testcase.name)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001628 self.build_dir = os.path.join(outdir, platform.name, testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001629
Anas Nashif56656842019-12-10 12:26:00 -05001630 self.build_only = True
1631 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001632
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001633 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001634
Marc Herbert0f7255c2019-04-05 14:14:21 -07001635 def __lt__(self, other):
1636 return self.name < other.name
1637
Anas Nashif56656842019-12-10 12:26:00 -05001638 def check_build_or_run(self, build_only=False, enable_slow=False, device_testing=False, fixture=[]):
1639
Anas Nashif19d67e42019-11-21 11:33:12 -05001640 # right now we only support building on windows. running is still work
1641 # in progress.
Anas Nashif19d67e42019-11-21 11:33:12 -05001642 if os.name == 'nt':
Anas Nashif56656842019-12-10 12:26:00 -05001643 self.build_only = True
1644 self.run = False
1645 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001646
Anas Nashif56656842019-12-10 12:26:00 -05001647 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001648
1649 # we asked for build-only on the command line
Anas Nashif56656842019-12-10 12:26:00 -05001650 if build_only or self.testcase.build_only:
1651 self.build_only = True
1652 self.run = False
1653 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001654
1655 # Do not run slow tests:
Anas Nashif56656842019-12-10 12:26:00 -05001656 skip_slow = self.testcase.slow and not enable_slow
Anas Nashif83fc06a2019-06-22 11:04:10 -04001657 if skip_slow:
Anas Nashif56656842019-12-10 12:26:00 -05001658 self.build_only = True
1659 self.run = False
1660 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001661
Anas Nashifd9882382019-12-12 09:58:28 -05001662 runnable = bool(self.testcase.type == "unit" or \
1663 self.platform.type == "native" or \
1664 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1665 device_testing)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001666
1667 if self.platform.simulation == "nsim":
1668 if not find_executable("nsimdrv"):
1669 runnable = False
1670
1671 if self.platform.simulation == "renode":
1672 if not find_executable("renode"):
1673 runnable = False
1674
1675 # console harness allows us to run the test and capture data.
1676 if self.testcase.harness == 'console':
1677
1678 # if we have a fixture that is also being supplied on the
1679 # command-line, then we need to run the test, not just build it.
1680 if "fixture" in self.testcase.harness_config:
Anas Nashifd9882382019-12-12 09:58:28 -05001681 fixture_cfg = self.testcase.harness_config['fixture']
1682 if fixture_cfg in fixture:
Anas Nashif56656842019-12-10 12:26:00 -05001683 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001684 else:
Anas Nashif56656842019-12-10 12:26:00 -05001685 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001686 else:
Anas Nashif56656842019-12-10 12:26:00 -05001687 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001688 elif self.testcase.harness:
Anas Nashif56656842019-12-10 12:26:00 -05001689 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001690 else:
Anas Nashif56656842019-12-10 12:26:00 -05001691 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001692
Anas Nashif56656842019-12-10 12:26:00 -05001693 self.build_only = not (not _build_only and runnable)
1694 self.run = not self.build_only
1695 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001696
Anas Nashif56656842019-12-10 12:26:00 -05001697 def create_overlay(self, platform, enable_asan=False, enable_coverage=False, coverage_platform=[]):
Marc Herbertc7633de2019-07-06 15:52:31 -07001698 # Create this in a "sanitycheck/" subdirectory otherwise this
1699 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1700 # will silently give that second time precedence over any
1701 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001702 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001703 os.makedirs(subdir, exist_ok=True)
1704 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashif56656842019-12-10 12:26:00 -05001705
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001706 with open(file, "w") as f:
1707 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001708
Anas Nashif83fc06a2019-06-22 11:04:10 -04001709 if self.testcase.extra_configs:
1710 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001711
Anas Nashif56656842019-12-10 12:26:00 -05001712 if enable_coverage:
1713 if platform.name in coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001714 content = content + "\nCONFIG_COVERAGE=y"
1715
Anas Nashif56656842019-12-10 12:26:00 -05001716 if enable_asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001717 if platform.type == "native":
1718 content = content + "\nCONFIG_ASAN=y"
1719
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001720 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001721
Andrew Boie6acbe632015-07-17 12:03:52 -07001722 def calculate_sizes(self):
1723 """Get the RAM/ROM sizes of a test case.
1724
1725 This can only be run after the instance has been executed by
1726 MakeGenerator, otherwise there won't be any binaries to measure.
1727
1728 @return A SizeCalculator object
1729 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001730 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1731 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001732 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001733 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001734 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001735
1736 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001737
1738 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001739 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001740
1741
Anas Nashif83fc06a2019-06-22 11:04:10 -04001742class CMake():
Anas Nashif83fc06a2019-06-22 11:04:10 -04001743 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1744 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1745
1746 def __init__(self, testcase, platform, source_dir, build_dir):
1747
1748 self.cwd = None
1749 self.capture_output = True
1750
1751 self.defconfig = {}
1752 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001753
1754 self.instance = None
1755 self.testcase = testcase
1756 self.platform = platform
1757 self.source_dir = source_dir
1758 self.build_dir = build_dir
1759 self.log = "build.log"
1760
1761 def parse_generated(self):
1762 self.defconfig = {}
1763 return {}
1764
1765 def run_build(self, args=[]):
1766
Anas Nashif7a361b82019-12-06 11:37:40 -05001767 logger.debug("Building %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001768
1769 cmake_args = []
1770 cmake_args.extend(args)
1771 cmake = shutil.which('cmake')
1772 cmd = [cmake] + cmake_args
1773 kwargs = dict()
1774
1775 if self.capture_output:
1776 kwargs['stdout'] = subprocess.PIPE
1777 # CMake sends the output of message() to stderr unless it's STATUS
1778 kwargs['stderr'] = subprocess.STDOUT
1779
1780 if self.cwd:
1781 kwargs['cwd'] = self.cwd
1782
1783 p = subprocess.Popen(cmd, **kwargs)
1784 out, _ = p.communicate()
1785
1786 results = {}
1787 if p.returncode == 0:
Anas Nashifd9882382019-12-12 09:58:28 -05001788 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001789
1790 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001791 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1792
1793 if out:
1794 log_msg = out.decode(sys.getdefaultencoding())
1795 with open(os.path.join(self.build_dir, self.log), "a") as log:
1796 log.write(log_msg)
1797
1798 else:
1799 return None
1800 else:
1801 # A real error occurred, raise an exception
1802 if out:
1803 log_msg = out.decode(sys.getdefaultencoding())
1804 with open(os.path.join(self.build_dir, self.log), "a") as log:
1805 log.write(log_msg)
1806
1807 overflow_flash = "region `FLASH' overflowed by"
1808 overflow_ram = "region `RAM' overflowed by"
1809
1810 if log_msg:
1811 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05001812 logger.debug("RAM/ROM Overflow")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001813 self.instance.status = "skipped"
1814 self.instance.reason = "overflow"
1815 else:
1816 self.instance.status = "failed"
1817 self.instance.reason = "Build failure"
1818
1819 results = {
Anas Nashifd9882382019-12-12 09:58:28 -05001820 "returncode": p.returncode,
1821 "instance": self.instance,
1822 }
Anas Nashif83fc06a2019-06-22 11:04:10 -04001823
1824 return results
1825
1826 def run_cmake(self, args=[]):
1827
Anas Nashif11ee5252019-12-04 12:59:10 -05001828 ldflags = "-Wl,--fatal-warnings"
Anas Nashifd9882382019-12-12 09:58:28 -05001829 logger.debug("Running cmake on %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001830
Anas Nashif11ee5252019-12-04 12:59:10 -05001831 # fixme: add additional cflags based on options
Anas Nashifa5984ab2019-10-22 07:36:24 -07001832 cmake_args = [
Anas Nashifd9882382019-12-12 09:58:28 -05001833 '-B{}'.format(self.build_dir),
1834 '-S{}'.format(self.source_dir),
1835 '-DEXTRA_CFLAGS="-Werror ',
1836 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1837 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1838 '-G{}'.format(get_generator()[1])
1839 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001840
Anas Nashifd91f9932019-11-30 10:15:23 -05001841 if options.cmake_only:
1842 cmake_args.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=1")
1843
Anas Nashif83fc06a2019-06-22 11:04:10 -04001844 args = ["-D{}".format(a.replace('"', '')) for a in args]
1845 cmake_args.extend(args)
1846
1847 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1848 cmake_args.extend(cmake_opts)
1849
1850 cmake = shutil.which('cmake')
1851 cmd = [cmake] + cmake_args
1852 kwargs = dict()
1853
1854 if self.capture_output:
1855 kwargs['stdout'] = subprocess.PIPE
1856 # CMake sends the output of message() to stderr unless it's STATUS
1857 kwargs['stderr'] = subprocess.STDOUT
1858
1859 if self.cwd:
1860 kwargs['cwd'] = self.cwd
1861
1862 p = subprocess.Popen(cmd, **kwargs)
1863 out, _ = p.communicate()
1864
1865 if p.returncode == 0:
1866 filter_results = self.parse_generated()
Anas Nashifd9882382019-12-12 09:58:28 -05001867 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001868
1869 results = {'msg': msg, 'filter': filter_results}
1870
1871 else:
1872 self.instance.status = "failed"
1873 self.instance.reason = "Cmake build failure"
1874 results = {"returncode": p.returncode}
1875
Anas Nashif83fc06a2019-06-22 11:04:10 -04001876 if out:
1877 with open(os.path.join(self.build_dir, self.log), "a") as log:
1878 log_msg = out.decode(sys.getdefaultencoding())
1879 log.write(log_msg)
1880
1881 return results
1882
1883
1884class FilterBuilder(CMake):
1885
1886 def __init__(self, testcase, platform, source_dir, build_dir):
1887 super().__init__(testcase, platform, source_dir, build_dir)
1888
1889 self.log = "config-sanitycheck.log"
1890
1891 def parse_generated(self):
1892
1893 if self.platform.name == "unit_testing":
1894 return {}
1895
Anas Nashif83fc06a2019-06-22 11:04:10 -04001896 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001897 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1898
1899 with open(defconfig_path, "r") as fp:
1900 defconfig = {}
1901 for line in fp.readlines():
1902 m = self.config_re.match(line)
1903 if not m:
1904 if line.strip() and not line.startswith("#"):
1905 sys.stderr.write("Unrecognized line %s\n" % line)
1906 continue
1907 defconfig[m.group(1)] = m.group(2).strip()
1908
1909 self.defconfig = defconfig
1910
1911 cmake_conf = {}
1912 try:
1913 cache = CMakeCache.from_file(cmake_cache_path)
1914 except FileNotFoundError:
1915 cache = {}
1916
1917 for k in iter(cache):
1918 cmake_conf[k.name] = k.value
1919
1920 self.cmake_cache = cmake_conf
1921
Anas Nashif83fc06a2019-06-22 11:04:10 -04001922 filter_data = {
1923 "ARCH": self.platform.arch,
1924 "PLATFORM": self.platform.name
1925 }
1926 filter_data.update(os.environ)
1927 filter_data.update(self.defconfig)
1928 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001929
Anas Nashif556f3cb2019-11-05 15:36:15 -08001930 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001931 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001932 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001933 if os.path.exists(dts_path):
1934 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1935 else:
1936 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001937 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1938
Anas Nashif83fc06a2019-06-22 11:04:10 -04001939 except (ValueError, SyntaxError) as se:
1940 sys.stderr.write(
1941 "Failed processing %s\n" % self.testcase.yamlfile)
1942 raise se
1943
1944 if not res:
1945 return {os.path.join(self.platform.name, self.testcase.name): True}
1946 else:
1947 return {os.path.join(self.platform.name, self.testcase.name): False}
1948 else:
1949 self.platform.filter_data = filter_data
1950 return filter_data
1951
1952
1953class ProjectBuilder(FilterBuilder):
1954
Anas Nashif56656842019-12-10 12:26:00 -05001955 def __init__(self, suite, instance, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001956 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1957
1958 self.log = "build.log"
1959 self.instance = instance
1960 self.suite = suite
1961
Anas Nashif56656842019-12-10 12:26:00 -05001962 self.lsan = kwargs.get('lsan', False)
1963 self.asan = kwargs.get('asan', False)
1964 self.valgrind = kwargs.get('valgrind', False)
1965 self.extra_args = kwargs.get('extra_args', [])
1966 self.device_testing = kwargs.get('device_testing', False)
1967 self.cmake_only = kwargs.get('cmake_only', False)
1968 self.coverage = kwargs.get('coverage', False)
Anas Nashife9eb0092019-12-10 16:31:22 -05001969 self.inline_logs = kwargs.get('inline_logs', False)
1970
Anas Nashifd9882382019-12-12 09:58:28 -05001971 @staticmethod
1972 def log_info(filename, inline_logs):
Anas Nashife9eb0092019-12-10 16:31:22 -05001973 filename = os.path.relpath(os.path.realpath(filename))
1974 if inline_logs:
1975 logger.info("{:-^100}".format(filename))
1976
1977 try:
1978 with open(filename) as fp:
1979 data = fp.read()
1980 except Exception as e:
1981 data = "Unable to read log data (%s)\n" % (str(e))
1982
1983 logger.error(data)
1984
1985 logger.info("{:-^100}".format(filename))
1986 else:
Anas Nashif97445682019-12-16 09:36:40 -05001987 logger.error("see: " + Fore.YELLOW + filename + Fore.RESET)
Anas Nashife9eb0092019-12-10 16:31:22 -05001988
1989 def log_info_file(self, inline_logs):
1990 build_dir = self.instance.build_dir
1991 h_log = "{}/handler.log".format(build_dir)
1992 b_log = "{}/build.log".format(build_dir)
1993 v_log = "{}/valgrind.log".format(build_dir)
1994 d_log = "{}/device.log".format(build_dir)
1995
1996 if os.path.exists(v_log) and "Valgrind" in self.instance.reason:
1997 self.log_info("{}".format(v_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05001998 elif os.path.exists(d_log) and os.path.getsize(d_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05001999 self.log_info("{}".format(d_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05002000 elif os.path.exists(h_log) and os.path.getsize(h_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05002001 self.log_info("{}".format(h_log), inline_logs)
2002 else:
2003 self.log_info("{}".format(b_log), inline_logs)
Anas Nashif56656842019-12-10 12:26:00 -05002004
Anas Nashif83fc06a2019-06-22 11:04:10 -04002005 def setup_handler(self):
2006
2007 instance = self.instance
2008 args = []
2009
2010 # FIXME: Needs simplification
2011 if instance.platform.simulation == "qemu":
2012 instance.handler = QEMUHandler(instance, "qemu")
2013 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
2014 instance.handler.call_make_run = True
2015 elif instance.testcase.type == "unit":
2016 instance.handler = BinaryHandler(instance, "unit")
2017 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
2018 elif instance.platform.type == "native":
Anas Nashif6c0e1702019-12-05 15:24:52 -05002019 handler = BinaryHandler(instance, "native")
2020
Anas Nashif56656842019-12-10 12:26:00 -05002021 handler.asan = self.asan
2022 handler.valgrind = self.valgrind
2023 handler.lsan = self.lsan
2024 handler.coverage = self.coverage
Anas Nashif6c0e1702019-12-05 15:24:52 -05002025
2026 handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
2027 instance.handler = handler
Anas Nashif83fc06a2019-06-22 11:04:10 -04002028 elif instance.platform.simulation == "nsim":
2029 if find_executable("nsimdrv"):
2030 instance.handler = BinaryHandler(instance, "nsim")
2031 instance.handler.call_make_run = True
2032 elif instance.platform.simulation == "renode":
2033 if find_executable("renode"):
2034 instance.handler = BinaryHandler(instance, "renode")
2035 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
2036 instance.handler.call_make_run = True
Anas Nashif56656842019-12-10 12:26:00 -05002037 elif self.device_testing:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002038 instance.handler = DeviceHandler(instance, "device")
2039
2040 if instance.handler:
2041 instance.handler.args = args
2042
2043 def process(self, message):
2044 op = message.get('op')
2045
2046 if not self.instance.handler:
2047 self.setup_handler()
2048
2049 # The build process, call cmake and build with configured generator
2050 if op == "cmake":
2051 results = self.cmake()
2052 if self.instance.status == "failed":
2053 pipeline.put({"op": "report", "test": self.instance})
Anas Nashif56656842019-12-10 12:26:00 -05002054 elif self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002055 pipeline.put({"op": "report", "test": self.instance})
2056 else:
2057 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
Anas Nashif7a361b82019-12-06 11:37:40 -05002058 logger.debug("filtering %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002059 self.instance.status = "skipped"
2060 self.instance.reason = "filter"
2061 pipeline.put({"op": "report", "test": self.instance})
2062 else:
2063 pipeline.put({"op": "build", "test": self.instance})
2064
Anas Nashif83fc06a2019-06-22 11:04:10 -04002065 elif op == "build":
Anas Nashifd9882382019-12-12 09:58:28 -05002066 logger.debug("build test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002067 results = self.build()
2068
2069 if results.get('returncode', 1) > 0:
2070 pipeline.put({"op": "report", "test": self.instance})
2071 else:
2072 if self.instance.run:
2073 pipeline.put({"op": "run", "test": self.instance})
2074 else:
2075 pipeline.put({"op": "report", "test": self.instance})
2076 # Run the generated binary using one of the supported handlers
2077 elif op == "run":
Anas Nashifd9882382019-12-12 09:58:28 -05002078 logger.debug("run test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002079 self.run()
2080 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002081 pipeline.put({
2082 "op": "report",
2083 "test": self.instance,
2084 "state": "executed",
2085 "status": self.instance.status,
Stephanos Ioannidis93889002019-11-11 20:31:03 +09002086 "reason": self.instance.reason}
Anas Nashifd9882382019-12-12 09:58:28 -05002087 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002088
2089 # Report results and output progress to screen
2090 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002091 with report_lock:
2092 self.report_out()
2093
Anas Nashif83fc06a2019-06-22 11:04:10 -04002094 def report_out(self):
2095 total_tests_width = len(str(self.suite.total_tests))
2096 self.suite.total_done += 1
2097 instance = self.instance
2098
2099 if instance.status in ["failed", "timeout"]:
2100 self.suite.total_failed += 1
Anas Nashif97445682019-12-16 09:36:40 -05002101 if VERBOSE:
2102 status = Fore.RED + "FAILED " + Fore.RESET + instance.reason
Anas Nashif83fc06a2019-06-22 11:04:10 -04002103 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002104 print("")
2105 logger.error(
2106 "{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002107 instance.platform.name,
2108 instance.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05002109 Fore.RED,
2110 Fore.RESET,
Anas Nashif7a361b82019-12-06 11:37:40 -05002111 instance.reason))
Anas Nashifc1ea4522019-10-11 07:32:45 -07002112 if not VERBOSE:
Anas Nashife9eb0092019-12-10 16:31:22 -05002113 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002114 elif instance.status == "skipped":
2115 self.suite.total_skipped += 1
Anas Nashif97445682019-12-16 09:36:40 -05002116 status = Fore.YELLOW + "SKIPPED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002117 else:
Anas Nashif97445682019-12-16 09:36:40 -05002118 status = Fore.GREEN + "PASSED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002119
Anas Nashif97445682019-12-16 09:36:40 -05002120 if VERBOSE:
Anas Nashif56656842019-12-10 12:26:00 -05002121 if self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002122 more_info = "cmake"
2123 elif instance.status == "skipped":
2124 more_info = instance.reason
2125 else:
2126 if instance.handler and instance.run:
2127 more_info = instance.handler.type_str
2128 htime = instance.handler.duration
2129 if htime:
2130 more_info += " {:.3f}s".format(htime)
2131 else:
2132 more_info = "build"
2133
Anas Nashif7a361b82019-12-06 11:37:40 -05002134 logger.info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002135 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2136 instance.testcase.name, status, more_info))
2137
2138 if instance.status in ["failed", "timeout"]:
Anas Nashifd9882382019-12-12 09:58:28 -05002139 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002140 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002141 sys.stdout.write("\rINFO - Total complete: %s%4d/%4d%s %2d%% skipped: %s%4d%s, failed: %s%4d%s" % (
Anas Nashif97445682019-12-16 09:36:40 -05002142 Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002143 self.suite.total_done,
2144 self.suite.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002145 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002146 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
Anas Nashif97445682019-12-16 09:36:40 -05002147 Fore.YELLOW if self.suite.total_skipped > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002148 self.suite.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002149 Fore.RESET,
2150 Fore.RED if self.suite.total_failed > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002151 self.suite.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002152 Fore.RESET
Anas Nashifd9882382019-12-12 09:58:28 -05002153 )
2154 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002155 sys.stdout.flush()
2156
2157 def cmake(self):
2158
2159 instance = self.instance
2160 args = self.testcase.extra_args[:]
Anas Nashif56656842019-12-10 12:26:00 -05002161 args += self.extra_args
Anas Nashif83fc06a2019-06-22 11:04:10 -04002162
2163 if instance.handler:
2164 args += instance.handler.args
2165
2166 # merge overlay files into one variable
2167 overlays = ""
2168 idx = 0
2169 for arg in args:
2170 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2171 if match:
2172 overlays += match.group(1)
2173 del args[idx]
2174 idx += 1
2175
Anas Nashif56656842019-12-10 12:26:00 -05002176 if (self.testcase.extra_configs or self.coverage or
2177 self.asan):
Anas Nashifd9882382019-12-12 09:58:28 -05002178 args.append("OVERLAY_CONFIG=\"%s %s\"" % (overlays,
2179 os.path.join(instance.build_dir,
2180 "sanitycheck", "testcase_extra.conf")))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002181
2182 results = self.run_cmake(args)
2183 return results
2184
2185 def build(self):
2186 results = self.run_build(['--build', self.build_dir])
2187 return results
2188
2189 def run(self):
2190
2191 instance = self.instance
2192
2193 if instance.handler.type_str == "device":
2194 instance.handler.suite = self.suite
2195
2196 instance.handler.handle()
2197
Anas Nashif83fc06a2019-06-22 11:04:10 -04002198 sys.stdout.flush()
2199
2200
2201pipeline = queue.LifoQueue()
2202
Anas Nashif11ee5252019-12-04 12:59:10 -05002203
Anas Nashif83fc06a2019-06-22 11:04:10 -04002204class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2205 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2206 calls to submit() once the limit given as "bound" work items are queued for
2207 execution.
2208 :param bound: Integer - the maximum number of items in the work queue
2209 :param max_workers: Integer - the size of the thread pool
2210 """
Anas Nashifd9882382019-12-12 09:58:28 -05002211
Anas Nashif83fc06a2019-06-22 11:04:10 -04002212 def __init__(self, bound, max_workers, **kwargs):
2213 super().__init__(max_workers)
Anas Nashif11ee5252019-12-04 12:59:10 -05002214 # self.executor = ThreadPoolExecutor(max_workers=max_workers)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002215 self.semaphore = BoundedSemaphore(bound + max_workers)
2216
2217 def submit(self, fn, *args, **kwargs):
2218 self.semaphore.acquire()
2219 try:
2220 future = super().submit(fn, *args, **kwargs)
Anas Nashif11ee5252019-12-04 12:59:10 -05002221 except Exception:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002222 self.semaphore.release()
2223 raise
2224 else:
2225 future.add_done_callback(lambda x: self.semaphore.release())
2226 return future
2227
Andrew Boie6acbe632015-07-17 12:03:52 -07002228
2229class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002230 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002231 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002232
Anas Nashif83fc06a2019-06-22 11:04:10 -04002233 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002234 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002235 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002236
Anas Nashif37f9dc52018-02-23 08:53:46 -06002237 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002238
2239 self.roots = testcase_roots
2240 if not isinstance(board_root_list, list):
Anas Nashif11ee5252019-12-04 12:59:10 -05002241 self.board_roots = [board_root_list]
Anas Nashif83fc06a2019-06-22 11:04:10 -04002242 else:
2243 self.board_roots = board_root_list
2244
Anas Nashif56656842019-12-10 12:26:00 -05002245 # Testsuite Options
2246 self.coverage_platform = []
2247 self.build_only = False
2248 self.cmake_only = False
2249 self.enable_slow = False
2250 self.device_testing = False
2251 self.fixture = []
2252 self.enable_coverage = False
2253 self.enable_lsan = False
2254 self.enable_asan = False
2255 self.enable_valgrind = False
2256 self.extra_args = []
Anas Nashife9eb0092019-12-10 16:31:22 -05002257 self.inline_logs = False
Anas Nashifc5ee3952019-12-10 16:38:45 -05002258 self.enable_sizes_report = False
Anas Nashif56656842019-12-10 12:26:00 -05002259
Andrew Boie6acbe632015-07-17 12:03:52 -07002260 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002261 self.testcases = {}
2262 self.platforms = []
Anas Nashif5f908822019-11-25 08:19:25 -05002263 self.selected_platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002264 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002265 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002266 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002267 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002268 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002269
Anas Nashif11ee5252019-12-04 12:59:10 -05002270 self.total_tests = 0 # number of test instances
2271 self.total_cases = 0 # number of test cases
2272 self.total_done = 0 # tests completed
Anas Nashif83fc06a2019-06-22 11:04:10 -04002273 self.total_failed = 0
2274 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002275
Anas Nashif83fc06a2019-06-22 11:04:10 -04002276 self.total_platforms = 0
2277 self.start_time = 0
2278 self.duration = 0
2279 self.warnings = 0
2280 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002281
Anas Nashif83fc06a2019-06-22 11:04:10 -04002282 # hardcoded for now
2283 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002284
Anas Nashif56656842019-12-10 12:26:00 -05002285 def config(self):
2286 logger.info("coverage platform: {}".format(self.coverage_platform))
2287
Anas Nashiff16ed8e2019-12-09 16:22:27 -05002288 # Debug Functions
2289 @staticmethod
2290 def info(what):
2291 sys.stdout.write(what + "\n")
2292 sys.stdout.flush()
2293
Anas Nashif83fc06a2019-06-22 11:04:10 -04002294 def update(self):
2295 self.total_tests = len(self.instances)
2296 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002297
Andrew Boie6acbe632015-07-17 12:03:52 -07002298 def compare_metrics(self, filename):
2299 # name, datatype, lower results better
2300 interesting_metrics = [("ram_size", int, True),
2301 ("rom_size", int, True)]
2302
Andrew Boie6acbe632015-07-17 12:03:52 -07002303 if not os.path.exists(filename):
Anas Nashif7a361b82019-12-06 11:37:40 -05002304 logger.info("Cannot compare metrics, %s not found" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -07002305 return []
2306
2307 results = []
2308 saved_metrics = {}
2309 with open(filename) as fp:
2310 cr = csv.DictReader(fp)
2311 for row in cr:
2312 d = {}
2313 for m, _, _ in interesting_metrics:
2314 d[m] = row[m]
2315 saved_metrics[(row["test"], row["platform"])] = d
2316
Anas Nashif83fc06a2019-06-22 11:04:10 -04002317 for instance in self.instances.values():
2318 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002319 if mkey not in saved_metrics:
2320 continue
2321 sm = saved_metrics[mkey]
2322 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002323 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002324 continue
2325 if sm[metric] == "":
2326 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002327 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002328 if delta == 0:
2329 continue
Anas Nashifd9882382019-12-12 09:58:28 -05002330 results.append((instance, metric, instance.metrics.get(metric, 0), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002331 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002332 return results
2333
Anas Nashif83fc06a2019-06-22 11:04:10 -04002334 def misc_reports(self, report, show_footprint, all_deltas,
2335 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002336
Anas Nashif83fc06a2019-06-22 11:04:10 -04002337 if not report:
2338 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002339
Anas Nashif83fc06a2019-06-22 11:04:10 -04002340 deltas = self.compare_metrics(report)
2341 warnings = 0
2342 if deltas and show_footprint:
2343 for i, metric, value, delta, lower_better in deltas:
2344 if not all_deltas and ((delta < 0 and lower_better) or
Anas Nashifd9882382019-12-12 09:58:28 -05002345 (delta > 0 and not lower_better)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002346 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002347
Anas Nashif83fc06a2019-06-22 11:04:10 -04002348 percentage = (float(delta) / float(value - delta))
2349 if not all_deltas and (percentage <
Anas Nashifd9882382019-12-12 09:58:28 -05002350 (footprint_threshold / 100.0)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002351 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002352
Anas Nashifd9882382019-12-12 09:58:28 -05002353 logger.info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Anas Nashif97445682019-12-16 09:36:40 -05002354 i.platform.name, i.testcase.name, Fore.YELLOW,
2355 "INFO" if all_deltas else "WARNING", Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002356 metric, delta, value, percentage))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002357 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002358
Anas Nashif83fc06a2019-06-22 11:04:10 -04002359 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05002360 logger.warning("Deltas based on metrics from last %s" %
Anas Nashifd9882382019-12-12 09:58:28 -05002361 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002362
Anas Nashif83fc06a2019-06-22 11:04:10 -04002363 def summary(self, unrecognized_sections):
2364 failed = 0
2365 for instance in self.instances.values():
2366 if instance.status == "failed":
2367 failed += 1
2368 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
Anas Nashif7a361b82019-12-06 11:37:40 -05002369 logger.error("%sFAILED%s: %s has unrecognized binary sections: %s" %
Anas Nashif97445682019-12-16 09:36:40 -05002370 (Fore.RED, Fore.RESET, instance.name,
Anas Nashifd9882382019-12-12 09:58:28 -05002371 str(instance.metrics.get("unrecognized", []))))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002372 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002373
Anas Nashif83fc06a2019-06-22 11:04:10 -04002374 if self.total_tests and self.total_tests != self.total_skipped:
Anas Nashifd9882382019-12-12 09:58:28 -05002375 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped) / float(
2376 self.total_tests - self.total_skipped))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002377 else:
2378 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002379
Anas Nashifd9882382019-12-12 09:58:28 -05002380 logger.info(
2381 "{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
Anas Nashif97445682019-12-16 09:36:40 -05002382 Fore.RED if failed else Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002383 self.total_tests - self.total_failed - self.total_skipped,
2384 self.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002385 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002386 pass_rate,
Anas Nashif97445682019-12-16 09:36:40 -05002387 Fore.RED if self.total_failed else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002388 self.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002389 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002390 self.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002391 Fore.YELLOW if self.warnings else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002392 self.warnings,
Anas Nashif97445682019-12-16 09:36:40 -05002393 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002394 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002395
Anas Nashif83fc06a2019-06-22 11:04:10 -04002396 self.total_platforms = len(self.platforms)
2397 if self.platforms:
Anas Nashif7a361b82019-12-06 11:37:40 -05002398 logger.info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002399 self.total_cases,
Anas Nashif5f908822019-11-25 08:19:25 -05002400 len(self.selected_platforms),
Anas Nashif83fc06a2019-06-22 11:04:10 -04002401 self.total_platforms,
Anas Nashif5f908822019-11-25 08:19:25 -05002402 (100 * len(self.selected_platforms) / len(self.platforms))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002403 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002404
Anas Nashif56656842019-12-10 12:26:00 -05002405 def save_reports(self, name, report_dir, no_update, release, only_failed):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002406 if not self.instances:
2407 return
Anas Nashif61e21632018-04-08 13:30:16 -05002408
Anas Nashif56656842019-12-10 12:26:00 -05002409 if name:
2410 report_name = name
2411 else:
2412 report_name = "sanitycheck"
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002413
Anas Nashif56656842019-12-10 12:26:00 -05002414 if report_dir:
2415 os.makedirs(report_dir, exist_ok=True)
2416 filename = os.path.join(report_dir, report_name)
2417 outdir = report_dir
Anas Nashif83fc06a2019-06-22 11:04:10 -04002418 else:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002419 filename = os.path.join(self.outdir, report_name)
2420 outdir = self.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002421
Anas Nashif56656842019-12-10 12:26:00 -05002422 if not no_update:
2423 self.xunit_report(filename + ".xml", only_failed)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002424 self.csv_report(filename + ".csv")
2425 self.target_report(outdir)
2426 if self.discards:
2427 self.discard_report(filename + "_discard.csv")
2428
Anas Nashif56656842019-12-10 12:26:00 -05002429 if release:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002430 self.csv_report(RELEASE_DATA)
2431
Anas Nashif83fc06a2019-06-22 11:04:10 -04002432 def add_configurations(self):
2433
2434 for board_root in self.board_roots:
2435 board_root = os.path.abspath(board_root)
2436
Anas Nashif7a361b82019-12-06 11:37:40 -05002437 logger.debug("Reading platform configuration files under %s..." %
Anas Nashifd9882382019-12-12 09:58:28 -05002438 board_root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002439
2440 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
Anas Nashif7a361b82019-12-06 11:37:40 -05002441 logger.debug("Found plaform configuration " + file)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002442 try:
2443 platform = Platform()
2444 platform.load(file)
2445 if platform.sanitycheck:
2446 self.platforms.append(platform)
2447 if platform.default:
2448 self.default_platforms.append(platform.name)
2449
2450 except RuntimeError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002451 logger.error("E: %s: can't load: %s" % (file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002452 self.load_errors += 1
2453
Anas Nashifeabaa7f2019-11-18 07:49:17 -08002454 def get_all_tests(self):
2455 tests = []
2456 for _, tc in self.testcases.items():
2457 for case in tc.cases:
2458 tests.append(case)
2459
2460 return tests
2461
Anas Nashif83fc06a2019-06-22 11:04:10 -04002462 @staticmethod
2463 def get_toolchain():
2464 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2465 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2466
2467 if toolchain == "gccarmemb":
2468 # Remove this translation when gccarmemb is no longer supported.
2469 toolchain = "gnuarmemb"
2470
Anas Nashifb4bdd662018-08-15 17:12:28 -05002471 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002472 if not toolchain:
2473 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002474 except Exception as e:
2475 print(str(e))
2476 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002477
Anas Nashif83fc06a2019-06-22 11:04:10 -04002478 return toolchain
2479
Anas Nashif83fc06a2019-06-22 11:04:10 -04002480 def add_testcases(self):
2481 for root in self.roots:
2482 root = os.path.abspath(root)
2483
Anas Nashifd9882382019-12-12 09:58:28 -05002484 logger.debug("Reading test case configuration files under %s..." % root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002485
2486 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
Anas Nashif7a361b82019-12-06 11:37:40 -05002487 logger.debug("scanning %s" % dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002488 if 'sample.yaml' in filenames:
2489 filename = 'sample.yaml'
2490 elif 'testcase.yaml' in filenames:
2491 filename = 'testcase.yaml'
2492 else:
2493 continue
2494
Anas Nashif7a361b82019-12-06 11:37:40 -05002495 logger.debug("Found possible test case in " + dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002496
2497 dirnames[:] = []
2498 tc_path = os.path.join(dirpath, filename)
2499 self.add_testcase(tc_path, root)
2500
2501 def add_testcase(self, tc_data_file, root):
2502 try:
2503 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2504 parsed_data.load()
2505
2506 tc_path = os.path.dirname(tc_data_file)
2507 workdir = os.path.relpath(tc_path, root)
2508
2509 for name in parsed_data.tests.keys():
2510 tc = TestCase()
2511 tc.name = tc.get_unique(root, workdir, name)
2512
2513 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2514
2515 tc.source_dir = tc_path
2516 tc.yamlfile = tc_data_file
2517
2518 tc.id = name
2519 tc.type = tc_dict["type"]
2520 tc.tags = tc_dict["tags"]
2521 tc.extra_args = tc_dict["extra_args"]
2522 tc.extra_configs = tc_dict["extra_configs"]
2523 tc.arch_whitelist = tc_dict["arch_whitelist"]
2524 tc.arch_exclude = tc_dict["arch_exclude"]
2525 tc.skip = tc_dict["skip"]
2526 tc.platform_exclude = tc_dict["platform_exclude"]
2527 tc.platform_whitelist = tc_dict["platform_whitelist"]
2528 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2529 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2530 tc.tc_filter = tc_dict["filter"]
2531 tc.timeout = tc_dict["timeout"]
2532 tc.harness = tc_dict["harness"]
2533 tc.harness_config = tc_dict["harness_config"]
2534 tc.build_only = tc_dict["build_only"]
2535 tc.build_on_all = tc_dict["build_on_all"]
2536 tc.slow = tc_dict["slow"]
2537 tc.min_ram = tc_dict["min_ram"]
2538 tc.depends_on = tc_dict["depends_on"]
2539 tc.min_flash = tc_dict["min_flash"]
2540 tc.extra_sections = tc_dict["extra_sections"]
2541
2542 tc.parse_subcases(tc_path)
2543
2544 if tc.name:
2545 self.testcases[tc.name] = tc
2546
2547 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002548 logger.error("%s: can't load (skipping): %s" % (tc_data_file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002549 self.load_errors += 1
2550 return False
2551
2552 return True
2553
Anas Nashif83fc06a2019-06-22 11:04:10 -04002554 def get_platform(self, name):
2555 selected_platform = None
2556 for platform in self.platforms:
2557 if platform.name == name:
2558 selected_platform = platform
2559 break
2560 return selected_platform
2561
2562 def get_last_failed(self):
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002563 last_run = os.path.join(self.outdir, "sanitycheck.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002564 try:
2565 if not os.path.exists(last_run):
Anas Nashifd9882382019-12-12 09:58:28 -05002566 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" % last_run)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002567 except Exception as e:
2568 print(str(e))
2569 sys.exit(2)
2570
2571 total_tests = 0
2572 with open(last_run, "r") as fp:
2573 cr = csv.DictReader(fp)
2574 instance_list = []
2575 for row in cr:
2576 total_tests += 1
2577 if row["passed"] == "True":
2578 continue
2579 test = row["test"]
2580 platform = self.get_platform(row["platform"])
2581 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002582 instance.check_build_or_run(
2583 self.build_only,
2584 self.enable_slow,
2585 self.device_testing,
2586 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002587 )
Anas Nashif56656842019-12-10 12:26:00 -05002588 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002589 instance_list.append(instance)
2590 self.add_instances(instance_list)
2591
2592 tests_to_run = len(self.instances)
Anas Nashifd9882382019-12-12 09:58:28 -05002593 logger.info("%d tests passed already, retrying %d tests" % (total_tests - tests_to_run, tests_to_run))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002594
2595 def load_from_file(self, file):
2596 try:
2597 if not os.path.exists(file):
2598 raise SanityRuntimeError(
2599 "Couldn't find input file with list of tests.")
2600 except Exception as e:
2601 print(str(e))
2602 sys.exit(2)
2603
2604 with open(file, "r") as fp:
2605 cr = csv.DictReader(fp)
2606 instance_list = []
2607 for row in cr:
2608 if row["arch"] == "arch":
2609 continue
2610 test = row["test"]
2611 platform = self.get_platform(row["platform"])
2612 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002613 instance.check_build_or_run(
2614 self.build_only,
2615 self.enable_slow,
2616 self.device_testing,
2617 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002618 )
Anas Nashif56656842019-12-10 12:26:00 -05002619 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002620 instance_list.append(instance)
2621 self.add_instances(instance_list)
2622
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002623 def apply_filters(self, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002624
2625 toolchain = self.get_toolchain()
2626
2627 discards = {}
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002628 platform_filter = kwargs.get('platform')
2629 testcase_filter = kwargs.get('run_individual_tests')
2630 arch_filter = kwargs.get('arch')
2631 tag_filter = kwargs.get('tag')
2632 exclude_tag = kwargs.get('exclude_tag')
2633 all_filter = kwargs.get('all')
2634 device_testing_filter = kwargs.get('device_testing')
2635 force_toolchain = kwargs.get('force_toolchain')
Anas Nashif83fc06a2019-06-22 11:04:10 -04002636
Anas Nashif7a361b82019-12-06 11:37:40 -05002637 logger.debug("platform filter: " + str(platform_filter))
2638 logger.debug(" arch_filter: " + str(arch_filter))
2639 logger.debug(" tag_filter: " + str(tag_filter))
2640 logger.debug(" exclude_tag: " + str(exclude_tag))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002641
2642 default_platforms = False
2643
2644 if platform_filter:
2645 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2646 else:
2647 platforms = self.platforms
2648
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002649 if all_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002650 logger.info("Selecting all possible platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002651 # When --all used, any --platform arguments ignored
2652 platform_filter = []
2653 elif not platform_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002654 logger.info("Selecting default platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002655 default_platforms = True
2656
Anas Nashif7a361b82019-12-06 11:37:40 -05002657 logger.info("Building initial testcase list...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002658
2659 for tc_name, tc in self.testcases.items():
2660 # list of instances per testcase, aka configurations.
2661 instance_list = []
2662 for plat in platforms:
2663 instance = TestInstance(tc, plat, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002664 instance.check_build_or_run(
2665 self.build_only,
2666 self.enable_slow,
2667 self.device_testing,
2668 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002669 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002670
2671 if (plat.arch == "unit") != (tc.type == "unit"):
2672 # Discard silently
2673 continue
2674
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002675 if device_testing_filter and instance.build_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002676 discards[instance] = "Not runnable on device"
2677 continue
2678
2679 if tc.skip:
2680 discards[instance] = "Skip filter"
2681 continue
2682
2683 if tc.build_on_all and not platform_filter:
2684 platform_filter = []
2685
2686 if tag_filter and not tc.tags.intersection(tag_filter):
2687 discards[instance] = "Command line testcase tag filter"
2688 continue
2689
2690 if exclude_tag and tc.tags.intersection(exclude_tag):
2691 discards[instance] = "Command line testcase exclude filter"
2692 continue
2693
2694 if testcase_filter and tc_name not in testcase_filter:
2695 discards[instance] = "Testcase name filter"
2696 continue
2697
2698 if arch_filter and plat.arch not in arch_filter:
2699 discards[instance] = "Command line testcase arch filter"
2700 continue
2701
2702 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2703 discards[instance] = "Not in test case arch whitelist"
2704 continue
2705
2706 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2707 discards[instance] = "In test case arch exclude"
2708 continue
2709
2710 if tc.platform_exclude and plat.name in tc.platform_exclude:
2711 discards[instance] = "In test case platform exclude"
2712 continue
2713
2714 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2715 discards[instance] = "In test case toolchain exclude"
2716 continue
2717
2718 if platform_filter and plat.name not in platform_filter:
2719 discards[instance] = "Command line platform filter"
2720 continue
2721
2722 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2723 discards[instance] = "Not in testcase platform whitelist"
2724 continue
2725
2726 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2727 discards[instance] = "Not in testcase toolchain whitelist"
2728 continue
2729
2730 if not plat.env_satisfied:
2731 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2732 continue
2733
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002734 if not force_toolchain \
Anas Nashifd9882382019-12-12 09:58:28 -05002735 and toolchain and (toolchain not in plat.supported_toolchains) \
2736 and tc.type != 'unit':
Anas Nashif83fc06a2019-06-22 11:04:10 -04002737 discards[instance] = "Not supported by the toolchain"
2738 continue
2739
2740 if plat.ram < tc.min_ram:
2741 discards[instance] = "Not enough RAM"
2742 continue
2743
2744 if tc.depends_on:
2745 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2746 if dep_intersection != set(tc.depends_on):
2747 discards[instance] = "No hardware support"
2748 continue
2749
2750 if plat.flash < tc.min_flash:
2751 discards[instance] = "Not enough FLASH"
2752 continue
2753
2754 if set(plat.ignore_tags) & tc.tags:
2755 discards[instance] = "Excluded tags per platform"
2756 continue
2757
2758 # if nothing stopped us until now, it means this configuration
2759 # needs to be added.
2760 instance_list.append(instance)
2761
2762 # no configurations, so jump to next testcase
2763 if not instance_list:
2764 continue
2765
2766 # if sanitycheck was launched with no platform options at all, we
2767 # take all default platforms
2768 if default_platforms and not tc.build_on_all:
2769 if tc.platform_whitelist:
2770 a = set(self.default_platforms)
2771 b = set(tc.platform_whitelist)
2772 c = a.intersection(b)
2773 if c:
Anas Nashifd9882382019-12-12 09:58:28 -05002774 aa = list(filter(lambda tc: tc.platform.name in c, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002775 self.add_instances(aa)
2776 else:
2777 self.add_instances(instance_list[:1])
2778 else:
Anas Nashifd9882382019-12-12 09:58:28 -05002779 instances = list(filter(lambda tc: tc.platform.default, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002780 self.add_instances(instances)
2781
2782 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2783 discards[instance] = "Not a default test platform"
2784
2785 else:
2786 self.add_instances(instance_list)
2787
2788 for _, case in self.instances.items():
Anas Nashif56656842019-12-10 12:26:00 -05002789 case.create_overlay(case.platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002790
2791 self.discards = discards
Anas Nashif5f908822019-11-25 08:19:25 -05002792 self.selected_platforms = set(p.platform.name for p in self.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04002793
2794 return discards
2795
2796 def add_instances(self, instance_list):
2797 for instance in instance_list:
2798 self.instances[instance.name] = instance
2799
Anas Nashif56656842019-12-10 12:26:00 -05002800 def add_tasks_to_queue(self, test_only=False):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002801 for instance in self.instances.values():
Anas Nashif56656842019-12-10 12:26:00 -05002802 if test_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002803 if instance.run:
2804 pipeline.put({"op": "run", "test": instance, "status": "built"})
2805 else:
2806 if instance.status not in ['passed', 'skipped']:
2807 instance.status = None
2808 pipeline.put({"op": "cmake", "test": instance})
2809
2810 return "DONE FEEDING"
2811
Anas Nashifc5ee3952019-12-10 16:38:45 -05002812 def execute(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002813 def calc_one_elf_size(instance):
2814 if instance.status not in ["failed", "skipped"]:
2815 if instance.platform.type != "native":
2816 size_calc = instance.calculate_sizes()
2817 instance.metrics["ram_size"] = size_calc.get_ram_size()
2818 instance.metrics["rom_size"] = size_calc.get_rom_size()
2819 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2820 else:
2821 instance.metrics["ram_size"] = 0
2822 instance.metrics["rom_size"] = 0
2823 instance.metrics["unrecognized"] = []
2824
2825 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2826
Anas Nashif7a361b82019-12-06 11:37:40 -05002827 logger.info("Adding tasks to the queue...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002828 # We can use a with statement to ensure threads are cleaned up promptly
2829 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2830
2831 # start a future for a thread which sends work in through the queue
2832 future_to_test = {
Anas Nashifd9882382019-12-12 09:58:28 -05002833 executor.submit(self.add_tasks_to_queue, self.test_only): 'FEEDER DONE'}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002834
2835 while future_to_test:
2836 # check for status of the futures which are currently working
2837 done, _ = concurrent.futures.wait(
Anas Nashifd9882382019-12-12 09:58:28 -05002838 future_to_test, timeout=0.25,
2839 return_when=concurrent.futures.FIRST_COMPLETED)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002840
2841 # if there is incoming work, start a new future
2842 while not pipeline.empty():
2843 # fetch a url from the queue
2844 message = pipeline.get()
2845 test = message['test']
2846
2847 # Start the load operation and mark the future with its URL
Anas Nashif56656842019-12-10 12:26:00 -05002848 pb = ProjectBuilder(self,
Anas Nashifd9882382019-12-12 09:58:28 -05002849 test,
2850 lsan=self.enable_lsan,
2851 asan=self.enable_asan,
2852 coverage=self.enable_coverage,
2853 extra_args=self.extra_args,
2854 device_testing=self.device_testing,
2855 cmake_only=self.cmake_only,
2856 valgrind=self.enable_valgrind,
2857 inline_logs=self.inline_logs
2858 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002859 future_to_test[executor.submit(pb.process, message)] = test.name
2860
2861 # process any completed futures
2862 for future in done:
2863 test = future_to_test[future]
2864 try:
2865 data = future.result()
2866 except Exception as exc:
Anas Nashif4f043862019-11-05 06:01:49 -08002867 sys.exit('%r generated an exception: %s' % (test, exc))
2868
Anas Nashif83fc06a2019-06-22 11:04:10 -04002869 else:
2870 if data:
Anas Nashif7a361b82019-12-06 11:37:40 -05002871 logger.debug(data)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002872
2873 # remove the now completed future
2874 del future_to_test[future]
2875
Anas Nashifc5ee3952019-12-10 16:38:45 -05002876 if self.enable_size_report and not self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002877 # Parallelize size calculation
2878 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2879 futures = [executor.submit(calc_one_elf_size, instance)
2880 for instance in self.instances.values()]
2881 concurrent.futures.wait(futures)
2882 else:
2883 for instance in self.instances.values():
2884 instance.metrics["ram_size"] = 0
2885 instance.metrics["rom_size"] = 0
2886 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2887 instance.metrics["unrecognized"] = []
2888
Anas Nashif83fc06a2019-06-22 11:04:10 -04002889 def discard_report(self, filename):
2890
2891 try:
2892 if self.discards is None:
2893 raise SanityRuntimeError("apply_filters() hasn't been run!")
2894 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002895 logger.error(str(e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002896 sys.exit(2)
2897
2898 with open(filename, "wt") as csvfile:
2899 fieldnames = ["test", "arch", "platform", "reason"]
2900 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2901 cw.writeheader()
2902 for instance, reason in sorted(self.discards.items()):
2903 rowdict = {"test": instance.testcase.name,
2904 "arch": instance.platform.arch,
2905 "platform": instance.platform.name,
2906 "reason": reason}
2907 cw.writerow(rowdict)
2908
Anas Nashif83fc06a2019-06-22 11:04:10 -04002909 def target_report(self, outdir):
2910 run = "Sanitycheck"
2911 eleTestsuite = None
2912
Anas Nashifd9882382019-12-12 09:58:28 -05002913 platforms = {inst.platform.name for _, inst in self.instances.items()}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002914 for platform in platforms:
2915 errors = 0
2916 passes = 0
2917 fails = 0
2918 duration = 0
2919 skips = 0
2920 for _, instance in self.instances.items():
2921 if instance.platform.name != platform:
2922 continue
2923
2924 handler_time = instance.metrics.get('handler_time', 0)
2925 duration += handler_time
2926 for k in instance.results.keys():
2927 if instance.results[k] == 'PASS':
2928 passes += 1
2929 elif instance.results[k] == 'BLOCK':
2930 errors += 1
2931 elif instance.results[k] == 'SKIP':
2932 skips += 1
2933 else:
2934 fails += 1
2935
2936 eleTestsuites = ET.Element('testsuites')
2937 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashifd9882382019-12-12 09:58:28 -05002938 name=run, time="%f" % duration,
2939 tests="%d" % (errors + passes + fails),
2940 failures="%d" % fails,
2941 errors="%d" % errors, skipped="%d" % skips)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002942
2943 handler_time = 0
2944
2945 # print out test results
2946 for _, instance in self.instances.items():
2947 if instance.platform.name != platform:
2948 continue
2949 handler_time = instance.metrics.get('handler_time', 0)
2950 for k in instance.results.keys():
2951 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05002952 eleTestsuite, 'testcase',
2953 classname="%s:%s" % (instance.platform.name, os.path.basename(instance.testcase.name)),
2954 name="%s" % (k), time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002955 if instance.results[k] in ['FAIL', 'BLOCK']:
2956 el = None
2957
2958 if instance.results[k] == 'FAIL':
2959 el = ET.SubElement(
2960 eleTestcase,
2961 'failure',
2962 type="failure",
2963 message="failed")
2964 elif instance.results[k] == 'BLOCK':
2965 el = ET.SubElement(
2966 eleTestcase,
2967 'error',
2968 type="failure",
2969 message="failed")
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002970 p = os.path.join(self.outdir, instance.platform.name, instance.testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002971 log_file = os.path.join(p, "handler.log")
2972
2973 if os.path.exists(log_file):
2974 with open(log_file, "rb") as f:
2975 log = f.read().decode("utf-8")
2976 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2977 el.text = filtered_string
2978
2979 elif instance.results[k] == 'SKIP':
2980 el = ET.SubElement(
2981 eleTestcase,
2982 'skipped',
2983 type="skipped",
2984 message="Skipped")
2985
Anas Nashif83fc06a2019-06-22 11:04:10 -04002986 result = ET.tostring(eleTestsuites)
2987 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
2988 f.write(result)
2989
Anas Nashif56656842019-12-10 12:26:00 -05002990 def xunit_report(self, filename, append=False):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002991 fails = 0
2992 passes = 0
2993 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002994 skips = 0
2995 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04002996
Anas Nashif83fc06a2019-06-22 11:04:10 -04002997 for instance in self.instances.values():
2998 handler_time = instance.metrics.get('handler_time', 0)
2999 duration += handler_time
3000 if instance.status == "failed":
3001 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003002 errors += 1
3003 else:
3004 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04003005 elif instance.status == 'skipped':
3006 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04003007 else:
3008 passes += 1
3009
3010 run = "Sanitycheck"
3011 eleTestsuite = None
Anas Nashifb3311ed2017-04-13 14:44:48 -04003012
Anas Nashif83fc06a2019-06-22 11:04:10 -04003013 # When we re-run the tests, we re-use the results and update only with
3014 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04003015 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003016 tree = ET.parse(filename)
3017 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05003018 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04003019 else:
3020 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05003021 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04003022 name=run, time="%f" % duration,
3023 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05003024 failures="%d" % fails,
Anas Nashifd9882382019-12-12 09:58:28 -05003025 errors="%d" % (errors), skip="%s" % (skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003026
Anas Nashif83fc06a2019-06-22 11:04:10 -04003027 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04003028
Anas Nashif83fc06a2019-06-22 11:04:10 -04003029 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04003030 if append:
3031 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003032 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04003033 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003034 eleTestsuite.remove(tc)
3035
Anas Nashif83fc06a2019-06-22 11:04:10 -04003036 handler_time = 0
3037 if instance.status != "failed" and instance.handler:
3038 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003039
Anas Nashifd9882382019-12-12 09:58:28 -05003040
Anas Nashif3ba1d432017-12-05 15:28:44 -05003041 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05003042 eleTestsuite,
3043 'testcase',
3044 classname="%s:%s" % (instance.platform.name, instance.testcase.name),
3045 name="%s" % (instance.testcase.name),
3046 time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003047
3048 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05003049 failure = ET.SubElement(
3050 eleTestcase,
3051 'failure',
3052 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003053 message=instance.reason)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003054 p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003055 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05003056 hl = os.path.join(p, "handler.log")
3057 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04003058 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05003059 if os.path.exists(hl):
3060 log_file = hl
3061 else:
3062 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04003063
Anas Nashifc96c90a2019-02-05 07:38:32 -05003064 if os.path.exists(log_file):
3065 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05003066 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04003067 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3068 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05003069 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003070 elif instance.status == "skipped":
Anas Nashifd9882382019-12-12 09:58:28 -05003071 ET.SubElement(eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04003072
3073 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003074 with open(filename, 'wb') as report:
3075 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003076
Anas Nashif83fc06a2019-06-22 11:04:10 -04003077 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08003078 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07003079 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003080 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07003081 "rom_size"]
3082 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3083 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003084 for instance in sorted(self.instances.values()):
3085 rowdict = {"test": instance.testcase.name,
3086 "arch": instance.platform.arch,
3087 "platform": instance.platform.name,
3088 "extra_args": " ".join(instance.testcase.extra_args),
3089 "handler": instance.platform.simulation}
3090
3091 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07003092 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04003093 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07003094 else:
3095 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04003096 if instance.handler:
3097 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
3098 ram_size = instance.metrics.get("ram_size", 0)
3099 rom_size = instance.metrics.get("rom_size", 0)
3100 rowdict["ram_size"] = ram_size
3101 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003102 cw.writerow(rowdict)
3103
Anas Nashif19ca7832019-11-18 08:16:21 -08003104 def get_testcase(self, identifier):
3105 results = []
3106 for _, tc in self.testcases.items():
3107 for case in tc.cases:
3108 if case == identifier:
3109 results.append(tc)
3110 return results
3111
3112
Andrew Boie6acbe632015-07-17 12:03:52 -07003113def parse_arguments():
Anas Nashif3ba1d432017-12-05 15:28:44 -05003114 parser = argparse.ArgumentParser(
3115 description=__doc__,
3116 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003117 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003118
Marc Herbert932a33a2019-03-12 11:37:53 -07003119 case_select = parser.add_argument_group("Test case selection",
3120 """
3121Artificially long but functional example:
3122 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003123 --testcase-root tests/ztest/base \\
3124 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003125 --test tests/ztest/base/testing.ztest.verbose_0 \\
3126 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3127
3128 "kernel.fifo.poll" is one of the test section names in
3129 __/fifo_api/testcase.yaml
3130 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003131
Anas Nashif07d54c02018-07-21 19:29:08 -05003132 parser.add_argument("--force-toolchain", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003133 help="Do not filter based on toolchain, use the set "
3134 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003135 parser.add_argument(
3136 "-p", "--platform", action="append",
3137 help="Platform filter for testing. This option may be used multiple "
Anas Nashifd9882382019-12-12 09:58:28 -05003138 "times. Testcases will only be built/run on the platforms "
3139 "specified. If this option is not used, then platforms marked "
3140 "as default in the platform metadata file will be chosen "
3141 "to build and test. ")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003142 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003143 "-a", "--arch", action="append",
3144 help="Arch filter for testing. Takes precedence over --platform. "
Anas Nashifd9882382019-12-12 09:58:28 -05003145 "If unspecified, test all arches. Multiple invocations "
3146 "are treated as a logical 'or' relationship")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003147 parser.add_argument(
3148 "-t", "--tag", action="append",
3149 help="Specify tags to restrict which tests to run by tag value. "
Anas Nashifd9882382019-12-12 09:58:28 -05003150 "Default is to not do any tag filtering. Multiple invocations "
3151 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003152 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003153 help="Specify tags of tests that should not run. "
Anas Nashifd9882382019-12-12 09:58:28 -05003154 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003155 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003156 "-f",
3157 "--only-failed",
3158 action="store_true",
3159 help="Run only those tests that failed the previous sanity check "
Anas Nashifd9882382019-12-12 09:58:28 -05003160 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003161
Anas Nashif3ba1d432017-12-05 15:28:44 -05003162 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003163 "--retry-failed", type=int, default=0,
3164 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003165
Marc Herbert0c465bb2019-03-11 17:28:36 -07003166 test_xor_subtest = case_select.add_mutually_exclusive_group()
3167
3168 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003169 "-s", "--test", action="append",
3170 help="Run only the specified test cases. These are named by "
Anas Nashifd9882382019-12-12 09:58:28 -05003171 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003172
Marc Herbert0c465bb2019-03-11 17:28:36 -07003173 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003174 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003175 help="""Recursively find sub-test functions and run the entire
3176 test section where they were found, including all sibling test
3177 functions. Sub-tests are named by:
3178 section.name.in.testcase.yaml.function_name_without_test_prefix
3179 Example: kernel.fifo.poll.fifo_loop
3180 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003181
Anas Nashif3ba1d432017-12-05 15:28:44 -05003182 parser.add_argument(
3183 "-l", "--all", action="store_true",
3184 help="Build/test on all platforms. Any --platform arguments "
Anas Nashifd9882382019-12-12 09:58:28 -05003185 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003186
Anas Nashif3ba1d432017-12-05 15:28:44 -05003187 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003188 "-o", "--report-dir",
3189 help="""Output reports containing results of the test run into the
3190 specified directory.
3191 The output will be both in CSV and JUNIT format
3192 (sanitycheck.csv and sanitycheck.xml).
3193 """)
3194
Anas Nashif3ba1d432017-12-05 15:28:44 -05003195 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003196 "--report-name",
3197 help="""Create a report with a custom name.
3198 """)
3199
Anas Nashif83fc06a2019-06-22 11:04:10 -04003200 parser.add_argument("--report-excluded",
Anas Nashifd9882382019-12-12 09:58:28 -05003201 action="store_true",
3202 help="""List all tests that are never run based on current scope and
Anas Nashif83fc06a2019-06-22 11:04:10 -04003203 coverage. If you are looking for accurate results, run this with
3204 --all, but this will take a while...""")
3205
Daniel Leung7f850102016-04-08 11:07:32 -07003206 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003207 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003208
Anas Nashif3ba1d432017-12-05 15:28:44 -05003209 parser.add_argument(
3210 "-B", "--subset",
3211 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
Anas Nashifd9882382019-12-12 09:58:28 -05003212 "3/5 means run the 3rd fifth of the total. "
3213 "This option is useful when running a large number of tests on "
3214 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003215
3216 parser.add_argument(
3217 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003218 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003219
Anas Nashif3ba1d432017-12-05 15:28:44 -05003220 parser.add_argument(
3221 "-y", "--dry-run", action="store_true",
Anas Nashif12d8cce2019-11-20 03:47:27 -08003222 help="""Create the filtered list of test cases, but don't actually
3223 run them. Useful if you're just interested in the discard report
3224 generated for every run and saved in the specified output
3225 directory (sanitycheck_discard.csv).
3226 """)
Andrew Boie6acbe632015-07-17 12:03:52 -07003227
Anas Nashif75547e22018-02-24 08:32:14 -06003228 parser.add_argument("--list-tags", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003229 help="list all tags in selected tests")
Anas Nashif75547e22018-02-24 08:32:14 -06003230
Marc Herbertedf17592019-03-08 12:39:11 -08003231 case_select.add_argument("--list-tests", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003232 help="""List of all sub-test functions recursively found in
Marc Herbert932a33a2019-03-12 11:37:53 -07003233 all --testcase-root arguments. Note different sub-tests can share
3234 the same section name and come from different directories.
3235 The output is flattened and reports --sub-test names only,
3236 not their directories. For instance net.socket.getaddrinfo_ok
3237 and net.socket.fd_set belong to different directories.
3238 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003239
Anas Nashif434995c2019-12-01 13:55:11 -05003240 case_select.add_argument("--test-tree", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003241 help="""Output the testsuite in a tree form""")
Anas Nashif434995c2019-12-01 13:55:11 -05003242
Anas Nashif19ca7832019-11-18 08:16:21 -08003243 case_select.add_argument("--list-test-duplicates", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003244 help="""List tests with duplicate identifiers.
Anas Nashif19ca7832019-11-18 08:16:21 -08003245 """)
3246
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003247 parser.add_argument("--export-tests", action="store",
Anas Nashifd9882382019-12-12 09:58:28 -05003248 metavar="FILENAME",
3249 help="Export tests case meta-data to a file in CSV format.")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003250
Anas Nashif654ec5982019-04-11 08:38:21 -04003251 parser.add_argument("--timestamps",
Anas Nashifd9882382019-12-12 09:58:28 -05003252 action="store_true",
3253 help="Print all messages with time stamps")
Anas Nashif654ec5982019-04-11 08:38:21 -04003254
Anas Nashif3ba1d432017-12-05 15:28:44 -05003255 parser.add_argument(
3256 "-r", "--release", action="store_true",
3257 help="Update the benchmark database with the results of this test "
Anas Nashifd9882382019-12-12 09:58:28 -05003258 "run. Intended to be run by CI when tagging an official "
3259 "release. This database is used as a basis for comparison "
3260 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003261 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003262 help="Treat warning conditions as errors")
3263 parser.add_argument(
3264 "-v",
3265 "--verbose",
3266 action="count",
3267 default=0,
3268 help="Emit debugging information, call multiple times to increase "
Anas Nashifd9882382019-12-12 09:58:28 -05003269 "verbosity")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003270 parser.add_argument(
3271 "-i", "--inline-logs", action="store_true",
3272 help="Upon test failure, print relevant log data to stdout "
Anas Nashifd9882382019-12-12 09:58:28 -05003273 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003274 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003275 help="log also to file")
3276 parser.add_argument(
3277 "-m", "--last-metrics", action="store_true",
3278 help="Instead of comparing metrics from the last --release, "
Anas Nashifd9882382019-12-12 09:58:28 -05003279 "compare with the results of the previous sanity check "
3280 "invocation")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003281 parser.add_argument(
3282 "-u",
3283 "--no-update",
3284 action="store_true",
3285 help="do not update the results of the last run of the sanity "
Anas Nashifd9882382019-12-12 09:58:28 -05003286 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003287 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003288 "-F",
3289 "--load-tests",
3290 metavar="FILENAME",
3291 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003292 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003293
Marc Herbertedf17592019-03-08 12:39:11 -08003294 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003295 "-E",
3296 "--save-tests",
3297 metavar="FILENAME",
3298 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003299 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003300
Andy Doancbecadd2019-02-08 10:19:10 -06003301 test_or_build = parser.add_mutually_exclusive_group()
3302 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003303 "-b", "--build-only", action="store_true",
3304 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003305
Andy Doancbecadd2019-02-08 10:19:10 -06003306 test_or_build.add_argument(
3307 "--test-only", action="store_true",
3308 help="""Only run device tests with current artifacts, do not build
3309 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003310 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003311 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003312 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003313
3314 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003315 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003316 help="Number of jobs for building, defaults to number of CPU threads, "
Anas Nashifd9882382019-12-12 09:58:28 -05003317 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003318
3319 parser.add_argument(
Anas Nashifd9882382019-12-12 09:58:28 -05003320 "--show-footprint", action="store_true",
3321 help="Show footprint statistics and deltas since last release."
3322 )
Anas Nashif424a3db2018-02-20 08:37:24 -06003323 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003324 "-H", "--footprint-threshold", type=float, default=5,
3325 help="When checking test case footprint sizes, warn the user if "
Anas Nashifd9882382019-12-12 09:58:28 -05003326 "the new app size is greater then the specified percentage "
3327 "from the last release. Default is 5. 0 to warn on any "
3328 "increase on app size")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003329 parser.add_argument(
3330 "-D", "--all-deltas", action="store_true",
3331 help="Show all footprint deltas, positive or negative. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003332 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003333 parser.add_argument(
3334 "-O", "--outdir",
Anas Nashifd9882382019-12-12 09:58:28 -05003335 default=os.path.join(os.getcwd(), "sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003336 help="Output directory for logs and binaries. "
Anas Nashifd9882382019-12-12 09:58:28 -05003337 "Default is 'sanity-out' in the current directory. "
3338 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003339 parser.add_argument(
3340 "-n", "--no-clean", action="store_true",
3341 help="Do not delete the outdir before building. Will result in "
Anas Nashifd9882382019-12-12 09:58:28 -05003342 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08003343 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003344 "-T", "--testcase-root", action="append", default=[],
3345 help="Base directory to recursively search for test cases. All "
Anas Nashifd9882382019-12-12 09:58:28 -05003346 "testcase.yaml files under here will be processed. May be "
3347 "called multiple times. Defaults to the 'samples/' and "
3348 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003349
Anas Nashif3ba1d432017-12-05 15:28:44 -05003350 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3351 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003352
Anas Nashif3ba1d432017-12-05 15:28:44 -05003353 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003354 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003355 help="""Directory to search for board configuration files. All .yaml
3356files in the directory will be processed. The directory should have the same
3357structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3358
Anas Nashif3ba1d432017-12-05 15:28:44 -05003359 parser.add_argument(
3360 "-z", "--size", action="append",
3361 help="Don't run sanity checks. Instead, produce a report to "
Anas Nashifd9882382019-12-12 09:58:28 -05003362 "stdout detailing RAM/ROM sizes on the specified filenames. "
3363 "All other command line arguments ignored.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003364 parser.add_argument(
3365 "-S", "--enable-slow", action="store_true",
3366 help="Execute time-consuming test cases that have been marked "
Anas Nashifd9882382019-12-12 09:58:28 -05003367 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003368 parser.add_argument(
3369 "--disable-unrecognized-section-test", action="store_true",
3370 default=False,
3371 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003372 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003373 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003374 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003375 parser.add_argument("--disable-asserts", action="store_false",
3376 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003377 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003378 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003379 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003380 parser.add_argument("--enable-size-report", action="store_true",
3381 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003382
3383 parser.add_argument(
3384 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003385 help="""Extra CMake cache entries to define when building test cases.
3386 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003387 prefixed with -D before being passed to CMake.
3388
3389 E.g
3390 "sanitycheck -x=USE_CCACHE=0"
3391 will translate to
3392 "cmake -DUSE_CCACHE=0"
3393
3394 which will ultimately disable ccache.
3395 """
3396 )
Michael Scott421ce462019-06-18 09:37:46 -07003397
Andy Doan79c48842019-02-08 10:09:04 -06003398 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003399 "--device-testing", action="store_true",
3400 help="Test on device directly. Specify the serial device to "
3401 "use with the --device-serial option.")
3402
3403 parser.add_argument(
3404 "-X", "--fixture", action="append", default=[],
3405 help="Specify a fixture that a board might support")
3406 parser.add_argument(
3407 "--device-serial",
3408 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3409
3410 parser.add_argument("--generate-hardware-map",
3411 help="""Probe serial devices connected to this platform
3412 and create a hardware map file to be used with
3413 --device-testing
3414 """)
3415
3416 parser.add_argument("--hardware-map",
3417 help="""Load hardware map from a file. This will be used
3418 for testing on hardware that is listed in the file.
3419 """)
3420
3421 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003422 "--west-flash", nargs='?', const=[],
3423 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003424 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003425
Michael Scott4ca54392019-07-09 14:21:30 -07003426 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003427 --west-flash="--board-id=foobar,--erase"
3428 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003429
3430 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003431 """
3432 )
Michael Scott421ce462019-06-18 09:37:46 -07003433 parser.add_argument(
3434 "--west-runner",
3435 help="""Uses the specified west runner instead of default when running
3436 with --west-flash.
3437
3438 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3439 --west-flash --west-runner=pyocd"
3440 will translate to "west flash --runner pyocd"
3441
3442 NOTE: west-flash must be enabled to use this option.
3443 """
3444 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003445
3446 valgrind_asan_group = parser.add_mutually_exclusive_group()
3447
3448 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003449 "--enable-valgrind", action="store_true",
3450 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003451 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003452 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003453 configuration and is mutual exclusive with --enable-asan.
3454 """)
3455
3456 valgrind_asan_group.add_argument(
3457 "--enable-asan", action="store_true",
3458 help="""Enable address sanitizer to check for several memory access
3459 errors. Libasan needs to be installed on the host. This option only
3460 works with host binaries such as those generated for the native_posix
3461 configuration and is mutual exclusive with --enable-valgrind.
3462 """)
3463
3464 parser.add_argument(
3465 "--enable-lsan", action="store_true",
3466 help="""Enable leak sanitizer to check for heap memory leaks.
3467 Libasan needs to be installed on the host. This option only
3468 works with host binaries such as those generated for the native_posix
3469 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003470 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003471
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003472 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003473 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003474
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003475 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003476 help="Generate coverage reports. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003477 "--enable_coverage.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003478
Andrew Boie8047a6f2019-07-02 15:43:29 -07003479 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003480 help="Plarforms to run coverage reports on. "
Anas Nashifd9882382019-12-12 09:58:28 -05003481 "This option may be used multiple times. "
3482 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003483
Anas Nashif83fc06a2019-06-22 11:04:10 -04003484 parser.add_argument("--gcov-tool", default=None,
3485 help="Path to the gcov tool to use for code coverage "
Anas Nashifd9882382019-12-12 09:58:28 -05003486 "reports")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003487
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003488 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='lcov',
3489 help="Tool to use to generate coverage report.")
3490
Andrew Boie6acbe632015-07-17 12:03:52 -07003491 return parser.parse_args()
3492
Anas Nashifd9882382019-12-12 09:58:28 -05003493
Andrew Boiebbd670c2015-08-17 13:16:11 -07003494def size_report(sc):
Anas Nashif7a361b82019-12-06 11:37:40 -05003495 logger.info(sc.filename)
3496 logger.info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003497 for i in range(len(sc.sections)):
3498 v = sc.sections[i]
3499
Anas Nashif7a361b82019-12-06 11:37:40 -05003500 logger.info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
Anas Nashifd9882382019-12-12 09:58:28 -05003501 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3502 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003503
Anas Nashif7a361b82019-12-06 11:37:40 -05003504 logger.info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashifd9882382019-12-12 09:58:28 -05003505 (sc.rom_size, sc.ram_size))
Anas Nashif7a361b82019-12-06 11:37:40 -05003506 logger.info("")
Andrew Boiebbd670c2015-08-17 13:16:11 -07003507
Anas Nashifd9882382019-12-12 09:58:28 -05003508
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003509class CoverageTool:
3510 """ Base class for every supported coverage tool
3511 """
3512
3513 def __init__(self):
3514 self.gcov_tool = options.gcov_tool
3515
3516 @staticmethod
3517 def factory(tool):
3518 if tool == 'lcov':
3519 return Lcov()
3520 if tool == 'gcovr':
3521 return Gcovr()
Anas Nashif7a361b82019-12-06 11:37:40 -05003522 logger.error("Unsupported coverage tool specified: {}".format(tool))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003523
3524 @staticmethod
3525 def retrieve_gcov_data(intput_file):
3526 if VERBOSE:
Anas Nashifd9882382019-12-12 09:58:28 -05003527 logger.debug("Working on %s" % intput_file)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003528 extracted_coverage_info = {}
3529 capture_data = False
3530 capture_complete = False
3531 with open(intput_file, 'r') as fp:
3532 for line in fp.readlines():
3533 if re.search("GCOV_COVERAGE_DUMP_START", line):
3534 capture_data = True
3535 continue
3536 if re.search("GCOV_COVERAGE_DUMP_END", line):
3537 capture_complete = True
3538 break
3539 # Loop until the coverage data is found.
3540 if not capture_data:
3541 continue
3542 if line.startswith("*"):
3543 sp = line.split("<")
3544 if len(sp) > 1:
3545 # Remove the leading delimiter "*"
3546 file_name = sp[0][1:]
3547 # Remove the trailing new line char
3548 hex_dump = sp[1][:-1]
3549 else:
3550 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003551 else:
3552 continue
Anas Nashifd9882382019-12-12 09:58:28 -05003553 extracted_coverage_info.update({file_name: hex_dump})
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003554 if not capture_data:
3555 capture_complete = True
3556 return {'complete': capture_complete, 'data': extracted_coverage_info}
3557
3558 @staticmethod
3559 def create_gcda_files(extracted_coverage_info):
3560 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003561 logger.debug("Generating gcda files")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003562 for filename, hexdump_val in extracted_coverage_info.items():
3563 # if kobject_hash is given for coverage gcovr fails
3564 # hence skipping it problem only in gcovr v4.1
3565 if "kobject_hash" in filename:
Anas Nashifd9882382019-12-12 09:58:28 -05003566 filename = (filename[:-4]) + "gcno"
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003567 try:
3568 os.remove(filename)
3569 except Exception:
3570 pass
Anas Nashifdb9592a2018-10-08 10:19:41 -04003571 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003572
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003573 with open(filename, 'wb') as fp:
3574 fp.write(bytes.fromhex(hexdump_val))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003575
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003576 def generate(self, outdir):
3577 for filename in glob.glob("%s/**/handler.log" % outdir, recursive=True):
3578 gcov_data = self.__class__.retrieve_gcov_data(filename)
3579 capture_complete = gcov_data['complete']
3580 extracted_coverage_info = gcov_data['data']
3581 if capture_complete:
3582 self.__class__.create_gcda_files(extracted_coverage_info)
Anas Nashif7a361b82019-12-06 11:37:40 -05003583 logger.debug("Gcov data captured: {}".format(filename))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003584 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003585 logger.error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003586
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003587 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3588 ret = self._generate(outdir, coveragelog)
3589 if ret == 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05003590 logger.info("HTML report generated: {}".format(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003591 os.path.join(outdir, "coverage", "index.html")))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003592
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003593
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003594class Lcov(CoverageTool):
3595
3596 def __init__(self):
3597 super().__init__()
3598 self.ignores = []
3599
3600 def add_ignore_file(self, pattern):
3601 self.ignores.append('*' + pattern + '*')
3602
3603 def add_ignore_directory(self, pattern):
3604 self.ignores.append(pattern + '/*')
3605
3606 def _generate(self, outdir, coveragelog):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003607 coveragefile = os.path.join(outdir, "coverage.info")
3608 ztestfile = os.path.join(outdir, "ztest.info")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003609 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool,
3610 "--capture", "--directory", outdir,
3611 "--rc", "lcov_branch_coverage=1",
3612 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003613 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003614 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--extract",
3615 coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003616 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003617 "--output-file", ztestfile,
3618 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3619
Anas Nashif3cbffef2018-11-07 23:50:54 -05003620 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003621 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3622 ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003623 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3624 "--output-file", ztestfile,
3625 "--rc", "lcov_branch_coverage=1"],
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003626 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003627 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003628 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003629 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003630
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003631 for i in self.ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003632 subprocess.call(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003633 ["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3634 coveragefile, i, "--output-file",
3635 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003636 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003637
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003638 # The --ignore-errors source option is added to avoid it exiting due to
3639 # samples/application_development/external_lib/
3640 return subprocess.call(["genhtml", "--legend", "--branch-coverage",
3641 "--ignore-errors", "source",
3642 "-output-directory",
3643 os.path.join(outdir, "coverage")] + files,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003644 stdout=coveragelog)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003645
3646
3647class Gcovr(CoverageTool):
3648
3649 def __init__(self):
3650 super().__init__()
3651 self.ignores = []
3652
3653 def add_ignore_file(self, pattern):
3654 self.ignores.append('.*' + pattern + '.*')
3655
3656 def add_ignore_directory(self, pattern):
3657 self.ignores.append(pattern + '/.*')
3658
3659 @staticmethod
3660 def _interleave_list(prefix, list):
3661 tuple_list = [(prefix, item) for item in list]
3662 return [item for sublist in tuple_list for item in sublist]
3663
3664 def _generate(self, outdir, coveragelog):
3665 coveragefile = os.path.join(outdir, "coverage.json")
3666 ztestfile = os.path.join(outdir, "ztest.json")
3667
3668 excludes = Gcovr._interleave_list("-e", self.ignores)
3669
3670 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
3671 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3672 self.gcov_tool, "-e", "tests/*"] + excludes +
3673 ["--json", "-o", coveragefile, outdir],
3674 stdout=coveragelog)
3675
3676 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3677 self.gcov_tool, "-f", "tests/ztest", "-e",
3678 "tests/ztest/test/*", "--json", "-o", ztestfile,
3679 outdir], stdout=coveragelog)
3680
3681 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
3682 files = [coveragefile, ztestfile]
3683 else:
3684 files = [coveragefile]
3685
3686 subdir = os.path.join(outdir, "coverage")
3687 os.makedirs(subdir, exist_ok=True)
3688
3689 tracefiles = self._interleave_list("--add-tracefile", files)
3690
3691 return subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--html",
3692 "--html-details"] + tracefiles +
3693 ["-o", os.path.join(subdir, "index.html")],
3694 stdout=coveragelog)
3695
Anas Nashif3ba1d432017-12-05 15:28:44 -05003696
Anas Nashif83fc06a2019-06-22 11:04:10 -04003697def get_generator():
3698 if options.ninja:
3699 generator_cmd = "ninja"
3700 generator = "Ninja"
3701 else:
3702 generator_cmd = "make"
3703 generator = "Unix Makefiles"
3704 return generator_cmd, generator
3705
3706
3707def export_tests(filename, tests):
3708 with open(filename, "wt") as csvfile:
3709 fieldnames = ['section', 'subsection', 'title', 'reference']
3710 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3711 for test in tests:
3712 data = test.split(".")
Anas Nashif19ca7832019-11-18 08:16:21 -08003713 if len(data) > 1:
3714 subsec = " ".join(data[1].split("_")).title()
3715 rowdict = {
Anas Nashifd9882382019-12-12 09:58:28 -05003716 "section": data[0].capitalize(),
3717 "subsection": subsec,
3718 "title": test,
3719 "reference": test
3720 }
Anas Nashif19ca7832019-11-18 08:16:21 -08003721 cw.writerow(rowdict)
3722 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003723 logger.info("{} can't be exported".format(test))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003724
Anas Nashif83fc06a2019-06-22 11:04:10 -04003725
3726def native_and_unit_first(a, b):
3727 if a[0].startswith('unit_testing'):
3728 return -1
3729 if b[0].startswith('unit_testing'):
3730 return 1
3731 if a[0].startswith('native_posix'):
3732 return -1
3733 if b[0].startswith('native_posix'):
3734 return 1
Anas Nashifd9882382019-12-12 09:58:28 -05003735 if a[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003736 return -1
Anas Nashifd9882382019-12-12 09:58:28 -05003737 if b[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003738 return 1
3739
3740 return (a > b) - (a < b)
3741
3742
Anas Nashif5f908822019-11-25 08:19:25 -05003743class HardwareMap:
3744 manufacturer = [
3745 'ARM',
3746 'SEGGER',
3747 'MBED',
3748 'STMicroelectronics',
3749 'Atmel Corp.',
3750 'Texas Instruments',
3751 'Silicon Labs',
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003752 'NXP Semiconductors',
3753 'Microchip Technology Inc.',
3754 'FTDI'
Anas Nashifd9882382019-12-12 09:58:28 -05003755 ]
Anas Nashif5f908822019-11-25 08:19:25 -05003756
3757 runner_mapping = {
3758 'pyocd': [
3759 'DAPLink CMSIS-DAP',
3760 'MBED CMSIS-DAP'
3761 ],
3762 'jlink': [
3763 'J-Link',
3764 'J-Link OB'
3765 ],
3766 'openocd': [
3767 'STM32 STLink', '^XDS110.*'
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003768 ],
3769 'dediprog': [
3770 'TTL232R-3V3',
3771 'MCP2200 USB Serial Port Emulator'
Anas Nashif5f908822019-11-25 08:19:25 -05003772 ]
3773 }
3774
3775 def __init__(self):
3776 self.detected = []
3777 self.connected_hardware = []
3778
3779 def load_device_from_cmdline(self, serial, platform):
3780 device = {
3781 "serial": serial,
3782 "platform": platform,
3783 "counter": 0,
3784 "available": True,
3785 "connected": True
3786 }
3787 self.connected_hardware.append(device)
3788
3789 def load_hardware_map(self, map_file):
3790 with open(map_file, 'r') as stream:
3791 try:
3792 self.connected_hardware = yaml.safe_load(stream)
3793 except yaml.YAMLError as exc:
3794 print(exc)
3795 for i in self.connected_hardware:
3796 i['counter'] = 0
3797
3798 def scan_hw(self):
3799 from serial.tools import list_ports
3800
3801 serial_devices = list_ports.comports()
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003802 logger.info("Scanning connected hardware...")
Anas Nashif5f908822019-11-25 08:19:25 -05003803 for d in serial_devices:
3804 if d.manufacturer in self.manufacturer:
3805
3806 # TI XDS110 can have multiple serial devices for a single board
3807 # assume endpoint 0 is the serial, skip all others
3808 if d.manufacturer == 'Texas Instruments' and not d.location.endswith('0'):
3809 continue
3810 s_dev = {}
3811 s_dev['platform'] = "unknown"
3812 s_dev['id'] = d.serial_number
3813 s_dev['serial'] = d.device
3814 s_dev['product'] = d.product
3815 s_dev['runner'] = 'unknown'
Anas Nashifd9882382019-12-12 09:58:28 -05003816 for runner, _ in self.runner_mapping.items():
Anas Nashif5f908822019-11-25 08:19:25 -05003817 products = self.runner_mapping.get(runner)
3818 if d.product in products:
3819 s_dev['runner'] = runner
3820 continue
3821 # Try regex matching
3822 for p in products:
3823 if re.match(p, d.product):
3824 s_dev['runner'] = runner
3825
3826 s_dev['available'] = True
3827 s_dev['connected'] = True
3828 self.detected.append(s_dev)
3829 else:
Anas Nashifd9882382019-12-12 09:58:28 -05003830 logger.warning("Unsupported device (%s): %s" % (d.manufacturer, d))
Anas Nashif5f908822019-11-25 08:19:25 -05003831
3832 def write_map(self, hwm_file):
3833 # use existing map
3834 if os.path.exists(hwm_file):
3835 with open(hwm_file, 'r') as yaml_file:
3836 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3837 # disconnect everything
3838 for h in hwm:
3839 h['connected'] = False
3840 h['serial'] = None
3841
3842 for d in self.detected:
3843 for h in hwm:
3844 if d['id'] == h['id'] and d['product'] == h['product']:
Anas Nashif5f908822019-11-25 08:19:25 -05003845 h['connected'] = True
3846 h['serial'] = d['serial']
3847 d['match'] = True
3848
Anas Nashifd9882382019-12-12 09:58:28 -05003849 new = list(filter(lambda n: not n.get('match', False), self.detected))
Anas Nashif5f908822019-11-25 08:19:25 -05003850 hwm = hwm + new
3851
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003852 logger.info("Registered devices:")
Anas Nashif0c69c282019-12-18 10:41:27 -05003853 self.dump(hwm)
Anas Nashife5006d12019-12-01 11:41:22 -05003854
Anas Nashif5f908822019-11-25 08:19:25 -05003855 with open(hwm_file, 'w') as yaml_file:
3856 yaml.dump(hwm, yaml_file, default_flow_style=False)
3857
3858 else:
3859 # create new file
3860 with open(hwm_file, 'w') as yaml_file:
3861 yaml.dump(self.detected, yaml_file, default_flow_style=False)
Anas Nashif0c69c282019-12-18 10:41:27 -05003862 logger.info("Detected devices:")
3863 self.dump(self.detected)
Anas Nashif5f908822019-11-25 08:19:25 -05003864
Anas Nashif0c69c282019-12-18 10:41:27 -05003865 @staticmethod
3866 def dump(hwmap=[], filtered=[], header=[], connected_only=False):
3867 print("")
3868 table = []
3869 if not header:
3870 header = ["Platform", "ID", "Serial device"]
3871 for p in sorted(hwmap, key=lambda i: i['platform']):
3872 platform = p.get('platform')
3873 connected = p.get('connected', False)
3874 if filtered and platform not in filtered:
3875 continue
3876
3877 if not connected_only or connected:
3878 table.append([platform, p.get('id', None), p.get('serial')])
3879
3880 print(tabulate(table, headers=header, tablefmt="github"))
Anas Nashifd9882382019-12-12 09:58:28 -05003881
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003882options = None
Anas Nashif5f908822019-11-25 08:19:25 -05003883
Anas Nashifd9882382019-12-12 09:58:28 -05003884
Andrew Boie6acbe632015-07-17 12:03:52 -07003885def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003886 start_time = time.time()
Anas Nashif7a361b82019-12-06 11:37:40 -05003887 global VERBOSE
Anas Nashife10b6512017-12-30 13:01:45 -05003888 global options
Andrew Boie1578ef72019-07-03 10:19:29 -07003889
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003890 options = parse_arguments()
Anas Nashif7a361b82019-12-06 11:37:40 -05003891
3892 # Cleanup
3893 if options.no_clean or options.only_failed or options.test_only:
3894 if os.path.exists(options.outdir):
3895 logger.info("Keeping artifacts untouched")
3896 elif os.path.exists(options.outdir):
Anas Nashifd9882382019-12-12 09:58:28 -05003897 for i in range(1, 100):
Anas Nashif7a361b82019-12-06 11:37:40 -05003898 new_out = options.outdir + ".{}".format(i)
3899 if not os.path.exists(new_out):
3900 logger.info("Renaming output directory to {}".format(new_out))
3901 shutil.move(options.outdir, new_out)
3902 break
3903
3904 os.makedirs(options.outdir, exist_ok=True)
3905
3906 # create file handler which logs even debug messages
3907 if options.log_file:
3908 fh = logging.FileHandler(options.log_file)
3909 else:
3910 fh = logging.FileHandler(os.path.join(options.outdir, "sanitycheck.log"))
3911
3912 fh.setLevel(logging.DEBUG)
3913
3914 # create console handler with a higher log level
3915 ch = logging.StreamHandler()
3916
Anas Nashif7a361b82019-12-06 11:37:40 -05003917 VERBOSE += options.verbose
3918 if VERBOSE > 1:
3919 ch.setLevel(logging.DEBUG)
3920 else:
3921 ch.setLevel(logging.INFO)
3922
Anas Nashif7a361b82019-12-06 11:37:40 -05003923 # create formatter and add it to the handlers
Anas Nashifd4cef072019-12-08 11:58:00 -05003924 if options.timestamps:
3925 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
3926 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003927 formatter = logging.Formatter('%(levelname)-7s - %(message)s')
Anas Nashifd4cef072019-12-08 11:58:00 -05003928
Anas Nashif7a361b82019-12-06 11:37:40 -05003929 formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
3930 ch.setFormatter(formatter)
3931 fh.setFormatter(formatter_file)
3932
3933 # add the handlers to logger
3934 logger.addHandler(ch)
3935 logger.addHandler(fh)
Andrew Boiebbd670c2015-08-17 13:16:11 -07003936
Anas Nashif5f908822019-11-25 08:19:25 -05003937 hwm = HardwareMap()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003938 if options.generate_hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05003939 hwm.scan_hw()
3940 hwm.write_map(options.generate_hardware_map)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003941 return
3942
Anas Nashif5f908822019-11-25 08:19:25 -05003943 if not options.device_testing and options.hardware_map:
3944 hwm.load_hardware_map(options.hardware_map)
3945
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003946 logger.info("Available devices:")
Anas Nashif5f908822019-11-25 08:19:25 -05003947 table = []
Anas Nashif0c69c282019-12-18 10:41:27 -05003948 hwm.dump(hwmap=hwm.connected_hardware, connected_only=True)
Anas Nashif5f908822019-11-25 08:19:25 -05003949 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04003950
Michael Scott421ce462019-06-18 09:37:46 -07003951 if options.west_runner and not options.west_flash:
Anas Nashif7a361b82019-12-06 11:37:40 -05003952 logger.error("west-runner requires west-flash to be enabled")
Michael Scott421ce462019-06-18 09:37:46 -07003953 sys.exit(1)
3954
Michael Scott4ca54392019-07-09 14:21:30 -07003955 if options.west_flash and not options.device_testing:
Anas Nashif7a361b82019-12-06 11:37:40 -05003956 logger.error("west-flash requires device-testing to be enabled")
Michael Scott4ca54392019-07-09 14:21:30 -07003957 sys.exit(1)
3958
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003959 if options.coverage:
3960 options.enable_coverage = True
Christian Taedckec415a4e2019-11-23 23:25:36 +01003961
3962 if not options.coverage_platform:
3963 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003964
Anas Nashife10b6512017-12-30 13:01:45 -05003965 if options.size:
3966 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003967 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003968 sys.exit(0)
3969
Anas Nashife10b6512017-12-30 13:01:45 -05003970 if options.subset:
3971 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003972 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif7a361b82019-12-06 11:37:40 -05003973 logger.info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003974 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003975 logger.error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003976 return
3977
Anas Nashife10b6512017-12-30 13:01:45 -05003978 if not options.testcase_root:
3979 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Anas Nashifd9882382019-12-12 09:58:28 -05003980 os.path.join(ZEPHYR_BASE, "samples")]
Andrew Boie3d348712016-04-08 11:52:13 -07003981
Anas Nashife0d931f2019-12-09 15:23:43 -05003982 if options.show_footprint or options.compare_report or options.release:
3983 options.enable_size_report = True
3984
Anas Nashif56656842019-12-10 12:26:00 -05003985 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
3986
3987 # Set testsuite options from command line.
3988 suite.build_only = options.build_only
Anas Nashifc5ee3952019-12-10 16:38:45 -05003989 suite.cmake_only = options.cmake_only
3990 suite.test_only = options.test_only
Anas Nashif56656842019-12-10 12:26:00 -05003991 suite.enable_slow = options.enable_slow
3992 suite.device_testing = options.device_testing
3993 suite.fixture = options.fixture
3994 suite.enable_asan = options.enable_asan
3995 suite.enable_lsan = options.enable_lsan
3996 suite.enable_coverage = options.enable_coverage
Anas Nashif56656842019-12-10 12:26:00 -05003997 suite.enable_valgrind = options.enable_valgrind
3998 suite.coverage_platform = options.coverage_platform
Anas Nashife9eb0092019-12-10 16:31:22 -05003999 suite.inline_logs = options.inline_logs
Anas Nashifc5ee3952019-12-10 16:38:45 -05004000 suite.enable_size_report = options.enable_size_report
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004001
4002 # Set number of jobs
4003 if options.jobs:
4004 suite.jobs = options.jobs
4005 elif options.build_only:
4006 suite.jobs = multiprocessing.cpu_count() * 2
4007 else:
4008 suite.jobs = multiprocessing.cpu_count()
Anas Nashif7a361b82019-12-06 11:37:40 -05004009 logger.info("JOBS: %d" % suite.jobs)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004010
Anas Nashif83fc06a2019-06-22 11:04:10 -04004011 suite.add_testcases()
4012 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04004013
Anas Nashif83fc06a2019-06-22 11:04:10 -04004014 if options.device_testing:
4015 if options.hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05004016 hwm.load_hardware_map(options.hardware_map)
4017 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004018 if not options.platform:
4019 options.platform = []
Anas Nashif5f908822019-11-25 08:19:25 -05004020 for platform in hwm.connected_hardware:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004021 if platform['connected']:
4022 options.platform.append(platform['platform'])
4023
Anas Nashifd9882382019-12-12 09:58:28 -05004024 elif options.device_serial: # back-ward compatibility
Anas Nashif83fc06a2019-06-22 11:04:10 -04004025 if options.platform and len(options.platform) == 1:
Anas Nashif5f908822019-11-25 08:19:25 -05004026 hwm.load_device_from_cmdline(options.device_serial, options.platform[0])
Anas Nashifebf8dae2019-12-16 09:22:21 -05004027 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004028 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004029 logger.error("""When --device-testing is used with --device-serial, only one
Anas Nashif83fc06a2019-06-22 11:04:10 -04004030 platform is allowed""")
4031
Anas Nashif83fc06a2019-06-22 11:04:10 -04004032 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05004033 sys.exit(1)
4034
Anas Nashif75547e22018-02-24 08:32:14 -06004035 if options.list_tags:
4036 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004037 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06004038 tags = tags.union(tc.tags)
4039
4040 for t in tags:
4041 print("- {}".format(t))
4042
4043 return
4044
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004045 if options.export_tests:
4046 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004047 tests = suite.get_all_tests()
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004048 export_tests(options.export_tests, tests)
4049 return
4050
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004051 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004052
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004053 if options.test:
4054 run_individual_tests = options.test
4055
Anas Nashif434995c2019-12-01 13:55:11 -05004056 if options.list_tests or options.test_tree or options.list_test_duplicates or options.sub_test:
Anas Nashif49b22d42019-06-14 13:45:34 -04004057 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004058 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004059
Anas Nashif19ca7832019-11-18 08:16:21 -08004060 if options.list_test_duplicates:
4061 import collections
4062 dupes = [item for item, count in collections.Counter(all_tests).items() if count > 1]
4063 if dupes:
4064 print("Tests with duplicate identifiers:")
4065 for dupe in dupes:
4066 print("- {}".format(dupe))
4067 for dc in suite.get_testcase(dupe):
4068 print(" - {}".format(dc))
4069 else:
4070 print("No duplicates found.")
4071 return
4072
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004073 if options.sub_test:
Anas Nashifd11fd782019-11-18 10:22:56 -08004074 for st in options.sub_test:
4075 subtests = suite.get_testcase(st)
4076 for sti in subtests:
4077 run_individual_tests.append(sti.name)
4078
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004079 if run_individual_tests:
Anas Nashif7a361b82019-12-06 11:37:40 -05004080 logger.info("Running the following tests:")
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004081 for test in run_individual_tests:
4082 print(" - {}".format(test))
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004083 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004084 logger.info("Tests not found")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004085 return
4086
Anas Nashif434995c2019-12-01 13:55:11 -05004087 elif options.list_tests or options.test_tree:
4088 if options.test_tree:
4089 testsuite = Node("Testsuite")
4090 samples = Node("Samples", parent=testsuite)
4091 tests = Node("Tests", parent=testsuite)
4092
Anas Nashifc1c3cc62019-12-01 13:24:33 -05004093 for test in sorted(all_tests):
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004094 cnt = cnt + 1
Anas Nashif434995c2019-12-01 13:55:11 -05004095 if options.list_tests:
4096 print(" - {}".format(test))
4097
4098 if options.test_tree:
4099 if test.startswith("sample."):
4100 sec = test.split(".")
4101 area = find(samples, lambda node: node.name == sec[1] and node.parent == samples)
4102 if not area:
4103 area = Node(sec[1], parent=samples)
4104
4105 t = Node(test, parent=area)
4106 else:
4107 sec = test.split(".")
4108 area = find(tests, lambda node: node.name == sec[0] and node.parent == tests)
4109 if not area:
4110 area = Node(sec[0], parent=tests)
4111
4112 if area and len(sec) > 2:
4113 subarea = find(area, lambda node: node.name == sec[1] and node.parent == area)
4114 if not subarea:
4115 subarea = Node(sec[1], parent=area)
4116
4117 t = Node(test, parent=subarea)
4118
4119 if options.list_tests:
4120 print("{} total.".format(cnt))
4121
4122 if options.test_tree:
4123 for pre, _, node in RenderTree(testsuite):
4124 print("%s%s" % (pre, node.name))
4125
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004126 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05004127
Anas Nashifbd166f42017-09-02 12:32:08 -04004128 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04004129
4130 if options.only_failed:
4131 suite.get_last_failed()
Anas Nashif5f908822019-11-25 08:19:25 -05004132 suite.selected_platforms = set(p.platform.name for p in suite.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04004133 elif options.load_tests:
4134 suite.load_from_file(options.load_tests)
4135 elif options.test_only:
4136 last_run = os.path.join(options.outdir, "sanitycheck.csv")
4137 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04004138 else:
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004139 discards = suite.apply_filters(
4140 build_only=options.build_only,
4141 enable_slow=options.enable_slow,
4142 platform=options.platform,
4143 arch=options.arch,
4144 tag=options.tag,
4145 exclude_tag=options.exclude_tag,
4146 force_toolchain=options.force_toolchain,
4147 all=options.all,
4148 run_individual_tests=run_individual_tests,
4149 device_testing=options.device_testing
4150
4151 )
Andrew Boie6acbe632015-07-17 12:03:52 -07004152
Anas Nashif30551f42018-01-12 21:56:59 -05004153 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004154 # if we are using command line platform filter, no need to list every
4155 # other platform as excluded, we know that already.
4156 # Show only the discards that apply to the selected platforms on the
4157 # command line
4158
Andrew Boie08ce5a52016-02-22 13:28:10 -08004159 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004160 if options.platform and i.platform.name not in options.platform:
4161 continue
Anas Nashif7a361b82019-12-06 11:37:40 -05004162 logger.debug(
Anas Nashif3ba1d432017-12-05 15:28:44 -05004163 "{:<25} {:<50} {}SKIPPED{}: {}".format(
4164 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04004165 i.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05004166 Fore.YELLOW,
4167 Fore.RESET,
Anas Nashif3ba1d432017-12-05 15:28:44 -05004168 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07004169
Anas Nashif49b22d42019-06-14 13:45:34 -04004170 if options.report_excluded:
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004171 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004172 to_be_run = set()
Anas Nashifd9882382019-12-12 09:58:28 -05004173 for i, p in suite.instances.items():
Anas Nashif83fc06a2019-06-22 11:04:10 -04004174 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04004175
Anas Nashif83fc06a2019-06-22 11:04:10 -04004176 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004177 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004178 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004179 print("- {}".format(not_run))
4180
4181 return
4182
Anas Nashife10b6512017-12-30 13:01:45 -05004183 if options.subset:
Anas Nashifd9882382019-12-12 09:58:28 -05004184 # suite.instances = OrderedDict(sorted(suite.instances.items(),
Anas Nashif83fc06a2019-06-22 11:04:10 -04004185 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05004186 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004187 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004188 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05004189 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04004190 if subset == sets:
4191 end = total
4192 else:
4193 end = start + per_set
4194
Anas Nashif83fc06a2019-06-22 11:04:10 -04004195 sliced_instances = islice(suite.instances.items(), start, end)
4196 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004197
Anas Nashif83fc06a2019-06-22 11:04:10 -04004198 if options.save_tests:
4199 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07004200 return
4201
Anas Nashif7a361b82019-12-06 11:37:40 -05004202 logger.info("%d test configurations selected, %d configurations discarded due to filters." %
Anas Nashifd9882382019-12-12 09:58:28 -05004203 (len(suite.instances), len(discards)))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004204
Peter Bigot3d46ea52019-11-21 12:00:18 -06004205 if options.device_testing:
4206 print("\nDevice testing on:")
Anas Nashif0c69c282019-12-18 10:41:27 -05004207 hwm.dump(suite.connected_hardware, suite.selected_platforms)
Anas Nashif5f908822019-11-25 08:19:25 -05004208 print("")
Peter Bigot3d46ea52019-11-21 12:00:18 -06004209
Anas Nashif83fc06a2019-06-22 11:04:10 -04004210 if options.dry_run:
4211 duration = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -05004212 logger.info("Completed in %d seconds" % (duration))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004213 return
4214
4215 retries = options.retry_failed + 1
4216 completed = 0
4217
4218 suite.update()
4219 suite.start_time = start_time
4220
4221 while True:
4222 completed += 1
4223
4224 if completed > 1:
Anas Nashifd9882382019-12-12 09:58:28 -05004225 logger.info("%d Iteration:" % (completed))
4226 time.sleep(60) # waiting for the system to settle down
Anas Nashif83fc06a2019-06-22 11:04:10 -04004227 suite.total_done = suite.total_tests - suite.total_failed
4228 suite.total_failed = 0
4229
Anas Nashifc5ee3952019-12-10 16:38:45 -05004230 suite.execute()
Anas Nashif7a361b82019-12-06 11:37:40 -05004231 print("")
Andrew Boie6acbe632015-07-17 12:03:52 -07004232
Anas Nashif83fc06a2019-06-22 11:04:10 -04004233 retries = retries - 1
4234 if retries == 0 or suite.total_failed == 0:
4235 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06004236
Anas Nashif83fc06a2019-06-22 11:04:10 -04004237 suite.misc_reports(options.compare_report, options.show_footprint,
Anas Nashifd9882382019-12-12 09:58:28 -05004238 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07004239
Anas Nashif83a98e52019-11-24 07:42:06 -05004240 suite.duration = time.time() - start_time
4241 suite.summary(options.disable_unrecognized_section_test)
4242
Anas Nashife10b6512017-12-30 13:01:45 -05004243 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004244 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004245 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07004246
4247 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004248 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004249 if ts_plat and (ts_plat.type in {"native", "unit"}):
4250 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07004251
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004252 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07004253 options.gcov_tool = "gcov"
4254 else:
4255 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
Anas Nashifd9882382019-12-12 09:58:28 -05004256 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
Andrew Boie49cf4862019-07-08 12:02:13 -07004257
Anas Nashif7a361b82019-12-06 11:37:40 -05004258 logger.info("Generating coverage files...")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01004259 coverage_tool = CoverageTool.factory(options.coverage_tool)
4260 coverage_tool.add_ignore_file('generated')
4261 coverage_tool.add_ignore_directory('tests')
4262 coverage_tool.add_ignore_directory('samples')
4263 coverage_tool.generate(options.outdir)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03004264
Anas Nashif83fc06a2019-06-22 11:04:10 -04004265 if options.device_testing:
4266 print("\nHardware distribution summary:\n")
Anas Nashif5f908822019-11-25 08:19:25 -05004267 table = []
4268 header = ['Board', 'ID', 'Counter']
4269 for p in hwm.connected_hardware:
4270 if p['connected'] and p['platform'] in suite.selected_platforms:
4271 row = [p['platform'], p.get('id', None), p['counter']]
4272 table.append(row)
4273 print(tabulate(table, headers=header, tablefmt="github"))
4274
Anas Nashif56656842019-12-10 12:26:00 -05004275 suite.save_reports(options.report_name,
4276 options.report_dir,
4277 options.no_update,
4278 options.release,
4279 options.only_failed)
4280
Anas Nashif83fc06a2019-06-22 11:04:10 -04004281 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07004282 sys.exit(1)
4283
Anas Nashifd9882382019-12-12 09:58:28 -05004284
Andrew Boie6acbe632015-07-17 12:03:52 -07004285if __name__ == "__main__":
4286 main()