blob: 8cc85e81000d847b74dc34ca98c9c7cb53f27ba1 [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
Anas Nashiffc85ff02019-12-19 11:59:54 -0500762
763 pre_script = hardware.get('pre_script')
764 post_script = hardware.get('post_script')
765
766 if pre_script:
767 with subprocess.Popen(pre_script, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
768 try:
769 (stdout, stderr) = proc.communicate(timeout=30)
770 logger.debug(stdout.decode())
771
772 except subprocess.TimeoutExpired:
773 proc.kill()
774 (stdout, stderr) = proc.communicate()
775 logger.error("{} timed out".format(post_script))
776
Marti Bolivar5591ca22019-02-07 15:53:39 -0700777 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashifd9882382019-12-12 09:58:28 -0500778 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600779 t.start()
780
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500781 d_log = "{}/device.log".format(self.instance.build_dir)
Anas Nashif7a361b82019-12-06 11:37:40 -0500782 logger.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500783 try:
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500784 stdout = stderr = None
785 with subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
786 try:
787 (stdout, stderr) = proc.communicate(timeout=30)
Anas Nashiffa8085e2019-12-09 16:42:58 -0500788 logger.debug(stdout.decode())
789
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500790 if proc.returncode != 0:
791 self.instance.reason = "Device issue (Flash?)"
792 with open(d_log, "w") as dlog_fp:
793 dlog_fp.write(stderr.decode())
794 except subprocess.TimeoutExpired:
795 proc.kill()
Anas Nashifd9882382019-12-12 09:58:28 -0500796 (stdout, stderr) = proc.communicate()
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500797 self.instance.reason = "Device issue (Timeout)"
798
799 with open(d_log, "w") as dlog_fp:
800 dlog_fp.write(stderr.decode())
Anas Nashif83fc06a2019-06-22 11:04:10 -0400801
Anas Nashif61e21632018-04-08 13:30:16 -0500802 except subprocess.CalledProcessError:
Anas Nashifd9882382019-12-12 09:58:28 -0500803 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600804
805 t.join(self.timeout)
806 if t.is_alive():
807 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600808
809 if ser.isOpen():
810 ser.close()
811
Anas Nashif17d066b2019-12-17 14:37:16 -0500812 handler_time = time.time() - start_time
813
Anas Nashifd3384fb2018-02-22 06:44:16 -0600814 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400815 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600816 if c not in harness.tests:
817 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500818
Anas Nashif17d066b2019-12-17 14:37:16 -0500819 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400820
Anas Nashif61e21632018-04-08 13:30:16 -0500821 self.instance.results = harness.tests
Anas Nashif17d066b2019-12-17 14:37:16 -0500822
Anas Nashif73440ea2018-02-19 10:57:03 -0600823 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400824 self.set_state(harness.state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500825 if harness.state == "failed":
826 self.instance.reason = "Failed"
Anas Nashif73440ea2018-02-19 10:57:03 -0600827 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400828 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600829
Anas Nashiffc85ff02019-12-19 11:59:54 -0500830 if post_script:
831 with subprocess.Popen(post_script, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
832 try:
833 (stdout, stderr) = proc.communicate(timeout=30)
834 logger.debug(stdout.decode())
835
836 except subprocess.TimeoutExpired:
837 proc.kill()
838 (stdout, stderr) = proc.communicate()
839 logger.error("{} timed out".format(post_script))
840
Anas Nashif83fc06a2019-06-22 11:04:10 -0400841 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500842
Anas Nashif3a00d0c2019-11-12 11:07:15 -0500843 self.record(harness)
844
Anas Nashif11ee5252019-12-04 12:59:10 -0500845
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300846class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700847 """Spawns a thread to monitor QEMU output from pipes
848
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400849 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700850 We need to do this as once qemu starts, it runs forever until killed.
851 Test cases emit special messages to the console as they run, we check
852 for these to collect whether the test passed or failed.
853 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700854
Anas Nashif83fc06a2019-06-22 11:04:10 -0400855 def __init__(self, instance, type_str):
856 """Constructor
857
858 @param instance Test instance
859 """
860
861 super().__init__(instance, type_str)
862 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
863
864 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
865
Andrew Boie6acbe632015-07-17 12:03:52 -0700866 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500867 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700868 fifo_in = fifo_fn + ".in"
869 fifo_out = fifo_fn + ".out"
870
871 # These in/out nodes are named from QEMU's perspective, not ours
872 if os.path.exists(fifo_in):
873 os.unlink(fifo_in)
874 os.mkfifo(fifo_in)
875 if os.path.exists(fifo_out):
876 os.unlink(fifo_out)
877 os.mkfifo(fifo_out)
878
879 # We don't do anything with out_fp but we need to open it for
880 # writing so that QEMU doesn't block, due to the way pipes work
881 out_fp = open(fifo_in, "wb")
882 # Disable internal buffering, we don't
883 # want read() or poll() to ever block if there is data in there
884 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800885 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700886
887 start_time = time.time()
888 timeout_time = start_time + timeout
889 p = select.poll()
890 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400891 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700892
Andrew Boie6acbe632015-07-17 12:03:52 -0700893 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500894 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700895 while True:
896 this_timeout = int((timeout_time - time.time()) * 1000)
897 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400898 if not out_state:
899 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700900 break
901
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500902 try:
903 c = in_fp.read(1).decode("utf-8")
904 except UnicodeDecodeError:
905 # Test is writing something weird, fail
906 out_state = "unexpected byte"
907 break
908
Andrew Boie6acbe632015-07-17 12:03:52 -0700909 if c == "":
910 # EOF, this shouldn't happen unless QEMU crashes
911 out_state = "unexpected eof"
912 break
913 line = line + c
914 if c != "\n":
915 continue
916
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300917 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700918 log_out_fp.write(line)
919 log_out_fp.flush()
920 line = line.strip()
Anas Nashif7a361b82019-12-06 11:37:40 -0500921 logger.debug("QEMU: %s" % line)
Andrew Boie6acbe632015-07-17 12:03:52 -0700922
Anas Nashif576be982017-12-23 20:20:27 -0500923 harness.handle(line)
924 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400925 # if we have registered a fail make sure the state is not
926 # overridden by a false success message coming from the
927 # testsuite
928 if out_state != 'failed':
929 out_state = harness.state
930
931 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700932 # the timeout and wait for 2 more seconds to catch anything
933 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700934 # coverage is enabled since dumping this information can
935 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500936 if not timeout_extended or harness.capture_coverage:
Anas Nashifd9882382019-12-12 09:58:28 -0500937 timeout_extended = True
Anas Nashiff29087e2019-01-25 09:37:38 -0500938 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700939 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500940 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500941 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700942 line = ""
943
Anas Nashif83fc06a2019-06-22 11:04:10 -0400944 handler.record(harness)
945
946 handler_time = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -0500947 logger.debug("QEMU complete (%s) after %f seconds" %
Anas Nashifd9882382019-12-12 09:58:28 -0500948 (out_state, handler_time))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400949 handler.set_state(out_state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500950 if out_state == "timeout":
951 handler.instance.reason = "Timeout"
952 elif out_state == "failed":
953 handler.instance.reason = "Failed"
Andrew Boie6acbe632015-07-17 12:03:52 -0700954
955 log_out_fp.close()
956 out_fp.close()
957 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400958 if os.path.exists(pid_fn):
959 pid = int(open(pid_fn).read())
960 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700961
Anas Nashifd6476ee2019-04-11 11:40:09 -0400962 try:
963 if pid:
964 os.kill(pid, signal.SIGTERM)
965 except ProcessLookupError:
966 # Oh well, as long as it's dead! User probably sent Ctrl-C
967 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800968
Andrew Boie6acbe632015-07-17 12:03:52 -0700969 os.unlink(fifo_in)
970 os.unlink(fifo_out)
971
Anas Nashif83fc06a2019-06-22 11:04:10 -0400972 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700973 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500974 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700975
976 # We pass this to QEMU which looks for fifos with .in and .out
977 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400978 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700979
Anas Nashif83fc06a2019-06-22 11:04:10 -0400980 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700981 if os.path.exists(self.pid_fn):
982 os.unlink(self.pid_fn)
983
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500984 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500985
Anas Nashif83fc06a2019-06-22 11:04:10 -0400986 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500987 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600988 harness.configure(self.instance)
989 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400990 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300991 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500992 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600993
994 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700995 self.thread.daemon = True
Anas Nashif7a361b82019-12-06 11:37:40 -0500996 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700997 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400998 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700999
Anas Nashifd9882382019-12-12 09:58:28 -05001000 logger.debug("Running %s (%s)" % (self.name, self.type_str))
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001001 command = [get_generator()[0]]
1002 command += ["-C", self.build_dir, "run"]
1003
1004 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -05001005 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001006 proc.wait()
1007 self.returncode = proc.returncode
1008
1009 if self.returncode != 0:
1010 self.set_state("failed", 0)
1011 self.instance.reason = "Exited with {}".format(self.returncode)
1012
Andrew Boie6acbe632015-07-17 12:03:52 -07001013 def get_fifo(self):
1014 return self.fifo_fn
1015
Anas Nashif11ee5252019-12-04 12:59:10 -05001016
Andrew Boie6acbe632015-07-17 12:03:52 -07001017class SizeCalculator:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001018 alloc_sections = [
1019 "bss",
1020 "noinit",
1021 "app_bss",
1022 "app_noinit",
1023 "ccm_bss",
1024 "ccm_noinit"
Anas Nashifd9882382019-12-12 09:58:28 -05001025 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001026
1027 rw_sections = [
1028 "datas",
1029 "initlevel",
1030 "exceptions",
1031 "initshell",
1032 "_static_thread_area",
1033 "_k_timer_area",
1034 "_k_mem_slab_area",
1035 "_k_mem_pool_area",
1036 "sw_isr_table",
1037 "_k_sem_area",
1038 "_k_mutex_area",
1039 "app_shmem_regions",
1040 "_k_fifo_area",
1041 "_k_lifo_area",
1042 "_k_stack_area",
1043 "_k_msgq_area",
1044 "_k_mbox_area",
1045 "_k_pipe_area",
1046 "net_if",
1047 "net_if_dev",
1048 "net_stack",
1049 "net_l2_data",
1050 "_k_queue_area",
1051 "_net_buf_pool_area",
1052 "app_datas",
1053 "kobject_data",
1054 "mmu_tables",
1055 "app_pad",
1056 "priv_stacks",
1057 "ccm_data",
1058 "usb_descriptor",
1059 "usb_data", "usb_bos_desc",
1060 'log_backends_sections',
1061 'log_dynamic_sections',
1062 'log_const_sections',
1063 "app_smem",
1064 'shell_root_cmds_sections',
1065 'log_const_sections',
1066 "font_entry_sections",
1067 "priv_stacks_noinit",
1068 "_TEXT_SECTION_NAME_2",
1069 "_GCOV_BSS_SECTION_NAME",
1070 "gcov",
1071 "nocache"
Anas Nashifd9882382019-12-12 09:58:28 -05001072 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001073
Andrew Boie73b4ee62015-10-07 11:33:22 -07001074 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001075 ro_sections = [
1076 "text",
1077 "ctors",
1078 "init_array",
1079 "reset",
1080 "object_access",
1081 "rodata",
1082 "devconfig",
1083 "net_l2",
1084 "vector",
1085 "sw_isr_table",
1086 "_settings_handlers_area",
1087 "_bt_channels_area",
1088 "_bt_br_channels_area",
1089 "_bt_services_area",
1090 "vectors",
1091 "net_socket_register",
1092 "net_ppp_proto"
Anas Nashifd9882382019-12-12 09:58:28 -05001093 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001094
Andrew Boie52fef672016-11-29 12:21:59 -08001095 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001096 """Constructor
1097
Andrew Boiebbd670c2015-08-17 13:16:11 -07001098 @param filename Path to the output binary
1099 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001100 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001101 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001102 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001103 magic = f.read(4)
1104
Anas Nashifb4bdd662018-08-15 17:12:28 -05001105 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001106 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001107 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1108 except Exception as e:
1109 print(str(e))
1110 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001111
1112 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001113 # GREP can not be used as it returns an error if the symbol is not
1114 # found.
1115 is_xip_command = "nm " + filename + \
Anas Nashifd9882382019-12-12 09:58:28 -05001116 " | awk '/CONFIG_XIP/ { print $3 }'"
Anas Nashif3ba1d432017-12-05 15:28:44 -05001117 is_xip_output = subprocess.check_output(
1118 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1119 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001120 try:
1121 if is_xip_output.endswith("no symbols"):
1122 raise SanityRuntimeError("%s has no symbol information" % filename)
1123 except Exception as e:
1124 print(str(e))
1125 sys.exit(2)
1126
Andrew Boie6acbe632015-07-17 12:03:52 -07001127 self.is_xip = (len(is_xip_output) != 0)
1128
Andrew Boiebbd670c2015-08-17 13:16:11 -07001129 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001130 self.sections = []
1131 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001132 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001133 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001134
1135 self._calculate_sizes()
1136
1137 def get_ram_size(self):
1138 """Get the amount of RAM the application will use up on the device
1139
1140 @return amount of RAM, in bytes
1141 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001142 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001143
1144 def get_rom_size(self):
1145 """Get the size of the data that this application uses on device's flash
1146
1147 @return amount of ROM, in bytes
1148 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001149 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001150
1151 def unrecognized_sections(self):
1152 """Get a list of sections inside the binary that weren't recognized
1153
David B. Kinder29963c32017-06-16 12:32:42 -07001154 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001155 """
1156 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001157 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001158 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001159 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001160 return slist
1161
1162 def _calculate_sizes(self):
1163 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001164 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001165 objdump_output = subprocess.check_output(
1166 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001167
1168 for line in objdump_output:
1169 words = line.split()
1170
Anas Nashifd9882382019-12-12 09:58:28 -05001171 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001172 continue
1173
1174 index = words[0]
Anas Nashifd9882382019-12-12 09:58:28 -05001175 if not index[0].isdigit(): # Skip lines that do not start
1176 continue # with a digit
Andrew Boie6acbe632015-07-17 12:03:52 -07001177
Anas Nashifd9882382019-12-12 09:58:28 -05001178 name = words[1] # Skip lines with section names
1179 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001180 continue
1181
Andrew Boie73b4ee62015-10-07 11:33:22 -07001182 # TODO this doesn't actually reflect the size in flash or RAM as
1183 # it doesn't include linker-imposed padding between sections.
1184 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001185 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001186 if size == 0:
1187 continue
1188
Andrew Boie73b4ee62015-10-07 11:33:22 -07001189 load_addr = int(words[4], 16)
1190 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001191
1192 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001193 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001194 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001195 if name in SizeCalculator.alloc_sections:
1196 self.ram_size += size
1197 stype = "alloc"
1198 elif name in SizeCalculator.rw_sections:
1199 self.ram_size += size
1200 self.rom_size += size
1201 stype = "rw"
1202 elif name in SizeCalculator.ro_sections:
1203 self.rom_size += size
1204 if not self.is_xip:
1205 self.ram_size += size
1206 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001207 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001208 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001209 if name not in self.extra_sections:
1210 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001211
Anas Nashif3ba1d432017-12-05 15:28:44 -05001212 self.sections.append({"name": name, "load_addr": load_addr,
1213 "size": size, "virt_addr": virt_addr,
1214 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001215
1216
Andrew Boie6acbe632015-07-17 12:03:52 -07001217# "list" - List of strings
1218# "list:<type>" - List of <type>
1219# "set" - Set of unordered, unique strings
1220# "set:<type>" - Set of <type>
1221# "float" - Floating point
1222# "int" - Integer
1223# "bool" - Boolean
1224# "str" - String
1225
1226# XXX Be sure to update __doc__ if you change any of this!!
1227
Anas Nashif83fc06a2019-06-22 11:04:10 -04001228platform_valid_keys = {
Anas Nashifd9882382019-12-12 09:58:28 -05001229 "supported_toolchains": {"type": "list", "default": []},
1230 "env": {"type": "list", "default": []}
1231}
Andrew Boie6acbe632015-07-17 12:03:52 -07001232
Anas Nashif3ba1d432017-12-05 15:28:44 -05001233testcase_valid_keys = {"tags": {"type": "set", "required": False},
1234 "type": {"type": "str", "default": "integration"},
1235 "extra_args": {"type": "list"},
1236 "extra_configs": {"type": "list"},
1237 "build_only": {"type": "bool", "default": False},
1238 "build_on_all": {"type": "bool", "default": False},
1239 "skip": {"type": "bool", "default": False},
1240 "slow": {"type": "bool", "default": False},
1241 "timeout": {"type": "int", "default": 60},
1242 "min_ram": {"type": "int", "default": 8},
1243 "depends_on": {"type": "set"},
1244 "min_flash": {"type": "int", "default": 32},
1245 "arch_whitelist": {"type": "set"},
1246 "arch_exclude": {"type": "set"},
1247 "extra_sections": {"type": "list", "default": []},
1248 "platform_exclude": {"type": "set"},
1249 "platform_whitelist": {"type": "set"},
1250 "toolchain_exclude": {"type": "set"},
1251 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001252 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001253 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301254 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001255 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001256
Anas Nashif11ee5252019-12-04 12:59:10 -05001257
Andrew Boie6acbe632015-07-17 12:03:52 -07001258class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001259 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001260 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001261
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001262 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 """Instantiate a new SanityConfigParser object
1264
Anas Nashifa792a3d2017-04-04 18:47:49 -04001265 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001266 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001267 self.data = {}
1268 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001269 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001270 self.tests = {}
1271 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001272
1273 def load(self):
1274 self.data = scl.yaml_load_verify(self.filename, self.schema)
1275
Anas Nashif255625b2017-12-05 15:08:26 -05001276 if 'tests' in self.data:
1277 self.tests = self.data['tests']
1278 if 'common' in self.data:
1279 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001280
1281 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001282 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001283 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001284 if typestr == "str":
1285 return v
1286
1287 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001288 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001289
1290 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001291 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001292
1293 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001294 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001295
Anas Nashif3ba1d432017-12-05 15:28:44 -05001296 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001297 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001298 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001299 vs = v.split()
1300 if len(typestr) > 4 and typestr[4] == ":":
1301 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1302 else:
1303 return vs
1304
1305 elif typestr.startswith("set"):
1306 vs = v.split()
1307 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001308 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001309 else:
1310 return set(vs)
1311
Anas Nashif576be982017-12-23 20:20:27 -05001312 elif typestr.startswith("map"):
1313 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001314 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001315 raise ConfigurationError(
1316 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001317
Anas Nashifb4754ed2017-12-05 17:27:58 -05001318 def get_test(self, name, valid_keys):
1319 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001320
Anas Nashifb4754ed2017-12-05 17:27:58 -05001321 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001322 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001323 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001324 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001325 here, it will generate an error. Each value in this dictionary
1326 is another dictionary containing metadata:
1327
1328 "default" - Default value if not given
1329 "type" - Data type to convert the text value to. Simple types
1330 supported are "str", "float", "int", "bool" which will get
1331 converted to respective Python data types. "set" and "list"
1332 may also be specified which will split the value by
1333 whitespace (but keep the elements as strings). finally,
1334 "list:<type>" and "set:<type>" may be given which will
1335 perform a type conversion after splitting the value up.
1336 "required" - If true, raise an error if not defined. If false
1337 and "default" isn't specified, a type conversion will be
1338 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001339 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001340 type conversion and default values filled in per valid_keys
1341 """
1342
1343 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001344 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001345 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001346
Anas Nashifb4754ed2017-12-05 17:27:58 -05001347 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001348 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001349 raise ConfigurationError(
1350 self.filename,
1351 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001352 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001353
Anas Nashiffa695d22017-10-04 16:14:27 -04001354 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001355 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001356 # By default, we just concatenate string values of keys
1357 # which appear both in "common" and per-test sections,
1358 # but some keys are handled in adhoc way based on their
1359 # semantics.
1360 if k == "filter":
1361 d[k] = "(%s) and (%s)" % (d[k], v)
1362 else:
1363 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001364 else:
1365 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001366
Andrew Boie08ce5a52016-02-22 13:28:10 -08001367 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001368 if k not in d:
1369 if "required" in kinfo:
1370 required = kinfo["required"]
1371 else:
1372 required = False
1373
1374 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001375 raise ConfigurationError(
1376 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001377 "missing required value for '%s' in test '%s'" %
1378 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001379 else:
1380 if "default" in kinfo:
1381 default = kinfo["default"]
1382 else:
1383 default = self._cast_value("", kinfo["type"])
1384 d[k] = default
1385 else:
1386 try:
1387 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001388 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001389 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001390 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
Anas Nashifd9882382019-12-12 09:58:28 -05001391 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001392
1393 return d
1394
1395
1396class Platform:
1397 """Class representing metadata for a particular platform
1398
Anas Nashifc7406082015-12-13 15:00:31 -05001399 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001400
Anas Nashif83fc06a2019-06-22 11:04:10 -04001401 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
Anas Nashifd9882382019-12-12 09:58:28 -05001402 "scripts", "sanity_chk", "platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001403
Anas Nashif83fc06a2019-06-22 11:04:10 -04001404 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001405 """Constructor.
1406
Andrew Boie6acbe632015-07-17 12:03:52 -07001407 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001408
1409 self.name = ""
1410 self.sanitycheck = True
1411 # if no RAM size is specified by the board, take a default of 128K
1412 self.ram = 128
1413
1414 self.ignore_tags = []
1415 self.default = False
1416 # if no flash size is specified by the board, take a default of 512K
1417 self.flash = 512
1418 self.supported = set()
1419
1420 self.arch = ""
1421 self.type = "na"
1422 self.simulation = "na"
1423 self.supported_toolchains = []
1424 self.env = []
1425 self.env_satisfied = True
1426 self.filter_data = dict()
1427
1428 def load(self, platform_file):
1429 scp = SanityConfigParser(platform_file, self.platform_schema)
1430 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001431 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001432
Anas Nashif255625b2017-12-05 15:08:26 -05001433 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001434 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001435 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001436 self.ram = data.get("ram", 128)
1437 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001438 self.ignore_tags = testing.get("ignore_tags", [])
1439 self.default = testing.get("default", False)
1440 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001441 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001442 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001443 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001444 for item in supp_feature.split(":"):
1445 self.supported.add(item)
1446
Anas Nashif255625b2017-12-05 15:08:26 -05001447 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001448 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001449 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001450 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001451 self.env = data.get("env", [])
1452 self.env_satisfied = True
1453 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001454 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001455 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001456
Andrew Boie6acbe632015-07-17 12:03:52 -07001457 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001458 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001459
Anas Nashif11ee5252019-12-04 12:59:10 -05001460
Anas Nashif83fc06a2019-06-22 11:04:10 -04001461class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001462 """Class representing a test application
1463 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001464
Anas Nashif83fc06a2019-06-22 11:04:10 -04001465 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001466 """TestCase constructor.
1467
Anas Nashif877d3ca2017-12-05 17:39:29 -05001468 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001469 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001470 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001471
Andrew Boie6acbe632015-07-17 12:03:52 -07001472 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001473 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001474 the test case is <workdir>/<name>.
1475
Marc Herbert1c8632c2019-04-15 17:58:45 -07001476 @param testcase_root os.path.abspath() of one of the --testcase-root
1477 @param workdir Sub-directory of testcase_root where the
1478 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001479 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001480 in the test case configuration file. For many test cases that just
1481 define one test, can be anything and is usually "test". This is
1482 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001483 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001484 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001485 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001486 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001487
Anas Nashif83fc06a2019-06-22 11:04:10 -04001488 self.id = ""
1489 self.source_dir = ""
1490 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001491 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001492 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001493
Anas Nashif83fc06a2019-06-22 11:04:10 -04001494 self.type = None
1495 self.tags = None
1496 self.extra_args = None
1497 self.extra_configs = None
1498 self.arch_whitelist = None
1499 self.arch_exclude = None
1500 self.skip = None
1501 self.platform_exclude = None
1502 self.platform_whitelist = None
1503 self.toolchain_exclude = None
1504 self.toolchain_whitelist = None
1505 self.tc_filter = None
1506 self.timeout = 60
1507 self.harness = ""
1508 self.harness_config = {}
1509 self.build_only = True
1510 self.build_on_all = False
1511 self.slow = False
1512 self.min_ram = None
1513 self.depends_on = None
1514 self.min_flash = None
1515 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001516
Anas Nashif83fc06a2019-06-22 11:04:10 -04001517 @staticmethod
1518 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001519
Marc Herbert1c8632c2019-04-15 17:58:45 -07001520 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001521 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001522 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001523 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001524 relative_tc_root = os.path.relpath(canonical_testcase_root,
Anas Nashif11ee5252019-12-04 12:59:10 -05001525 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001526 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001527 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001528
Marc Herbert1c8632c2019-04-15 17:58:45 -07001529 # workdir can be "."
1530 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001531 return unique
1532
Anas Nashif83fc06a2019-06-22 11:04:10 -04001533 @staticmethod
1534 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001535 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001536 # do not match until end-of-line, otherwise we won't allow
1537 # stc_regex below to catch the ones that are declared in the same
1538 # line--as we only search starting the end of this match
1539 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001540 re.MULTILINE)
1541 stc_regex = re.compile(
Anas Nashifd9882382019-12-12 09:58:28 -05001542 br"^\s*" # empy space at the beginning is ok
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001543 # catch the case where it is declared in the same sentence, e.g:
1544 #
1545 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1546 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1547 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
Anas Nashif9091a012019-11-24 09:22:22 -05001548 br"ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?"
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001549 # Consume the argument that becomes the extra testcse
1550 br"\(\s*"
1551 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1552 # _setup_teardown() variant has two extra arguments that we ignore
1553 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1554 br"\s*\)",
1555 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001556 re.MULTILINE)
1557 suite_run_regex = re.compile(
1558 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1559 re.MULTILINE)
1560 achtung_regex = re.compile(
1561 br"(#ifdef|#endif)",
1562 re.MULTILINE)
1563 warnings = None
1564
1565 with open(inf_name) as inf:
Anas Nashif19d67e42019-11-21 11:33:12 -05001566 if os.name == 'nt':
Anas Nashifd9882382019-12-12 09:58:28 -05001567 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
Anas Nashif19d67e42019-11-21 11:33:12 -05001568 else:
Anas Nashifd9882382019-12-12 09:58:28 -05001569 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
1570 'offset': 0}
Anas Nashif19d67e42019-11-21 11:33:12 -05001571
1572 with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001573 # contextlib makes pylint think main_c isn't subscriptable
1574 # pylint: disable=unsubscriptable-object
1575
Anas Nashifaae71d72018-04-21 22:26:48 -05001576 suite_regex_match = suite_regex.search(main_c)
1577 if not suite_regex_match:
1578 # can't find ztest_test_suite, maybe a client, because
1579 # it includes ztest.h
1580 return None, None
1581
1582 suite_run_match = suite_run_regex.search(main_c)
1583 if not suite_run_match:
1584 raise ValueError("can't find ztest_run_test_suite")
1585
1586 achtung_matches = re.findall(
1587 achtung_regex,
1588 main_c[suite_regex_match.end():suite_run_match.start()])
1589 if achtung_matches:
1590 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001591 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001592 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001593 stc_regex,
1594 main_c[suite_regex_match.end():suite_run_match.start()])
Anas Nashifd9882382019-12-12 09:58:28 -05001595 matches = [match.decode().replace("test_", "") for match in _matches]
Anas Nashifaae71d72018-04-21 22:26:48 -05001596 return matches, warnings
1597
1598 def scan_path(self, path):
1599 subcases = []
1600 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1601 try:
1602 _subcases, warnings = self.scan_file(filename)
1603 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001604 logger.error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001605 if _subcases:
1606 subcases += _subcases
1607 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001608 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashif434995c2019-12-01 13:55:11 -05001609 for filename in glob.glob(os.path.join(path, "*.c")):
1610 try:
1611 _subcases, warnings = self.scan_file(filename)
1612 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001613 logger.error("%s: %s" % (filename, warnings))
Anas Nashif434995c2019-12-01 13:55:11 -05001614 if _subcases:
1615 subcases += _subcases
1616 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001617 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001618 return subcases
1619
Anas Nashif83fc06a2019-06-22 11:04:10 -04001620 def parse_subcases(self, test_path):
Anas Nashif9091a012019-11-24 09:22:22 -05001621 results = self.scan_path(test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001622 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001623 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001624 self.cases.append(name)
1625
Anas Nashiff16e92c2019-03-31 16:58:12 -04001626 if not results:
1627 self.cases.append(self.id)
1628
Anas Nashif75547e22018-02-24 08:32:14 -06001629 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001630 return self.name
1631
1632
Andrew Boie6acbe632015-07-17 12:03:52 -07001633class TestInstance:
1634 """Class representing the execution of a particular TestCase on a platform
1635
1636 @param test The TestCase object we want to build/execute
1637 @param platform Platform object that we want to build and run against
1638 @param base_outdir Base directory for all test results. The actual
1639 out directory used is <outdir>/<platform>/<test case name>
1640 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001641
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001642 def __init__(self, testcase, platform, outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001643
Anas Nashif83fc06a2019-06-22 11:04:10 -04001644 self.testcase = testcase
1645 self.platform = platform
1646
1647 self.status = None
1648 self.reason = "N/A"
1649 self.metrics = dict()
1650 self.handler = None
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001651 self.outdir = outdir
Anas Nashif83fc06a2019-06-22 11:04:10 -04001652
1653 self.name = os.path.join(platform.name, testcase.name)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001654 self.build_dir = os.path.join(outdir, platform.name, testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001655
Anas Nashif56656842019-12-10 12:26:00 -05001656 self.build_only = True
1657 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001658
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001659 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001660
Marc Herbert0f7255c2019-04-05 14:14:21 -07001661 def __lt__(self, other):
1662 return self.name < other.name
1663
Anas Nashif56656842019-12-10 12:26:00 -05001664 def check_build_or_run(self, build_only=False, enable_slow=False, device_testing=False, fixture=[]):
1665
Anas Nashif19d67e42019-11-21 11:33:12 -05001666 # right now we only support building on windows. running is still work
1667 # in progress.
Anas Nashif19d67e42019-11-21 11:33:12 -05001668 if os.name == 'nt':
Anas Nashif56656842019-12-10 12:26:00 -05001669 self.build_only = True
1670 self.run = False
1671 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001672
Anas Nashif56656842019-12-10 12:26:00 -05001673 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001674
1675 # we asked for build-only on the command line
Anas Nashif56656842019-12-10 12:26:00 -05001676 if build_only or self.testcase.build_only:
1677 self.build_only = True
1678 self.run = False
1679 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001680
1681 # Do not run slow tests:
Anas Nashif56656842019-12-10 12:26:00 -05001682 skip_slow = self.testcase.slow and not enable_slow
Anas Nashif83fc06a2019-06-22 11:04:10 -04001683 if skip_slow:
Anas Nashif56656842019-12-10 12:26:00 -05001684 self.build_only = True
1685 self.run = False
1686 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001687
Anas Nashifd9882382019-12-12 09:58:28 -05001688 runnable = bool(self.testcase.type == "unit" or \
1689 self.platform.type == "native" or \
1690 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1691 device_testing)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001692
1693 if self.platform.simulation == "nsim":
1694 if not find_executable("nsimdrv"):
1695 runnable = False
1696
1697 if self.platform.simulation == "renode":
1698 if not find_executable("renode"):
1699 runnable = False
1700
1701 # console harness allows us to run the test and capture data.
1702 if self.testcase.harness == 'console':
1703
1704 # if we have a fixture that is also being supplied on the
1705 # command-line, then we need to run the test, not just build it.
1706 if "fixture" in self.testcase.harness_config:
Anas Nashifd9882382019-12-12 09:58:28 -05001707 fixture_cfg = self.testcase.harness_config['fixture']
1708 if fixture_cfg in fixture:
Anas Nashif56656842019-12-10 12:26:00 -05001709 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001710 else:
Anas Nashif56656842019-12-10 12:26:00 -05001711 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001712 else:
Anas Nashif56656842019-12-10 12:26:00 -05001713 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001714 elif self.testcase.harness:
Anas Nashif56656842019-12-10 12:26:00 -05001715 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001716 else:
Anas Nashif56656842019-12-10 12:26:00 -05001717 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001718
Anas Nashif56656842019-12-10 12:26:00 -05001719 self.build_only = not (not _build_only and runnable)
1720 self.run = not self.build_only
1721 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001722
Anas Nashif56656842019-12-10 12:26:00 -05001723 def create_overlay(self, platform, enable_asan=False, enable_coverage=False, coverage_platform=[]):
Marc Herbertc7633de2019-07-06 15:52:31 -07001724 # Create this in a "sanitycheck/" subdirectory otherwise this
1725 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1726 # will silently give that second time precedence over any
1727 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001728 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001729 os.makedirs(subdir, exist_ok=True)
1730 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashif56656842019-12-10 12:26:00 -05001731
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001732 with open(file, "w") as f:
1733 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001734
Anas Nashif83fc06a2019-06-22 11:04:10 -04001735 if self.testcase.extra_configs:
1736 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001737
Anas Nashif56656842019-12-10 12:26:00 -05001738 if enable_coverage:
1739 if platform.name in coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001740 content = content + "\nCONFIG_COVERAGE=y"
1741
Anas Nashif56656842019-12-10 12:26:00 -05001742 if enable_asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001743 if platform.type == "native":
1744 content = content + "\nCONFIG_ASAN=y"
1745
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001746 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001747
Andrew Boie6acbe632015-07-17 12:03:52 -07001748 def calculate_sizes(self):
1749 """Get the RAM/ROM sizes of a test case.
1750
1751 This can only be run after the instance has been executed by
1752 MakeGenerator, otherwise there won't be any binaries to measure.
1753
1754 @return A SizeCalculator object
1755 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001756 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1757 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001758 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001759 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001760 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001761
1762 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001763
1764 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001765 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001766
1767
Anas Nashif83fc06a2019-06-22 11:04:10 -04001768class CMake():
Anas Nashif83fc06a2019-06-22 11:04:10 -04001769 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1770 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1771
1772 def __init__(self, testcase, platform, source_dir, build_dir):
1773
1774 self.cwd = None
1775 self.capture_output = True
1776
1777 self.defconfig = {}
1778 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001779
1780 self.instance = None
1781 self.testcase = testcase
1782 self.platform = platform
1783 self.source_dir = source_dir
1784 self.build_dir = build_dir
1785 self.log = "build.log"
1786
1787 def parse_generated(self):
1788 self.defconfig = {}
1789 return {}
1790
1791 def run_build(self, args=[]):
1792
Anas Nashif7a361b82019-12-06 11:37:40 -05001793 logger.debug("Building %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001794
1795 cmake_args = []
1796 cmake_args.extend(args)
1797 cmake = shutil.which('cmake')
1798 cmd = [cmake] + cmake_args
1799 kwargs = dict()
1800
1801 if self.capture_output:
1802 kwargs['stdout'] = subprocess.PIPE
1803 # CMake sends the output of message() to stderr unless it's STATUS
1804 kwargs['stderr'] = subprocess.STDOUT
1805
1806 if self.cwd:
1807 kwargs['cwd'] = self.cwd
1808
1809 p = subprocess.Popen(cmd, **kwargs)
1810 out, _ = p.communicate()
1811
1812 results = {}
1813 if p.returncode == 0:
Anas Nashifd9882382019-12-12 09:58:28 -05001814 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001815
1816 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001817 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1818
1819 if out:
1820 log_msg = out.decode(sys.getdefaultencoding())
1821 with open(os.path.join(self.build_dir, self.log), "a") as log:
1822 log.write(log_msg)
1823
1824 else:
1825 return None
1826 else:
1827 # A real error occurred, raise an exception
1828 if out:
1829 log_msg = out.decode(sys.getdefaultencoding())
1830 with open(os.path.join(self.build_dir, self.log), "a") as log:
1831 log.write(log_msg)
1832
1833 overflow_flash = "region `FLASH' overflowed by"
1834 overflow_ram = "region `RAM' overflowed by"
1835
1836 if log_msg:
1837 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05001838 logger.debug("RAM/ROM Overflow")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001839 self.instance.status = "skipped"
1840 self.instance.reason = "overflow"
1841 else:
1842 self.instance.status = "failed"
1843 self.instance.reason = "Build failure"
1844
1845 results = {
Anas Nashifd9882382019-12-12 09:58:28 -05001846 "returncode": p.returncode,
1847 "instance": self.instance,
1848 }
Anas Nashif83fc06a2019-06-22 11:04:10 -04001849
1850 return results
1851
1852 def run_cmake(self, args=[]):
1853
Anas Nashif11ee5252019-12-04 12:59:10 -05001854 ldflags = "-Wl,--fatal-warnings"
Anas Nashifd9882382019-12-12 09:58:28 -05001855 logger.debug("Running cmake on %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001856
Anas Nashif11ee5252019-12-04 12:59:10 -05001857 # fixme: add additional cflags based on options
Anas Nashifa5984ab2019-10-22 07:36:24 -07001858 cmake_args = [
Anas Nashifd9882382019-12-12 09:58:28 -05001859 '-B{}'.format(self.build_dir),
1860 '-S{}'.format(self.source_dir),
1861 '-DEXTRA_CFLAGS="-Werror ',
1862 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1863 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1864 '-G{}'.format(get_generator()[1])
1865 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001866
Anas Nashifd91f9932019-11-30 10:15:23 -05001867 if options.cmake_only:
1868 cmake_args.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=1")
1869
Anas Nashif83fc06a2019-06-22 11:04:10 -04001870 args = ["-D{}".format(a.replace('"', '')) for a in args]
1871 cmake_args.extend(args)
1872
1873 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1874 cmake_args.extend(cmake_opts)
1875
1876 cmake = shutil.which('cmake')
1877 cmd = [cmake] + cmake_args
1878 kwargs = dict()
1879
1880 if self.capture_output:
1881 kwargs['stdout'] = subprocess.PIPE
1882 # CMake sends the output of message() to stderr unless it's STATUS
1883 kwargs['stderr'] = subprocess.STDOUT
1884
1885 if self.cwd:
1886 kwargs['cwd'] = self.cwd
1887
1888 p = subprocess.Popen(cmd, **kwargs)
1889 out, _ = p.communicate()
1890
1891 if p.returncode == 0:
1892 filter_results = self.parse_generated()
Anas Nashifd9882382019-12-12 09:58:28 -05001893 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001894
1895 results = {'msg': msg, 'filter': filter_results}
1896
1897 else:
1898 self.instance.status = "failed"
1899 self.instance.reason = "Cmake build failure"
1900 results = {"returncode": p.returncode}
1901
Anas Nashif83fc06a2019-06-22 11:04:10 -04001902 if out:
1903 with open(os.path.join(self.build_dir, self.log), "a") as log:
1904 log_msg = out.decode(sys.getdefaultencoding())
1905 log.write(log_msg)
1906
1907 return results
1908
1909
1910class FilterBuilder(CMake):
1911
1912 def __init__(self, testcase, platform, source_dir, build_dir):
1913 super().__init__(testcase, platform, source_dir, build_dir)
1914
1915 self.log = "config-sanitycheck.log"
1916
1917 def parse_generated(self):
1918
1919 if self.platform.name == "unit_testing":
1920 return {}
1921
Anas Nashif83fc06a2019-06-22 11:04:10 -04001922 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001923 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1924
1925 with open(defconfig_path, "r") as fp:
1926 defconfig = {}
1927 for line in fp.readlines():
1928 m = self.config_re.match(line)
1929 if not m:
1930 if line.strip() and not line.startswith("#"):
1931 sys.stderr.write("Unrecognized line %s\n" % line)
1932 continue
1933 defconfig[m.group(1)] = m.group(2).strip()
1934
1935 self.defconfig = defconfig
1936
1937 cmake_conf = {}
1938 try:
1939 cache = CMakeCache.from_file(cmake_cache_path)
1940 except FileNotFoundError:
1941 cache = {}
1942
1943 for k in iter(cache):
1944 cmake_conf[k.name] = k.value
1945
1946 self.cmake_cache = cmake_conf
1947
Anas Nashif83fc06a2019-06-22 11:04:10 -04001948 filter_data = {
1949 "ARCH": self.platform.arch,
1950 "PLATFORM": self.platform.name
1951 }
1952 filter_data.update(os.environ)
1953 filter_data.update(self.defconfig)
1954 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001955
Anas Nashif556f3cb2019-11-05 15:36:15 -08001956 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001957 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001958 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001959 if os.path.exists(dts_path):
1960 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1961 else:
1962 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001963 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1964
Anas Nashif83fc06a2019-06-22 11:04:10 -04001965 except (ValueError, SyntaxError) as se:
1966 sys.stderr.write(
1967 "Failed processing %s\n" % self.testcase.yamlfile)
1968 raise se
1969
1970 if not res:
1971 return {os.path.join(self.platform.name, self.testcase.name): True}
1972 else:
1973 return {os.path.join(self.platform.name, self.testcase.name): False}
1974 else:
1975 self.platform.filter_data = filter_data
1976 return filter_data
1977
1978
1979class ProjectBuilder(FilterBuilder):
1980
Anas Nashif56656842019-12-10 12:26:00 -05001981 def __init__(self, suite, instance, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001982 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1983
1984 self.log = "build.log"
1985 self.instance = instance
1986 self.suite = suite
1987
Anas Nashif56656842019-12-10 12:26:00 -05001988 self.lsan = kwargs.get('lsan', False)
1989 self.asan = kwargs.get('asan', False)
1990 self.valgrind = kwargs.get('valgrind', False)
1991 self.extra_args = kwargs.get('extra_args', [])
1992 self.device_testing = kwargs.get('device_testing', False)
1993 self.cmake_only = kwargs.get('cmake_only', False)
1994 self.coverage = kwargs.get('coverage', False)
Anas Nashife9eb0092019-12-10 16:31:22 -05001995 self.inline_logs = kwargs.get('inline_logs', False)
1996
Anas Nashifd9882382019-12-12 09:58:28 -05001997 @staticmethod
1998 def log_info(filename, inline_logs):
Andrew Boiebd137102020-01-02 18:56:17 -08001999 filename = os.path.abspath(os.path.realpath(filename))
Anas Nashife9eb0092019-12-10 16:31:22 -05002000 if inline_logs:
2001 logger.info("{:-^100}".format(filename))
2002
2003 try:
2004 with open(filename) as fp:
2005 data = fp.read()
2006 except Exception as e:
2007 data = "Unable to read log data (%s)\n" % (str(e))
2008
2009 logger.error(data)
2010
2011 logger.info("{:-^100}".format(filename))
2012 else:
Anas Nashif97445682019-12-16 09:36:40 -05002013 logger.error("see: " + Fore.YELLOW + filename + Fore.RESET)
Anas Nashife9eb0092019-12-10 16:31:22 -05002014
2015 def log_info_file(self, inline_logs):
2016 build_dir = self.instance.build_dir
2017 h_log = "{}/handler.log".format(build_dir)
2018 b_log = "{}/build.log".format(build_dir)
2019 v_log = "{}/valgrind.log".format(build_dir)
2020 d_log = "{}/device.log".format(build_dir)
2021
2022 if os.path.exists(v_log) and "Valgrind" in self.instance.reason:
2023 self.log_info("{}".format(v_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05002024 elif os.path.exists(d_log) and os.path.getsize(d_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05002025 self.log_info("{}".format(d_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05002026 elif os.path.exists(h_log) and os.path.getsize(h_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05002027 self.log_info("{}".format(h_log), inline_logs)
2028 else:
2029 self.log_info("{}".format(b_log), inline_logs)
Anas Nashif56656842019-12-10 12:26:00 -05002030
Anas Nashif83fc06a2019-06-22 11:04:10 -04002031 def setup_handler(self):
2032
2033 instance = self.instance
2034 args = []
2035
2036 # FIXME: Needs simplification
2037 if instance.platform.simulation == "qemu":
2038 instance.handler = QEMUHandler(instance, "qemu")
2039 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
2040 instance.handler.call_make_run = True
2041 elif instance.testcase.type == "unit":
2042 instance.handler = BinaryHandler(instance, "unit")
2043 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
2044 elif instance.platform.type == "native":
Anas Nashif6c0e1702019-12-05 15:24:52 -05002045 handler = BinaryHandler(instance, "native")
2046
Anas Nashif56656842019-12-10 12:26:00 -05002047 handler.asan = self.asan
2048 handler.valgrind = self.valgrind
2049 handler.lsan = self.lsan
2050 handler.coverage = self.coverage
Anas Nashif6c0e1702019-12-05 15:24:52 -05002051
2052 handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
2053 instance.handler = handler
Anas Nashif83fc06a2019-06-22 11:04:10 -04002054 elif instance.platform.simulation == "nsim":
2055 if find_executable("nsimdrv"):
2056 instance.handler = BinaryHandler(instance, "nsim")
2057 instance.handler.call_make_run = True
2058 elif instance.platform.simulation == "renode":
2059 if find_executable("renode"):
2060 instance.handler = BinaryHandler(instance, "renode")
2061 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
2062 instance.handler.call_make_run = True
Anas Nashif56656842019-12-10 12:26:00 -05002063 elif self.device_testing:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002064 instance.handler = DeviceHandler(instance, "device")
2065
2066 if instance.handler:
2067 instance.handler.args = args
2068
2069 def process(self, message):
2070 op = message.get('op')
2071
2072 if not self.instance.handler:
2073 self.setup_handler()
2074
2075 # The build process, call cmake and build with configured generator
2076 if op == "cmake":
2077 results = self.cmake()
2078 if self.instance.status == "failed":
2079 pipeline.put({"op": "report", "test": self.instance})
Anas Nashif56656842019-12-10 12:26:00 -05002080 elif self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002081 pipeline.put({"op": "report", "test": self.instance})
2082 else:
2083 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
Anas Nashif7a361b82019-12-06 11:37:40 -05002084 logger.debug("filtering %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002085 self.instance.status = "skipped"
2086 self.instance.reason = "filter"
2087 pipeline.put({"op": "report", "test": self.instance})
2088 else:
2089 pipeline.put({"op": "build", "test": self.instance})
2090
Anas Nashif83fc06a2019-06-22 11:04:10 -04002091 elif op == "build":
Anas Nashifd9882382019-12-12 09:58:28 -05002092 logger.debug("build test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002093 results = self.build()
2094
2095 if results.get('returncode', 1) > 0:
2096 pipeline.put({"op": "report", "test": self.instance})
2097 else:
2098 if self.instance.run:
2099 pipeline.put({"op": "run", "test": self.instance})
2100 else:
2101 pipeline.put({"op": "report", "test": self.instance})
2102 # Run the generated binary using one of the supported handlers
2103 elif op == "run":
Anas Nashifd9882382019-12-12 09:58:28 -05002104 logger.debug("run test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002105 self.run()
2106 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002107 pipeline.put({
2108 "op": "report",
2109 "test": self.instance,
2110 "state": "executed",
2111 "status": self.instance.status,
Stephanos Ioannidis93889002019-11-11 20:31:03 +09002112 "reason": self.instance.reason}
Anas Nashifd9882382019-12-12 09:58:28 -05002113 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002114
2115 # Report results and output progress to screen
2116 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002117 with report_lock:
2118 self.report_out()
2119
Anas Nashif83fc06a2019-06-22 11:04:10 -04002120 def report_out(self):
2121 total_tests_width = len(str(self.suite.total_tests))
2122 self.suite.total_done += 1
2123 instance = self.instance
2124
2125 if instance.status in ["failed", "timeout"]:
2126 self.suite.total_failed += 1
Anas Nashif97445682019-12-16 09:36:40 -05002127 if VERBOSE:
2128 status = Fore.RED + "FAILED " + Fore.RESET + instance.reason
Anas Nashif83fc06a2019-06-22 11:04:10 -04002129 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002130 print("")
2131 logger.error(
2132 "{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002133 instance.platform.name,
2134 instance.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05002135 Fore.RED,
2136 Fore.RESET,
Anas Nashif7a361b82019-12-06 11:37:40 -05002137 instance.reason))
Anas Nashifc1ea4522019-10-11 07:32:45 -07002138 if not VERBOSE:
Anas Nashife9eb0092019-12-10 16:31:22 -05002139 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002140 elif instance.status == "skipped":
2141 self.suite.total_skipped += 1
Anas Nashif97445682019-12-16 09:36:40 -05002142 status = Fore.YELLOW + "SKIPPED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002143 else:
Anas Nashif97445682019-12-16 09:36:40 -05002144 status = Fore.GREEN + "PASSED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002145
Anas Nashif97445682019-12-16 09:36:40 -05002146 if VERBOSE:
Anas Nashif56656842019-12-10 12:26:00 -05002147 if self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002148 more_info = "cmake"
2149 elif instance.status == "skipped":
2150 more_info = instance.reason
2151 else:
2152 if instance.handler and instance.run:
2153 more_info = instance.handler.type_str
2154 htime = instance.handler.duration
2155 if htime:
2156 more_info += " {:.3f}s".format(htime)
2157 else:
2158 more_info = "build"
2159
Anas Nashif7a361b82019-12-06 11:37:40 -05002160 logger.info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002161 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2162 instance.testcase.name, status, more_info))
2163
2164 if instance.status in ["failed", "timeout"]:
Anas Nashifd9882382019-12-12 09:58:28 -05002165 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002166 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002167 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 -05002168 Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002169 self.suite.total_done,
2170 self.suite.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002171 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002172 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
Anas Nashif97445682019-12-16 09:36:40 -05002173 Fore.YELLOW if self.suite.total_skipped > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002174 self.suite.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002175 Fore.RESET,
2176 Fore.RED if self.suite.total_failed > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002177 self.suite.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002178 Fore.RESET
Anas Nashifd9882382019-12-12 09:58:28 -05002179 )
2180 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002181 sys.stdout.flush()
2182
2183 def cmake(self):
2184
2185 instance = self.instance
2186 args = self.testcase.extra_args[:]
Anas Nashif56656842019-12-10 12:26:00 -05002187 args += self.extra_args
Anas Nashif83fc06a2019-06-22 11:04:10 -04002188
2189 if instance.handler:
2190 args += instance.handler.args
2191
2192 # merge overlay files into one variable
2193 overlays = ""
2194 idx = 0
2195 for arg in args:
2196 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2197 if match:
2198 overlays += match.group(1)
2199 del args[idx]
2200 idx += 1
2201
Anas Nashif56656842019-12-10 12:26:00 -05002202 if (self.testcase.extra_configs or self.coverage or
2203 self.asan):
Anas Nashifd9882382019-12-12 09:58:28 -05002204 args.append("OVERLAY_CONFIG=\"%s %s\"" % (overlays,
2205 os.path.join(instance.build_dir,
2206 "sanitycheck", "testcase_extra.conf")))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002207
2208 results = self.run_cmake(args)
2209 return results
2210
2211 def build(self):
2212 results = self.run_build(['--build', self.build_dir])
2213 return results
2214
2215 def run(self):
2216
2217 instance = self.instance
2218
2219 if instance.handler.type_str == "device":
2220 instance.handler.suite = self.suite
2221
2222 instance.handler.handle()
2223
Anas Nashif83fc06a2019-06-22 11:04:10 -04002224 sys.stdout.flush()
2225
2226
2227pipeline = queue.LifoQueue()
2228
Anas Nashif11ee5252019-12-04 12:59:10 -05002229
Anas Nashif83fc06a2019-06-22 11:04:10 -04002230class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2231 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2232 calls to submit() once the limit given as "bound" work items are queued for
2233 execution.
2234 :param bound: Integer - the maximum number of items in the work queue
2235 :param max_workers: Integer - the size of the thread pool
2236 """
Anas Nashifd9882382019-12-12 09:58:28 -05002237
Anas Nashif83fc06a2019-06-22 11:04:10 -04002238 def __init__(self, bound, max_workers, **kwargs):
2239 super().__init__(max_workers)
Anas Nashif11ee5252019-12-04 12:59:10 -05002240 # self.executor = ThreadPoolExecutor(max_workers=max_workers)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002241 self.semaphore = BoundedSemaphore(bound + max_workers)
2242
2243 def submit(self, fn, *args, **kwargs):
2244 self.semaphore.acquire()
2245 try:
2246 future = super().submit(fn, *args, **kwargs)
Anas Nashif11ee5252019-12-04 12:59:10 -05002247 except Exception:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002248 self.semaphore.release()
2249 raise
2250 else:
2251 future.add_done_callback(lambda x: self.semaphore.release())
2252 return future
2253
Andrew Boie6acbe632015-07-17 12:03:52 -07002254
2255class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002256 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002257 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002258
Anas Nashif83fc06a2019-06-22 11:04:10 -04002259 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002260 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002261 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002262
Anas Nashif37f9dc52018-02-23 08:53:46 -06002263 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002264
2265 self.roots = testcase_roots
2266 if not isinstance(board_root_list, list):
Anas Nashif11ee5252019-12-04 12:59:10 -05002267 self.board_roots = [board_root_list]
Anas Nashif83fc06a2019-06-22 11:04:10 -04002268 else:
2269 self.board_roots = board_root_list
2270
Anas Nashif56656842019-12-10 12:26:00 -05002271 # Testsuite Options
2272 self.coverage_platform = []
2273 self.build_only = False
2274 self.cmake_only = False
2275 self.enable_slow = False
2276 self.device_testing = False
2277 self.fixture = []
2278 self.enable_coverage = False
2279 self.enable_lsan = False
2280 self.enable_asan = False
2281 self.enable_valgrind = False
2282 self.extra_args = []
Anas Nashife9eb0092019-12-10 16:31:22 -05002283 self.inline_logs = False
Anas Nashifc5ee3952019-12-10 16:38:45 -05002284 self.enable_sizes_report = False
Anas Nashif56656842019-12-10 12:26:00 -05002285
Andrew Boie6acbe632015-07-17 12:03:52 -07002286 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002287 self.testcases = {}
2288 self.platforms = []
Anas Nashif5f908822019-11-25 08:19:25 -05002289 self.selected_platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002290 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002291 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002292 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002293 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002294 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002295
Anas Nashif11ee5252019-12-04 12:59:10 -05002296 self.total_tests = 0 # number of test instances
2297 self.total_cases = 0 # number of test cases
2298 self.total_done = 0 # tests completed
Anas Nashif83fc06a2019-06-22 11:04:10 -04002299 self.total_failed = 0
2300 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002301
Anas Nashif83fc06a2019-06-22 11:04:10 -04002302 self.total_platforms = 0
2303 self.start_time = 0
2304 self.duration = 0
2305 self.warnings = 0
2306 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002307
Anas Nashif83fc06a2019-06-22 11:04:10 -04002308 # hardcoded for now
2309 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002310
Anas Nashif56656842019-12-10 12:26:00 -05002311 def config(self):
2312 logger.info("coverage platform: {}".format(self.coverage_platform))
2313
Anas Nashiff16ed8e2019-12-09 16:22:27 -05002314 # Debug Functions
2315 @staticmethod
2316 def info(what):
2317 sys.stdout.write(what + "\n")
2318 sys.stdout.flush()
2319
Anas Nashif83fc06a2019-06-22 11:04:10 -04002320 def update(self):
2321 self.total_tests = len(self.instances)
2322 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002323
Andrew Boie6acbe632015-07-17 12:03:52 -07002324 def compare_metrics(self, filename):
2325 # name, datatype, lower results better
2326 interesting_metrics = [("ram_size", int, True),
2327 ("rom_size", int, True)]
2328
Andrew Boie6acbe632015-07-17 12:03:52 -07002329 if not os.path.exists(filename):
Anas Nashif7a361b82019-12-06 11:37:40 -05002330 logger.info("Cannot compare metrics, %s not found" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -07002331 return []
2332
2333 results = []
2334 saved_metrics = {}
2335 with open(filename) as fp:
2336 cr = csv.DictReader(fp)
2337 for row in cr:
2338 d = {}
2339 for m, _, _ in interesting_metrics:
2340 d[m] = row[m]
2341 saved_metrics[(row["test"], row["platform"])] = d
2342
Anas Nashif83fc06a2019-06-22 11:04:10 -04002343 for instance in self.instances.values():
2344 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002345 if mkey not in saved_metrics:
2346 continue
2347 sm = saved_metrics[mkey]
2348 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002349 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002350 continue
2351 if sm[metric] == "":
2352 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002353 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002354 if delta == 0:
2355 continue
Anas Nashifd9882382019-12-12 09:58:28 -05002356 results.append((instance, metric, instance.metrics.get(metric, 0), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002357 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002358 return results
2359
Anas Nashif83fc06a2019-06-22 11:04:10 -04002360 def misc_reports(self, report, show_footprint, all_deltas,
2361 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002362
Anas Nashif83fc06a2019-06-22 11:04:10 -04002363 if not report:
2364 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002365
Anas Nashif83fc06a2019-06-22 11:04:10 -04002366 deltas = self.compare_metrics(report)
2367 warnings = 0
2368 if deltas and show_footprint:
2369 for i, metric, value, delta, lower_better in deltas:
2370 if not all_deltas and ((delta < 0 and lower_better) or
Anas Nashifd9882382019-12-12 09:58:28 -05002371 (delta > 0 and not lower_better)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002372 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002373
Anas Nashif83fc06a2019-06-22 11:04:10 -04002374 percentage = (float(delta) / float(value - delta))
2375 if not all_deltas and (percentage <
Anas Nashifd9882382019-12-12 09:58:28 -05002376 (footprint_threshold / 100.0)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002377 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002378
Anas Nashifd9882382019-12-12 09:58:28 -05002379 logger.info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Anas Nashif97445682019-12-16 09:36:40 -05002380 i.platform.name, i.testcase.name, Fore.YELLOW,
2381 "INFO" if all_deltas else "WARNING", Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002382 metric, delta, value, percentage))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002383 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002384
Anas Nashif83fc06a2019-06-22 11:04:10 -04002385 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05002386 logger.warning("Deltas based on metrics from last %s" %
Anas Nashifd9882382019-12-12 09:58:28 -05002387 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002388
Anas Nashif83fc06a2019-06-22 11:04:10 -04002389 def summary(self, unrecognized_sections):
2390 failed = 0
2391 for instance in self.instances.values():
2392 if instance.status == "failed":
2393 failed += 1
2394 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
Anas Nashif7a361b82019-12-06 11:37:40 -05002395 logger.error("%sFAILED%s: %s has unrecognized binary sections: %s" %
Anas Nashif97445682019-12-16 09:36:40 -05002396 (Fore.RED, Fore.RESET, instance.name,
Anas Nashifd9882382019-12-12 09:58:28 -05002397 str(instance.metrics.get("unrecognized", []))))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002398 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002399
Anas Nashif83fc06a2019-06-22 11:04:10 -04002400 if self.total_tests and self.total_tests != self.total_skipped:
Anas Nashifd9882382019-12-12 09:58:28 -05002401 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped) / float(
2402 self.total_tests - self.total_skipped))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002403 else:
2404 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002405
Anas Nashifd9882382019-12-12 09:58:28 -05002406 logger.info(
2407 "{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
Anas Nashif97445682019-12-16 09:36:40 -05002408 Fore.RED if failed else Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002409 self.total_tests - self.total_failed - self.total_skipped,
2410 self.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002411 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002412 pass_rate,
Anas Nashif97445682019-12-16 09:36:40 -05002413 Fore.RED if self.total_failed else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002414 self.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002415 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002416 self.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002417 Fore.YELLOW if self.warnings else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002418 self.warnings,
Anas Nashif97445682019-12-16 09:36:40 -05002419 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002420 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002421
Anas Nashif83fc06a2019-06-22 11:04:10 -04002422 self.total_platforms = len(self.platforms)
2423 if self.platforms:
Anas Nashif7a361b82019-12-06 11:37:40 -05002424 logger.info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002425 self.total_cases,
Anas Nashif5f908822019-11-25 08:19:25 -05002426 len(self.selected_platforms),
Anas Nashif83fc06a2019-06-22 11:04:10 -04002427 self.total_platforms,
Anas Nashif5f908822019-11-25 08:19:25 -05002428 (100 * len(self.selected_platforms) / len(self.platforms))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002429 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002430
Anas Nashif56656842019-12-10 12:26:00 -05002431 def save_reports(self, name, report_dir, no_update, release, only_failed):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002432 if not self.instances:
2433 return
Anas Nashif61e21632018-04-08 13:30:16 -05002434
Anas Nashif56656842019-12-10 12:26:00 -05002435 if name:
2436 report_name = name
2437 else:
2438 report_name = "sanitycheck"
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002439
Anas Nashif56656842019-12-10 12:26:00 -05002440 if report_dir:
2441 os.makedirs(report_dir, exist_ok=True)
2442 filename = os.path.join(report_dir, report_name)
2443 outdir = report_dir
Anas Nashif83fc06a2019-06-22 11:04:10 -04002444 else:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002445 filename = os.path.join(self.outdir, report_name)
2446 outdir = self.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002447
Anas Nashif56656842019-12-10 12:26:00 -05002448 if not no_update:
2449 self.xunit_report(filename + ".xml", only_failed)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002450 self.csv_report(filename + ".csv")
2451 self.target_report(outdir)
2452 if self.discards:
2453 self.discard_report(filename + "_discard.csv")
2454
Anas Nashif56656842019-12-10 12:26:00 -05002455 if release:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002456 self.csv_report(RELEASE_DATA)
2457
Anas Nashif83fc06a2019-06-22 11:04:10 -04002458 def add_configurations(self):
2459
2460 for board_root in self.board_roots:
2461 board_root = os.path.abspath(board_root)
2462
Anas Nashif7a361b82019-12-06 11:37:40 -05002463 logger.debug("Reading platform configuration files under %s..." %
Anas Nashifd9882382019-12-12 09:58:28 -05002464 board_root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002465
2466 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
Anas Nashif7a361b82019-12-06 11:37:40 -05002467 logger.debug("Found plaform configuration " + file)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002468 try:
2469 platform = Platform()
2470 platform.load(file)
2471 if platform.sanitycheck:
2472 self.platforms.append(platform)
2473 if platform.default:
2474 self.default_platforms.append(platform.name)
2475
2476 except RuntimeError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002477 logger.error("E: %s: can't load: %s" % (file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002478 self.load_errors += 1
2479
Anas Nashifeabaa7f2019-11-18 07:49:17 -08002480 def get_all_tests(self):
2481 tests = []
2482 for _, tc in self.testcases.items():
2483 for case in tc.cases:
2484 tests.append(case)
2485
2486 return tests
2487
Anas Nashif83fc06a2019-06-22 11:04:10 -04002488 @staticmethod
2489 def get_toolchain():
2490 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2491 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2492
2493 if toolchain == "gccarmemb":
2494 # Remove this translation when gccarmemb is no longer supported.
2495 toolchain = "gnuarmemb"
2496
Anas Nashifb4bdd662018-08-15 17:12:28 -05002497 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002498 if not toolchain:
2499 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002500 except Exception as e:
2501 print(str(e))
2502 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002503
Anas Nashif83fc06a2019-06-22 11:04:10 -04002504 return toolchain
2505
Anas Nashif83fc06a2019-06-22 11:04:10 -04002506 def add_testcases(self):
2507 for root in self.roots:
2508 root = os.path.abspath(root)
2509
Anas Nashifd9882382019-12-12 09:58:28 -05002510 logger.debug("Reading test case configuration files under %s..." % root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002511
2512 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
Anas Nashif7a361b82019-12-06 11:37:40 -05002513 logger.debug("scanning %s" % dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002514 if 'sample.yaml' in filenames:
2515 filename = 'sample.yaml'
2516 elif 'testcase.yaml' in filenames:
2517 filename = 'testcase.yaml'
2518 else:
2519 continue
2520
Anas Nashif7a361b82019-12-06 11:37:40 -05002521 logger.debug("Found possible test case in " + dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002522
2523 dirnames[:] = []
2524 tc_path = os.path.join(dirpath, filename)
2525 self.add_testcase(tc_path, root)
2526
2527 def add_testcase(self, tc_data_file, root):
2528 try:
2529 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2530 parsed_data.load()
2531
2532 tc_path = os.path.dirname(tc_data_file)
2533 workdir = os.path.relpath(tc_path, root)
2534
2535 for name in parsed_data.tests.keys():
2536 tc = TestCase()
2537 tc.name = tc.get_unique(root, workdir, name)
2538
2539 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2540
2541 tc.source_dir = tc_path
2542 tc.yamlfile = tc_data_file
2543
2544 tc.id = name
2545 tc.type = tc_dict["type"]
2546 tc.tags = tc_dict["tags"]
2547 tc.extra_args = tc_dict["extra_args"]
2548 tc.extra_configs = tc_dict["extra_configs"]
2549 tc.arch_whitelist = tc_dict["arch_whitelist"]
2550 tc.arch_exclude = tc_dict["arch_exclude"]
2551 tc.skip = tc_dict["skip"]
2552 tc.platform_exclude = tc_dict["platform_exclude"]
2553 tc.platform_whitelist = tc_dict["platform_whitelist"]
2554 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2555 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2556 tc.tc_filter = tc_dict["filter"]
2557 tc.timeout = tc_dict["timeout"]
2558 tc.harness = tc_dict["harness"]
2559 tc.harness_config = tc_dict["harness_config"]
2560 tc.build_only = tc_dict["build_only"]
2561 tc.build_on_all = tc_dict["build_on_all"]
2562 tc.slow = tc_dict["slow"]
2563 tc.min_ram = tc_dict["min_ram"]
2564 tc.depends_on = tc_dict["depends_on"]
2565 tc.min_flash = tc_dict["min_flash"]
2566 tc.extra_sections = tc_dict["extra_sections"]
2567
2568 tc.parse_subcases(tc_path)
2569
2570 if tc.name:
2571 self.testcases[tc.name] = tc
2572
2573 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002574 logger.error("%s: can't load (skipping): %s" % (tc_data_file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002575 self.load_errors += 1
2576 return False
2577
2578 return True
2579
Anas Nashif83fc06a2019-06-22 11:04:10 -04002580 def get_platform(self, name):
2581 selected_platform = None
2582 for platform in self.platforms:
2583 if platform.name == name:
2584 selected_platform = platform
2585 break
2586 return selected_platform
2587
2588 def get_last_failed(self):
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002589 last_run = os.path.join(self.outdir, "sanitycheck.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002590 try:
2591 if not os.path.exists(last_run):
Anas Nashifd9882382019-12-12 09:58:28 -05002592 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" % last_run)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002593 except Exception as e:
2594 print(str(e))
2595 sys.exit(2)
2596
2597 total_tests = 0
2598 with open(last_run, "r") as fp:
2599 cr = csv.DictReader(fp)
2600 instance_list = []
2601 for row in cr:
2602 total_tests += 1
2603 if row["passed"] == "True":
2604 continue
2605 test = row["test"]
2606 platform = self.get_platform(row["platform"])
2607 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002608 instance.check_build_or_run(
2609 self.build_only,
2610 self.enable_slow,
2611 self.device_testing,
2612 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002613 )
Anas Nashif56656842019-12-10 12:26:00 -05002614 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002615 instance_list.append(instance)
2616 self.add_instances(instance_list)
2617
2618 tests_to_run = len(self.instances)
Anas Nashifd9882382019-12-12 09:58:28 -05002619 logger.info("%d tests passed already, retrying %d tests" % (total_tests - tests_to_run, tests_to_run))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002620
2621 def load_from_file(self, file):
2622 try:
2623 if not os.path.exists(file):
2624 raise SanityRuntimeError(
2625 "Couldn't find input file with list of tests.")
2626 except Exception as e:
2627 print(str(e))
2628 sys.exit(2)
2629
2630 with open(file, "r") as fp:
2631 cr = csv.DictReader(fp)
2632 instance_list = []
2633 for row in cr:
2634 if row["arch"] == "arch":
2635 continue
2636 test = row["test"]
2637 platform = self.get_platform(row["platform"])
2638 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002639 instance.check_build_or_run(
2640 self.build_only,
2641 self.enable_slow,
2642 self.device_testing,
2643 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002644 )
Anas Nashif56656842019-12-10 12:26:00 -05002645 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002646 instance_list.append(instance)
2647 self.add_instances(instance_list)
2648
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002649 def apply_filters(self, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002650
2651 toolchain = self.get_toolchain()
2652
2653 discards = {}
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002654 platform_filter = kwargs.get('platform')
2655 testcase_filter = kwargs.get('run_individual_tests')
2656 arch_filter = kwargs.get('arch')
2657 tag_filter = kwargs.get('tag')
2658 exclude_tag = kwargs.get('exclude_tag')
2659 all_filter = kwargs.get('all')
2660 device_testing_filter = kwargs.get('device_testing')
2661 force_toolchain = kwargs.get('force_toolchain')
Anas Nashif83fc06a2019-06-22 11:04:10 -04002662
Anas Nashif7a361b82019-12-06 11:37:40 -05002663 logger.debug("platform filter: " + str(platform_filter))
2664 logger.debug(" arch_filter: " + str(arch_filter))
2665 logger.debug(" tag_filter: " + str(tag_filter))
2666 logger.debug(" exclude_tag: " + str(exclude_tag))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002667
2668 default_platforms = False
2669
2670 if platform_filter:
2671 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2672 else:
2673 platforms = self.platforms
2674
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002675 if all_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002676 logger.info("Selecting all possible platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002677 # When --all used, any --platform arguments ignored
2678 platform_filter = []
2679 elif not platform_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002680 logger.info("Selecting default platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002681 default_platforms = True
2682
Anas Nashif7a361b82019-12-06 11:37:40 -05002683 logger.info("Building initial testcase list...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002684
2685 for tc_name, tc in self.testcases.items():
2686 # list of instances per testcase, aka configurations.
2687 instance_list = []
2688 for plat in platforms:
2689 instance = TestInstance(tc, plat, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002690 instance.check_build_or_run(
2691 self.build_only,
2692 self.enable_slow,
2693 self.device_testing,
2694 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002695 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002696
2697 if (plat.arch == "unit") != (tc.type == "unit"):
2698 # Discard silently
2699 continue
2700
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002701 if device_testing_filter and instance.build_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002702 discards[instance] = "Not runnable on device"
2703 continue
2704
2705 if tc.skip:
2706 discards[instance] = "Skip filter"
2707 continue
2708
2709 if tc.build_on_all and not platform_filter:
2710 platform_filter = []
2711
2712 if tag_filter and not tc.tags.intersection(tag_filter):
2713 discards[instance] = "Command line testcase tag filter"
2714 continue
2715
2716 if exclude_tag and tc.tags.intersection(exclude_tag):
2717 discards[instance] = "Command line testcase exclude filter"
2718 continue
2719
2720 if testcase_filter and tc_name not in testcase_filter:
2721 discards[instance] = "Testcase name filter"
2722 continue
2723
2724 if arch_filter and plat.arch not in arch_filter:
2725 discards[instance] = "Command line testcase arch filter"
2726 continue
2727
2728 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2729 discards[instance] = "Not in test case arch whitelist"
2730 continue
2731
2732 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2733 discards[instance] = "In test case arch exclude"
2734 continue
2735
2736 if tc.platform_exclude and plat.name in tc.platform_exclude:
2737 discards[instance] = "In test case platform exclude"
2738 continue
2739
2740 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2741 discards[instance] = "In test case toolchain exclude"
2742 continue
2743
2744 if platform_filter and plat.name not in platform_filter:
2745 discards[instance] = "Command line platform filter"
2746 continue
2747
2748 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2749 discards[instance] = "Not in testcase platform whitelist"
2750 continue
2751
2752 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2753 discards[instance] = "Not in testcase toolchain whitelist"
2754 continue
2755
2756 if not plat.env_satisfied:
2757 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2758 continue
2759
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002760 if not force_toolchain \
Anas Nashifd9882382019-12-12 09:58:28 -05002761 and toolchain and (toolchain not in plat.supported_toolchains) \
2762 and tc.type != 'unit':
Anas Nashif83fc06a2019-06-22 11:04:10 -04002763 discards[instance] = "Not supported by the toolchain"
2764 continue
2765
2766 if plat.ram < tc.min_ram:
2767 discards[instance] = "Not enough RAM"
2768 continue
2769
2770 if tc.depends_on:
2771 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2772 if dep_intersection != set(tc.depends_on):
2773 discards[instance] = "No hardware support"
2774 continue
2775
2776 if plat.flash < tc.min_flash:
2777 discards[instance] = "Not enough FLASH"
2778 continue
2779
2780 if set(plat.ignore_tags) & tc.tags:
2781 discards[instance] = "Excluded tags per platform"
2782 continue
2783
2784 # if nothing stopped us until now, it means this configuration
2785 # needs to be added.
2786 instance_list.append(instance)
2787
2788 # no configurations, so jump to next testcase
2789 if not instance_list:
2790 continue
2791
2792 # if sanitycheck was launched with no platform options at all, we
2793 # take all default platforms
2794 if default_platforms and not tc.build_on_all:
2795 if tc.platform_whitelist:
2796 a = set(self.default_platforms)
2797 b = set(tc.platform_whitelist)
2798 c = a.intersection(b)
2799 if c:
Anas Nashifd9882382019-12-12 09:58:28 -05002800 aa = list(filter(lambda tc: tc.platform.name in c, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002801 self.add_instances(aa)
2802 else:
2803 self.add_instances(instance_list[:1])
2804 else:
Anas Nashifd9882382019-12-12 09:58:28 -05002805 instances = list(filter(lambda tc: tc.platform.default, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002806 self.add_instances(instances)
2807
2808 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2809 discards[instance] = "Not a default test platform"
2810
2811 else:
2812 self.add_instances(instance_list)
2813
2814 for _, case in self.instances.items():
Anas Nashif56656842019-12-10 12:26:00 -05002815 case.create_overlay(case.platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002816
2817 self.discards = discards
Anas Nashif5f908822019-11-25 08:19:25 -05002818 self.selected_platforms = set(p.platform.name for p in self.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04002819
2820 return discards
2821
2822 def add_instances(self, instance_list):
2823 for instance in instance_list:
2824 self.instances[instance.name] = instance
2825
Anas Nashif56656842019-12-10 12:26:00 -05002826 def add_tasks_to_queue(self, test_only=False):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002827 for instance in self.instances.values():
Anas Nashif56656842019-12-10 12:26:00 -05002828 if test_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002829 if instance.run:
2830 pipeline.put({"op": "run", "test": instance, "status": "built"})
2831 else:
2832 if instance.status not in ['passed', 'skipped']:
2833 instance.status = None
2834 pipeline.put({"op": "cmake", "test": instance})
2835
2836 return "DONE FEEDING"
2837
Anas Nashifc5ee3952019-12-10 16:38:45 -05002838 def execute(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002839 def calc_one_elf_size(instance):
2840 if instance.status not in ["failed", "skipped"]:
2841 if instance.platform.type != "native":
2842 size_calc = instance.calculate_sizes()
2843 instance.metrics["ram_size"] = size_calc.get_ram_size()
2844 instance.metrics["rom_size"] = size_calc.get_rom_size()
2845 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2846 else:
2847 instance.metrics["ram_size"] = 0
2848 instance.metrics["rom_size"] = 0
2849 instance.metrics["unrecognized"] = []
2850
2851 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2852
Anas Nashif7a361b82019-12-06 11:37:40 -05002853 logger.info("Adding tasks to the queue...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002854 # We can use a with statement to ensure threads are cleaned up promptly
2855 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2856
2857 # start a future for a thread which sends work in through the queue
2858 future_to_test = {
Anas Nashifd9882382019-12-12 09:58:28 -05002859 executor.submit(self.add_tasks_to_queue, self.test_only): 'FEEDER DONE'}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002860
2861 while future_to_test:
2862 # check for status of the futures which are currently working
2863 done, _ = concurrent.futures.wait(
Anas Nashifd9882382019-12-12 09:58:28 -05002864 future_to_test, timeout=0.25,
2865 return_when=concurrent.futures.FIRST_COMPLETED)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002866
2867 # if there is incoming work, start a new future
2868 while not pipeline.empty():
2869 # fetch a url from the queue
2870 message = pipeline.get()
2871 test = message['test']
2872
2873 # Start the load operation and mark the future with its URL
Anas Nashif56656842019-12-10 12:26:00 -05002874 pb = ProjectBuilder(self,
Anas Nashifd9882382019-12-12 09:58:28 -05002875 test,
2876 lsan=self.enable_lsan,
2877 asan=self.enable_asan,
2878 coverage=self.enable_coverage,
2879 extra_args=self.extra_args,
2880 device_testing=self.device_testing,
2881 cmake_only=self.cmake_only,
2882 valgrind=self.enable_valgrind,
2883 inline_logs=self.inline_logs
2884 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002885 future_to_test[executor.submit(pb.process, message)] = test.name
2886
2887 # process any completed futures
2888 for future in done:
2889 test = future_to_test[future]
2890 try:
2891 data = future.result()
2892 except Exception as exc:
Anas Nashif4f043862019-11-05 06:01:49 -08002893 sys.exit('%r generated an exception: %s' % (test, exc))
2894
Anas Nashif83fc06a2019-06-22 11:04:10 -04002895 else:
2896 if data:
Anas Nashif7a361b82019-12-06 11:37:40 -05002897 logger.debug(data)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002898
2899 # remove the now completed future
2900 del future_to_test[future]
2901
Anas Nashifc5ee3952019-12-10 16:38:45 -05002902 if self.enable_size_report and not self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002903 # Parallelize size calculation
2904 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2905 futures = [executor.submit(calc_one_elf_size, instance)
2906 for instance in self.instances.values()]
2907 concurrent.futures.wait(futures)
2908 else:
2909 for instance in self.instances.values():
2910 instance.metrics["ram_size"] = 0
2911 instance.metrics["rom_size"] = 0
2912 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2913 instance.metrics["unrecognized"] = []
2914
Anas Nashif83fc06a2019-06-22 11:04:10 -04002915 def discard_report(self, filename):
2916
2917 try:
2918 if self.discards is None:
2919 raise SanityRuntimeError("apply_filters() hasn't been run!")
2920 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002921 logger.error(str(e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002922 sys.exit(2)
2923
2924 with open(filename, "wt") as csvfile:
2925 fieldnames = ["test", "arch", "platform", "reason"]
2926 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2927 cw.writeheader()
2928 for instance, reason in sorted(self.discards.items()):
2929 rowdict = {"test": instance.testcase.name,
2930 "arch": instance.platform.arch,
2931 "platform": instance.platform.name,
2932 "reason": reason}
2933 cw.writerow(rowdict)
2934
Anas Nashif83fc06a2019-06-22 11:04:10 -04002935 def target_report(self, outdir):
2936 run = "Sanitycheck"
2937 eleTestsuite = None
2938
Anas Nashifd9882382019-12-12 09:58:28 -05002939 platforms = {inst.platform.name for _, inst in self.instances.items()}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002940 for platform in platforms:
2941 errors = 0
2942 passes = 0
2943 fails = 0
2944 duration = 0
2945 skips = 0
2946 for _, instance in self.instances.items():
2947 if instance.platform.name != platform:
2948 continue
2949
2950 handler_time = instance.metrics.get('handler_time', 0)
2951 duration += handler_time
2952 for k in instance.results.keys():
2953 if instance.results[k] == 'PASS':
2954 passes += 1
2955 elif instance.results[k] == 'BLOCK':
2956 errors += 1
2957 elif instance.results[k] == 'SKIP':
2958 skips += 1
2959 else:
2960 fails += 1
2961
2962 eleTestsuites = ET.Element('testsuites')
2963 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashifd9882382019-12-12 09:58:28 -05002964 name=run, time="%f" % duration,
2965 tests="%d" % (errors + passes + fails),
2966 failures="%d" % fails,
2967 errors="%d" % errors, skipped="%d" % skips)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002968
2969 handler_time = 0
2970
2971 # print out test results
2972 for _, instance in self.instances.items():
2973 if instance.platform.name != platform:
2974 continue
2975 handler_time = instance.metrics.get('handler_time', 0)
2976 for k in instance.results.keys():
2977 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05002978 eleTestsuite, 'testcase',
2979 classname="%s:%s" % (instance.platform.name, os.path.basename(instance.testcase.name)),
2980 name="%s" % (k), time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002981 if instance.results[k] in ['FAIL', 'BLOCK']:
2982 el = None
2983
2984 if instance.results[k] == 'FAIL':
2985 el = ET.SubElement(
2986 eleTestcase,
2987 'failure',
2988 type="failure",
2989 message="failed")
2990 elif instance.results[k] == 'BLOCK':
2991 el = ET.SubElement(
2992 eleTestcase,
2993 'error',
2994 type="failure",
2995 message="failed")
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002996 p = os.path.join(self.outdir, instance.platform.name, instance.testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002997 log_file = os.path.join(p, "handler.log")
2998
2999 if os.path.exists(log_file):
3000 with open(log_file, "rb") as f:
3001 log = f.read().decode("utf-8")
3002 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3003 el.text = filtered_string
3004
3005 elif instance.results[k] == 'SKIP':
3006 el = ET.SubElement(
3007 eleTestcase,
3008 'skipped',
3009 type="skipped",
3010 message="Skipped")
3011
Anas Nashif83fc06a2019-06-22 11:04:10 -04003012 result = ET.tostring(eleTestsuites)
3013 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
3014 f.write(result)
3015
Anas Nashif56656842019-12-10 12:26:00 -05003016 def xunit_report(self, filename, append=False):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003017 fails = 0
3018 passes = 0
3019 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04003020 skips = 0
3021 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04003022
Anas Nashif83fc06a2019-06-22 11:04:10 -04003023 for instance in self.instances.values():
3024 handler_time = instance.metrics.get('handler_time', 0)
3025 duration += handler_time
3026 if instance.status == "failed":
3027 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003028 errors += 1
3029 else:
3030 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04003031 elif instance.status == 'skipped':
3032 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04003033 else:
3034 passes += 1
3035
3036 run = "Sanitycheck"
3037 eleTestsuite = None
Anas Nashifb3311ed2017-04-13 14:44:48 -04003038
Anas Nashif83fc06a2019-06-22 11:04:10 -04003039 # When we re-run the tests, we re-use the results and update only with
3040 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04003041 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003042 tree = ET.parse(filename)
3043 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05003044 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04003045 else:
3046 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05003047 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04003048 name=run, time="%f" % duration,
3049 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05003050 failures="%d" % fails,
Anas Nashifd9882382019-12-12 09:58:28 -05003051 errors="%d" % (errors), skip="%s" % (skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003052
Anas Nashif83fc06a2019-06-22 11:04:10 -04003053 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04003054
Anas Nashif83fc06a2019-06-22 11:04:10 -04003055 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04003056 if append:
3057 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003058 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04003059 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003060 eleTestsuite.remove(tc)
3061
Anas Nashif83fc06a2019-06-22 11:04:10 -04003062 handler_time = 0
3063 if instance.status != "failed" and instance.handler:
3064 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003065
Anas Nashifd9882382019-12-12 09:58:28 -05003066
Anas Nashif3ba1d432017-12-05 15:28:44 -05003067 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05003068 eleTestsuite,
3069 'testcase',
3070 classname="%s:%s" % (instance.platform.name, instance.testcase.name),
3071 name="%s" % (instance.testcase.name),
3072 time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003073
3074 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05003075 failure = ET.SubElement(
3076 eleTestcase,
3077 'failure',
3078 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003079 message=instance.reason)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003080 p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003081 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05003082 hl = os.path.join(p, "handler.log")
3083 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04003084 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05003085 if os.path.exists(hl):
3086 log_file = hl
3087 else:
3088 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04003089
Anas Nashifc96c90a2019-02-05 07:38:32 -05003090 if os.path.exists(log_file):
3091 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05003092 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04003093 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3094 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05003095 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003096 elif instance.status == "skipped":
Anas Nashifd9882382019-12-12 09:58:28 -05003097 ET.SubElement(eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04003098
3099 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003100 with open(filename, 'wb') as report:
3101 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003102
Anas Nashif83fc06a2019-06-22 11:04:10 -04003103 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08003104 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07003105 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003106 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07003107 "rom_size"]
3108 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3109 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003110 for instance in sorted(self.instances.values()):
3111 rowdict = {"test": instance.testcase.name,
3112 "arch": instance.platform.arch,
3113 "platform": instance.platform.name,
3114 "extra_args": " ".join(instance.testcase.extra_args),
3115 "handler": instance.platform.simulation}
3116
3117 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07003118 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04003119 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07003120 else:
3121 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04003122 if instance.handler:
3123 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
3124 ram_size = instance.metrics.get("ram_size", 0)
3125 rom_size = instance.metrics.get("rom_size", 0)
3126 rowdict["ram_size"] = ram_size
3127 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003128 cw.writerow(rowdict)
3129
Anas Nashif19ca7832019-11-18 08:16:21 -08003130 def get_testcase(self, identifier):
3131 results = []
3132 for _, tc in self.testcases.items():
3133 for case in tc.cases:
3134 if case == identifier:
3135 results.append(tc)
3136 return results
3137
3138
Andrew Boie6acbe632015-07-17 12:03:52 -07003139def parse_arguments():
Anas Nashif3ba1d432017-12-05 15:28:44 -05003140 parser = argparse.ArgumentParser(
3141 description=__doc__,
3142 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003143 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003144
Marc Herbert932a33a2019-03-12 11:37:53 -07003145 case_select = parser.add_argument_group("Test case selection",
3146 """
3147Artificially long but functional example:
3148 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003149 --testcase-root tests/ztest/base \\
3150 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003151 --test tests/ztest/base/testing.ztest.verbose_0 \\
3152 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3153
3154 "kernel.fifo.poll" is one of the test section names in
3155 __/fifo_api/testcase.yaml
3156 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003157
Anas Nashif07d54c02018-07-21 19:29:08 -05003158 parser.add_argument("--force-toolchain", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003159 help="Do not filter based on toolchain, use the set "
3160 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003161 parser.add_argument(
3162 "-p", "--platform", action="append",
3163 help="Platform filter for testing. This option may be used multiple "
Anas Nashifd9882382019-12-12 09:58:28 -05003164 "times. Testcases will only be built/run on the platforms "
3165 "specified. If this option is not used, then platforms marked "
3166 "as default in the platform metadata file will be chosen "
3167 "to build and test. ")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003168 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003169 "-a", "--arch", action="append",
3170 help="Arch filter for testing. Takes precedence over --platform. "
Anas Nashifd9882382019-12-12 09:58:28 -05003171 "If unspecified, test all arches. Multiple invocations "
3172 "are treated as a logical 'or' relationship")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003173 parser.add_argument(
3174 "-t", "--tag", action="append",
3175 help="Specify tags to restrict which tests to run by tag value. "
Anas Nashifd9882382019-12-12 09:58:28 -05003176 "Default is to not do any tag filtering. Multiple invocations "
3177 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003178 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003179 help="Specify tags of tests that should not run. "
Anas Nashifd9882382019-12-12 09:58:28 -05003180 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003181 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003182 "-f",
3183 "--only-failed",
3184 action="store_true",
3185 help="Run only those tests that failed the previous sanity check "
Anas Nashifd9882382019-12-12 09:58:28 -05003186 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003187
Anas Nashif3ba1d432017-12-05 15:28:44 -05003188 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003189 "--retry-failed", type=int, default=0,
3190 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003191
Marc Herbert0c465bb2019-03-11 17:28:36 -07003192 test_xor_subtest = case_select.add_mutually_exclusive_group()
3193
3194 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003195 "-s", "--test", action="append",
3196 help="Run only the specified test cases. These are named by "
Anas Nashifd9882382019-12-12 09:58:28 -05003197 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003198
Marc Herbert0c465bb2019-03-11 17:28:36 -07003199 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003200 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003201 help="""Recursively find sub-test functions and run the entire
3202 test section where they were found, including all sibling test
3203 functions. Sub-tests are named by:
3204 section.name.in.testcase.yaml.function_name_without_test_prefix
3205 Example: kernel.fifo.poll.fifo_loop
3206 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003207
Anas Nashif3ba1d432017-12-05 15:28:44 -05003208 parser.add_argument(
3209 "-l", "--all", action="store_true",
3210 help="Build/test on all platforms. Any --platform arguments "
Anas Nashifd9882382019-12-12 09:58:28 -05003211 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003212
Anas Nashif3ba1d432017-12-05 15:28:44 -05003213 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003214 "-o", "--report-dir",
3215 help="""Output reports containing results of the test run into the
3216 specified directory.
3217 The output will be both in CSV and JUNIT format
3218 (sanitycheck.csv and sanitycheck.xml).
3219 """)
3220
Anas Nashif3ba1d432017-12-05 15:28:44 -05003221 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003222 "--report-name",
3223 help="""Create a report with a custom name.
3224 """)
3225
Anas Nashif83fc06a2019-06-22 11:04:10 -04003226 parser.add_argument("--report-excluded",
Anas Nashifd9882382019-12-12 09:58:28 -05003227 action="store_true",
3228 help="""List all tests that are never run based on current scope and
Anas Nashif83fc06a2019-06-22 11:04:10 -04003229 coverage. If you are looking for accurate results, run this with
3230 --all, but this will take a while...""")
3231
Daniel Leung7f850102016-04-08 11:07:32 -07003232 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003233 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003234
Anas Nashif3ba1d432017-12-05 15:28:44 -05003235 parser.add_argument(
3236 "-B", "--subset",
3237 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
Anas Nashifd9882382019-12-12 09:58:28 -05003238 "3/5 means run the 3rd fifth of the total. "
3239 "This option is useful when running a large number of tests on "
3240 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003241
3242 parser.add_argument(
3243 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003244 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003245
Anas Nashif3ba1d432017-12-05 15:28:44 -05003246 parser.add_argument(
3247 "-y", "--dry-run", action="store_true",
Anas Nashif12d8cce2019-11-20 03:47:27 -08003248 help="""Create the filtered list of test cases, but don't actually
3249 run them. Useful if you're just interested in the discard report
3250 generated for every run and saved in the specified output
3251 directory (sanitycheck_discard.csv).
3252 """)
Andrew Boie6acbe632015-07-17 12:03:52 -07003253
Anas Nashif75547e22018-02-24 08:32:14 -06003254 parser.add_argument("--list-tags", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003255 help="list all tags in selected tests")
Anas Nashif75547e22018-02-24 08:32:14 -06003256
Marc Herbertedf17592019-03-08 12:39:11 -08003257 case_select.add_argument("--list-tests", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003258 help="""List of all sub-test functions recursively found in
Marc Herbert932a33a2019-03-12 11:37:53 -07003259 all --testcase-root arguments. Note different sub-tests can share
3260 the same section name and come from different directories.
3261 The output is flattened and reports --sub-test names only,
3262 not their directories. For instance net.socket.getaddrinfo_ok
3263 and net.socket.fd_set belong to different directories.
3264 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003265
Anas Nashif434995c2019-12-01 13:55:11 -05003266 case_select.add_argument("--test-tree", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003267 help="""Output the testsuite in a tree form""")
Anas Nashif434995c2019-12-01 13:55:11 -05003268
Anas Nashif19ca7832019-11-18 08:16:21 -08003269 case_select.add_argument("--list-test-duplicates", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003270 help="""List tests with duplicate identifiers.
Anas Nashif19ca7832019-11-18 08:16:21 -08003271 """)
3272
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003273 parser.add_argument("--export-tests", action="store",
Anas Nashifd9882382019-12-12 09:58:28 -05003274 metavar="FILENAME",
3275 help="Export tests case meta-data to a file in CSV format.")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003276
Anas Nashif654ec5982019-04-11 08:38:21 -04003277 parser.add_argument("--timestamps",
Anas Nashifd9882382019-12-12 09:58:28 -05003278 action="store_true",
3279 help="Print all messages with time stamps")
Anas Nashif654ec5982019-04-11 08:38:21 -04003280
Anas Nashif3ba1d432017-12-05 15:28:44 -05003281 parser.add_argument(
3282 "-r", "--release", action="store_true",
3283 help="Update the benchmark database with the results of this test "
Anas Nashifd9882382019-12-12 09:58:28 -05003284 "run. Intended to be run by CI when tagging an official "
3285 "release. This database is used as a basis for comparison "
3286 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003287 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003288 help="Treat warning conditions as errors")
3289 parser.add_argument(
3290 "-v",
3291 "--verbose",
3292 action="count",
3293 default=0,
3294 help="Emit debugging information, call multiple times to increase "
Anas Nashifd9882382019-12-12 09:58:28 -05003295 "verbosity")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003296 parser.add_argument(
3297 "-i", "--inline-logs", action="store_true",
3298 help="Upon test failure, print relevant log data to stdout "
Anas Nashifd9882382019-12-12 09:58:28 -05003299 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003300 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003301 help="log also to file")
3302 parser.add_argument(
3303 "-m", "--last-metrics", action="store_true",
3304 help="Instead of comparing metrics from the last --release, "
Anas Nashifd9882382019-12-12 09:58:28 -05003305 "compare with the results of the previous sanity check "
3306 "invocation")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003307 parser.add_argument(
3308 "-u",
3309 "--no-update",
3310 action="store_true",
3311 help="do not update the results of the last run of the sanity "
Anas Nashifd9882382019-12-12 09:58:28 -05003312 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003313 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003314 "-F",
3315 "--load-tests",
3316 metavar="FILENAME",
3317 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003318 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003319
Marc Herbertedf17592019-03-08 12:39:11 -08003320 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003321 "-E",
3322 "--save-tests",
3323 metavar="FILENAME",
3324 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003325 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003326
Andy Doancbecadd2019-02-08 10:19:10 -06003327 test_or_build = parser.add_mutually_exclusive_group()
3328 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003329 "-b", "--build-only", action="store_true",
3330 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003331
Andy Doancbecadd2019-02-08 10:19:10 -06003332 test_or_build.add_argument(
3333 "--test-only", action="store_true",
3334 help="""Only run device tests with current artifacts, do not build
3335 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003336 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003337 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003338 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003339
3340 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003341 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003342 help="Number of jobs for building, defaults to number of CPU threads, "
Anas Nashifd9882382019-12-12 09:58:28 -05003343 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003344
3345 parser.add_argument(
Anas Nashifd9882382019-12-12 09:58:28 -05003346 "--show-footprint", action="store_true",
3347 help="Show footprint statistics and deltas since last release."
3348 )
Anas Nashif424a3db2018-02-20 08:37:24 -06003349 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003350 "-H", "--footprint-threshold", type=float, default=5,
3351 help="When checking test case footprint sizes, warn the user if "
Anas Nashifd9882382019-12-12 09:58:28 -05003352 "the new app size is greater then the specified percentage "
3353 "from the last release. Default is 5. 0 to warn on any "
3354 "increase on app size")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003355 parser.add_argument(
3356 "-D", "--all-deltas", action="store_true",
3357 help="Show all footprint deltas, positive or negative. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003358 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003359 parser.add_argument(
3360 "-O", "--outdir",
Anas Nashifd9882382019-12-12 09:58:28 -05003361 default=os.path.join(os.getcwd(), "sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003362 help="Output directory for logs and binaries. "
Anas Nashifd9882382019-12-12 09:58:28 -05003363 "Default is 'sanity-out' in the current directory. "
Andrew Boie114c01b2020-01-02 18:44:22 -08003364 "This directory will be cleaned unless '--no-clean' is set. "
3365 "The '--clobber-output' option controls what cleaning does.")
3366 parser.add_argument(
3367 "-c", "--clobber-output", action="store_true",
3368 help="Cleaning the output directory will simply delete it instead "
3369 "of the default policy of renaming.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003370 parser.add_argument(
3371 "-n", "--no-clean", action="store_true",
Andrew Boie114c01b2020-01-02 18:44:22 -08003372 help="Re-use the outdir before building. Will result in "
3373 "faster compilation since builds will be incremental.")
Marc Herbertedf17592019-03-08 12:39:11 -08003374 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003375 "-T", "--testcase-root", action="append", default=[],
3376 help="Base directory to recursively search for test cases. All "
Anas Nashifd9882382019-12-12 09:58:28 -05003377 "testcase.yaml files under here will be processed. May be "
3378 "called multiple times. Defaults to the 'samples/' and "
3379 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003380
Anas Nashif3ba1d432017-12-05 15:28:44 -05003381 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3382 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003383
Anas Nashif3ba1d432017-12-05 15:28:44 -05003384 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003385 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003386 help="""Directory to search for board configuration files. All .yaml
3387files in the directory will be processed. The directory should have the same
3388structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3389
Anas Nashif3ba1d432017-12-05 15:28:44 -05003390 parser.add_argument(
3391 "-z", "--size", action="append",
3392 help="Don't run sanity checks. Instead, produce a report to "
Anas Nashifd9882382019-12-12 09:58:28 -05003393 "stdout detailing RAM/ROM sizes on the specified filenames. "
3394 "All other command line arguments ignored.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003395 parser.add_argument(
3396 "-S", "--enable-slow", action="store_true",
3397 help="Execute time-consuming test cases that have been marked "
Anas Nashifd9882382019-12-12 09:58:28 -05003398 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003399 parser.add_argument(
3400 "--disable-unrecognized-section-test", action="store_true",
3401 default=False,
3402 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003403 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003404 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003405 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003406 parser.add_argument("--disable-asserts", action="store_false",
3407 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003408 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003409 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003410 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003411 parser.add_argument("--enable-size-report", action="store_true",
3412 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003413
3414 parser.add_argument(
3415 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003416 help="""Extra CMake cache entries to define when building test cases.
3417 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003418 prefixed with -D before being passed to CMake.
3419
3420 E.g
3421 "sanitycheck -x=USE_CCACHE=0"
3422 will translate to
3423 "cmake -DUSE_CCACHE=0"
3424
3425 which will ultimately disable ccache.
3426 """
3427 )
Michael Scott421ce462019-06-18 09:37:46 -07003428
Andy Doan79c48842019-02-08 10:09:04 -06003429 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003430 "--device-testing", action="store_true",
3431 help="Test on device directly. Specify the serial device to "
3432 "use with the --device-serial option.")
3433
3434 parser.add_argument(
3435 "-X", "--fixture", action="append", default=[],
3436 help="Specify a fixture that a board might support")
3437 parser.add_argument(
3438 "--device-serial",
3439 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3440
3441 parser.add_argument("--generate-hardware-map",
3442 help="""Probe serial devices connected to this platform
3443 and create a hardware map file to be used with
3444 --device-testing
3445 """)
3446
3447 parser.add_argument("--hardware-map",
3448 help="""Load hardware map from a file. This will be used
3449 for testing on hardware that is listed in the file.
3450 """)
3451
3452 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003453 "--west-flash", nargs='?', const=[],
3454 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003455 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003456
Michael Scott4ca54392019-07-09 14:21:30 -07003457 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003458 --west-flash="--board-id=foobar,--erase"
3459 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003460
3461 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003462 """
3463 )
Michael Scott421ce462019-06-18 09:37:46 -07003464 parser.add_argument(
3465 "--west-runner",
3466 help="""Uses the specified west runner instead of default when running
3467 with --west-flash.
3468
3469 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3470 --west-flash --west-runner=pyocd"
3471 will translate to "west flash --runner pyocd"
3472
3473 NOTE: west-flash must be enabled to use this option.
3474 """
3475 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003476
3477 valgrind_asan_group = parser.add_mutually_exclusive_group()
3478
3479 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003480 "--enable-valgrind", action="store_true",
3481 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003482 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003483 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003484 configuration and is mutual exclusive with --enable-asan.
3485 """)
3486
3487 valgrind_asan_group.add_argument(
3488 "--enable-asan", action="store_true",
3489 help="""Enable address sanitizer to check for several memory access
3490 errors. Libasan needs to be installed on the host. This option only
3491 works with host binaries such as those generated for the native_posix
3492 configuration and is mutual exclusive with --enable-valgrind.
3493 """)
3494
3495 parser.add_argument(
3496 "--enable-lsan", action="store_true",
3497 help="""Enable leak sanitizer to check for heap memory leaks.
3498 Libasan needs to be installed on the host. This option only
3499 works with host binaries such as those generated for the native_posix
3500 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003501 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003502
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003503 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003504 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003505
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003506 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003507 help="Generate coverage reports. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003508 "--enable_coverage.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003509
Andrew Boie8047a6f2019-07-02 15:43:29 -07003510 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003511 help="Plarforms to run coverage reports on. "
Anas Nashifd9882382019-12-12 09:58:28 -05003512 "This option may be used multiple times. "
3513 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003514
Anas Nashif83fc06a2019-06-22 11:04:10 -04003515 parser.add_argument("--gcov-tool", default=None,
3516 help="Path to the gcov tool to use for code coverage "
Anas Nashifd9882382019-12-12 09:58:28 -05003517 "reports")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003518
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003519 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='lcov',
3520 help="Tool to use to generate coverage report.")
3521
Andrew Boie6acbe632015-07-17 12:03:52 -07003522 return parser.parse_args()
3523
Anas Nashifd9882382019-12-12 09:58:28 -05003524
Andrew Boiebbd670c2015-08-17 13:16:11 -07003525def size_report(sc):
Anas Nashif7a361b82019-12-06 11:37:40 -05003526 logger.info(sc.filename)
3527 logger.info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003528 for i in range(len(sc.sections)):
3529 v = sc.sections[i]
3530
Anas Nashif7a361b82019-12-06 11:37:40 -05003531 logger.info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
Anas Nashifd9882382019-12-12 09:58:28 -05003532 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3533 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003534
Anas Nashif7a361b82019-12-06 11:37:40 -05003535 logger.info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashifd9882382019-12-12 09:58:28 -05003536 (sc.rom_size, sc.ram_size))
Anas Nashif7a361b82019-12-06 11:37:40 -05003537 logger.info("")
Andrew Boiebbd670c2015-08-17 13:16:11 -07003538
Anas Nashifd9882382019-12-12 09:58:28 -05003539
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003540class CoverageTool:
3541 """ Base class for every supported coverage tool
3542 """
3543
3544 def __init__(self):
3545 self.gcov_tool = options.gcov_tool
3546
3547 @staticmethod
3548 def factory(tool):
3549 if tool == 'lcov':
3550 return Lcov()
3551 if tool == 'gcovr':
3552 return Gcovr()
Anas Nashif7a361b82019-12-06 11:37:40 -05003553 logger.error("Unsupported coverage tool specified: {}".format(tool))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003554
3555 @staticmethod
3556 def retrieve_gcov_data(intput_file):
3557 if VERBOSE:
Anas Nashifd9882382019-12-12 09:58:28 -05003558 logger.debug("Working on %s" % intput_file)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003559 extracted_coverage_info = {}
3560 capture_data = False
3561 capture_complete = False
3562 with open(intput_file, 'r') as fp:
3563 for line in fp.readlines():
3564 if re.search("GCOV_COVERAGE_DUMP_START", line):
3565 capture_data = True
3566 continue
3567 if re.search("GCOV_COVERAGE_DUMP_END", line):
3568 capture_complete = True
3569 break
3570 # Loop until the coverage data is found.
3571 if not capture_data:
3572 continue
3573 if line.startswith("*"):
3574 sp = line.split("<")
3575 if len(sp) > 1:
3576 # Remove the leading delimiter "*"
3577 file_name = sp[0][1:]
3578 # Remove the trailing new line char
3579 hex_dump = sp[1][:-1]
3580 else:
3581 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003582 else:
3583 continue
Anas Nashifd9882382019-12-12 09:58:28 -05003584 extracted_coverage_info.update({file_name: hex_dump})
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003585 if not capture_data:
3586 capture_complete = True
3587 return {'complete': capture_complete, 'data': extracted_coverage_info}
3588
3589 @staticmethod
3590 def create_gcda_files(extracted_coverage_info):
3591 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003592 logger.debug("Generating gcda files")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003593 for filename, hexdump_val in extracted_coverage_info.items():
3594 # if kobject_hash is given for coverage gcovr fails
3595 # hence skipping it problem only in gcovr v4.1
3596 if "kobject_hash" in filename:
Anas Nashifd9882382019-12-12 09:58:28 -05003597 filename = (filename[:-4]) + "gcno"
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003598 try:
3599 os.remove(filename)
3600 except Exception:
3601 pass
Anas Nashifdb9592a2018-10-08 10:19:41 -04003602 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003603
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003604 with open(filename, 'wb') as fp:
3605 fp.write(bytes.fromhex(hexdump_val))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003606
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003607 def generate(self, outdir):
3608 for filename in glob.glob("%s/**/handler.log" % outdir, recursive=True):
3609 gcov_data = self.__class__.retrieve_gcov_data(filename)
3610 capture_complete = gcov_data['complete']
3611 extracted_coverage_info = gcov_data['data']
3612 if capture_complete:
3613 self.__class__.create_gcda_files(extracted_coverage_info)
Anas Nashif7a361b82019-12-06 11:37:40 -05003614 logger.debug("Gcov data captured: {}".format(filename))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003615 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003616 logger.error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003617
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003618 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3619 ret = self._generate(outdir, coveragelog)
3620 if ret == 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05003621 logger.info("HTML report generated: {}".format(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003622 os.path.join(outdir, "coverage", "index.html")))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003623
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003624
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003625class Lcov(CoverageTool):
3626
3627 def __init__(self):
3628 super().__init__()
3629 self.ignores = []
3630
3631 def add_ignore_file(self, pattern):
3632 self.ignores.append('*' + pattern + '*')
3633
3634 def add_ignore_directory(self, pattern):
3635 self.ignores.append(pattern + '/*')
3636
3637 def _generate(self, outdir, coveragelog):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003638 coveragefile = os.path.join(outdir, "coverage.info")
3639 ztestfile = os.path.join(outdir, "ztest.info")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003640 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool,
3641 "--capture", "--directory", outdir,
3642 "--rc", "lcov_branch_coverage=1",
3643 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003644 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003645 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--extract",
3646 coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003647 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003648 "--output-file", ztestfile,
3649 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3650
Anas Nashif3cbffef2018-11-07 23:50:54 -05003651 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003652 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3653 ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003654 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3655 "--output-file", ztestfile,
3656 "--rc", "lcov_branch_coverage=1"],
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003657 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003658 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003659 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003660 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003661
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003662 for i in self.ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003663 subprocess.call(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003664 ["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3665 coveragefile, i, "--output-file",
3666 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003667 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003668
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003669 # The --ignore-errors source option is added to avoid it exiting due to
3670 # samples/application_development/external_lib/
3671 return subprocess.call(["genhtml", "--legend", "--branch-coverage",
3672 "--ignore-errors", "source",
3673 "-output-directory",
3674 os.path.join(outdir, "coverage")] + files,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003675 stdout=coveragelog)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003676
3677
3678class Gcovr(CoverageTool):
3679
3680 def __init__(self):
3681 super().__init__()
3682 self.ignores = []
3683
3684 def add_ignore_file(self, pattern):
3685 self.ignores.append('.*' + pattern + '.*')
3686
3687 def add_ignore_directory(self, pattern):
3688 self.ignores.append(pattern + '/.*')
3689
3690 @staticmethod
3691 def _interleave_list(prefix, list):
3692 tuple_list = [(prefix, item) for item in list]
3693 return [item for sublist in tuple_list for item in sublist]
3694
3695 def _generate(self, outdir, coveragelog):
3696 coveragefile = os.path.join(outdir, "coverage.json")
3697 ztestfile = os.path.join(outdir, "ztest.json")
3698
3699 excludes = Gcovr._interleave_list("-e", self.ignores)
3700
3701 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
3702 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3703 self.gcov_tool, "-e", "tests/*"] + excludes +
3704 ["--json", "-o", coveragefile, outdir],
3705 stdout=coveragelog)
3706
3707 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3708 self.gcov_tool, "-f", "tests/ztest", "-e",
3709 "tests/ztest/test/*", "--json", "-o", ztestfile,
3710 outdir], stdout=coveragelog)
3711
3712 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
3713 files = [coveragefile, ztestfile]
3714 else:
3715 files = [coveragefile]
3716
3717 subdir = os.path.join(outdir, "coverage")
3718 os.makedirs(subdir, exist_ok=True)
3719
3720 tracefiles = self._interleave_list("--add-tracefile", files)
3721
3722 return subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--html",
3723 "--html-details"] + tracefiles +
3724 ["-o", os.path.join(subdir, "index.html")],
3725 stdout=coveragelog)
3726
Anas Nashif3ba1d432017-12-05 15:28:44 -05003727
Anas Nashif83fc06a2019-06-22 11:04:10 -04003728def get_generator():
3729 if options.ninja:
3730 generator_cmd = "ninja"
3731 generator = "Ninja"
3732 else:
3733 generator_cmd = "make"
3734 generator = "Unix Makefiles"
3735 return generator_cmd, generator
3736
3737
3738def export_tests(filename, tests):
3739 with open(filename, "wt") as csvfile:
3740 fieldnames = ['section', 'subsection', 'title', 'reference']
3741 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3742 for test in tests:
3743 data = test.split(".")
Anas Nashif19ca7832019-11-18 08:16:21 -08003744 if len(data) > 1:
3745 subsec = " ".join(data[1].split("_")).title()
3746 rowdict = {
Anas Nashifd9882382019-12-12 09:58:28 -05003747 "section": data[0].capitalize(),
3748 "subsection": subsec,
3749 "title": test,
3750 "reference": test
3751 }
Anas Nashif19ca7832019-11-18 08:16:21 -08003752 cw.writerow(rowdict)
3753 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003754 logger.info("{} can't be exported".format(test))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003755
Anas Nashif83fc06a2019-06-22 11:04:10 -04003756
3757def native_and_unit_first(a, b):
3758 if a[0].startswith('unit_testing'):
3759 return -1
3760 if b[0].startswith('unit_testing'):
3761 return 1
3762 if a[0].startswith('native_posix'):
3763 return -1
3764 if b[0].startswith('native_posix'):
3765 return 1
Anas Nashifd9882382019-12-12 09:58:28 -05003766 if a[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003767 return -1
Anas Nashifd9882382019-12-12 09:58:28 -05003768 if b[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003769 return 1
3770
3771 return (a > b) - (a < b)
3772
3773
Anas Nashif5f908822019-11-25 08:19:25 -05003774class HardwareMap:
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003775
3776 schema_path = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk", "hwmap-schema.yaml")
3777
Anas Nashif5f908822019-11-25 08:19:25 -05003778 manufacturer = [
3779 'ARM',
3780 'SEGGER',
3781 'MBED',
3782 'STMicroelectronics',
3783 'Atmel Corp.',
3784 'Texas Instruments',
3785 'Silicon Labs',
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003786 'NXP Semiconductors',
3787 'Microchip Technology Inc.',
3788 'FTDI'
Anas Nashifd9882382019-12-12 09:58:28 -05003789 ]
Anas Nashif5f908822019-11-25 08:19:25 -05003790
3791 runner_mapping = {
3792 'pyocd': [
3793 'DAPLink CMSIS-DAP',
3794 'MBED CMSIS-DAP'
3795 ],
3796 'jlink': [
3797 'J-Link',
3798 'J-Link OB'
3799 ],
3800 'openocd': [
3801 'STM32 STLink', '^XDS110.*'
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003802 ],
3803 'dediprog': [
3804 'TTL232R-3V3',
3805 'MCP2200 USB Serial Port Emulator'
Anas Nashif5f908822019-11-25 08:19:25 -05003806 ]
3807 }
3808
3809 def __init__(self):
3810 self.detected = []
3811 self.connected_hardware = []
3812
3813 def load_device_from_cmdline(self, serial, platform):
3814 device = {
3815 "serial": serial,
3816 "platform": platform,
3817 "counter": 0,
3818 "available": True,
3819 "connected": True
3820 }
3821 self.connected_hardware.append(device)
3822
3823 def load_hardware_map(self, map_file):
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003824 hwm_schema = scl.yaml_load(self.schema_path)
3825 self.connected_hardware = scl.yaml_load_verify(map_file, hwm_schema)
Anas Nashif5f908822019-11-25 08:19:25 -05003826 for i in self.connected_hardware:
3827 i['counter'] = 0
3828
3829 def scan_hw(self):
3830 from serial.tools import list_ports
3831
3832 serial_devices = list_ports.comports()
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003833 logger.info("Scanning connected hardware...")
Anas Nashif5f908822019-11-25 08:19:25 -05003834 for d in serial_devices:
3835 if d.manufacturer in self.manufacturer:
3836
3837 # TI XDS110 can have multiple serial devices for a single board
3838 # assume endpoint 0 is the serial, skip all others
3839 if d.manufacturer == 'Texas Instruments' and not d.location.endswith('0'):
3840 continue
3841 s_dev = {}
3842 s_dev['platform'] = "unknown"
3843 s_dev['id'] = d.serial_number
3844 s_dev['serial'] = d.device
3845 s_dev['product'] = d.product
3846 s_dev['runner'] = 'unknown'
Anas Nashifd9882382019-12-12 09:58:28 -05003847 for runner, _ in self.runner_mapping.items():
Anas Nashif5f908822019-11-25 08:19:25 -05003848 products = self.runner_mapping.get(runner)
3849 if d.product in products:
3850 s_dev['runner'] = runner
3851 continue
3852 # Try regex matching
3853 for p in products:
3854 if re.match(p, d.product):
3855 s_dev['runner'] = runner
3856
3857 s_dev['available'] = True
3858 s_dev['connected'] = True
3859 self.detected.append(s_dev)
3860 else:
Anas Nashifd9882382019-12-12 09:58:28 -05003861 logger.warning("Unsupported device (%s): %s" % (d.manufacturer, d))
Anas Nashif5f908822019-11-25 08:19:25 -05003862
3863 def write_map(self, hwm_file):
3864 # use existing map
3865 if os.path.exists(hwm_file):
3866 with open(hwm_file, 'r') as yaml_file:
3867 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3868 # disconnect everything
3869 for h in hwm:
3870 h['connected'] = False
3871 h['serial'] = None
3872
3873 for d in self.detected:
3874 for h in hwm:
3875 if d['id'] == h['id'] and d['product'] == h['product']:
Anas Nashif5f908822019-11-25 08:19:25 -05003876 h['connected'] = True
3877 h['serial'] = d['serial']
3878 d['match'] = True
3879
Anas Nashifd9882382019-12-12 09:58:28 -05003880 new = list(filter(lambda n: not n.get('match', False), self.detected))
Anas Nashif5f908822019-11-25 08:19:25 -05003881 hwm = hwm + new
3882
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003883 logger.info("Registered devices:")
Anas Nashif0c69c282019-12-18 10:41:27 -05003884 self.dump(hwm)
Anas Nashife5006d12019-12-01 11:41:22 -05003885
Anas Nashif5f908822019-11-25 08:19:25 -05003886 with open(hwm_file, 'w') as yaml_file:
3887 yaml.dump(hwm, yaml_file, default_flow_style=False)
3888
3889 else:
3890 # create new file
3891 with open(hwm_file, 'w') as yaml_file:
3892 yaml.dump(self.detected, yaml_file, default_flow_style=False)
Anas Nashif0c69c282019-12-18 10:41:27 -05003893 logger.info("Detected devices:")
3894 self.dump(self.detected)
Anas Nashif5f908822019-11-25 08:19:25 -05003895
Anas Nashif0c69c282019-12-18 10:41:27 -05003896 @staticmethod
3897 def dump(hwmap=[], filtered=[], header=[], connected_only=False):
3898 print("")
3899 table = []
3900 if not header:
3901 header = ["Platform", "ID", "Serial device"]
3902 for p in sorted(hwmap, key=lambda i: i['platform']):
3903 platform = p.get('platform')
3904 connected = p.get('connected', False)
3905 if filtered and platform not in filtered:
3906 continue
3907
3908 if not connected_only or connected:
3909 table.append([platform, p.get('id', None), p.get('serial')])
3910
3911 print(tabulate(table, headers=header, tablefmt="github"))
Anas Nashifd9882382019-12-12 09:58:28 -05003912
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003913options = None
Anas Nashif5f908822019-11-25 08:19:25 -05003914
Anas Nashifd9882382019-12-12 09:58:28 -05003915
Andrew Boie6acbe632015-07-17 12:03:52 -07003916def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003917 start_time = time.time()
Anas Nashif7a361b82019-12-06 11:37:40 -05003918 global VERBOSE
Anas Nashife10b6512017-12-30 13:01:45 -05003919 global options
Andrew Boie1578ef72019-07-03 10:19:29 -07003920
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003921 options = parse_arguments()
Anas Nashif7a361b82019-12-06 11:37:40 -05003922
3923 # Cleanup
3924 if options.no_clean or options.only_failed or options.test_only:
3925 if os.path.exists(options.outdir):
Andrew Boie2d8d4c52020-01-02 19:31:20 -08003926 print("Keeping artifacts untouched")
Anas Nashif7a361b82019-12-06 11:37:40 -05003927 elif os.path.exists(options.outdir):
Andrew Boie114c01b2020-01-02 18:44:22 -08003928 if options.clobber_output:
3929 print("Deleting output directory {}".format(options.outdir))
3930 shutil.rmtree(options.outdir)
3931 else:
3932 for i in range(1, 100):
3933 new_out = options.outdir + ".{}".format(i)
3934 if not os.path.exists(new_out):
3935 print("Renaming output directory to {}".format(new_out))
3936 shutil.move(options.outdir, new_out)
3937 break
Anas Nashif7a361b82019-12-06 11:37:40 -05003938
3939 os.makedirs(options.outdir, exist_ok=True)
3940
3941 # create file handler which logs even debug messages
3942 if options.log_file:
3943 fh = logging.FileHandler(options.log_file)
3944 else:
3945 fh = logging.FileHandler(os.path.join(options.outdir, "sanitycheck.log"))
3946
3947 fh.setLevel(logging.DEBUG)
3948
3949 # create console handler with a higher log level
3950 ch = logging.StreamHandler()
3951
Anas Nashif7a361b82019-12-06 11:37:40 -05003952 VERBOSE += options.verbose
3953 if VERBOSE > 1:
3954 ch.setLevel(logging.DEBUG)
3955 else:
3956 ch.setLevel(logging.INFO)
3957
Anas Nashif7a361b82019-12-06 11:37:40 -05003958 # create formatter and add it to the handlers
Anas Nashifd4cef072019-12-08 11:58:00 -05003959 if options.timestamps:
3960 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
3961 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003962 formatter = logging.Formatter('%(levelname)-7s - %(message)s')
Anas Nashifd4cef072019-12-08 11:58:00 -05003963
Anas Nashif7a361b82019-12-06 11:37:40 -05003964 formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
3965 ch.setFormatter(formatter)
3966 fh.setFormatter(formatter_file)
3967
3968 # add the handlers to logger
3969 logger.addHandler(ch)
3970 logger.addHandler(fh)
Andrew Boiebbd670c2015-08-17 13:16:11 -07003971
Anas Nashif5f908822019-11-25 08:19:25 -05003972 hwm = HardwareMap()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003973 if options.generate_hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05003974 hwm.scan_hw()
3975 hwm.write_map(options.generate_hardware_map)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003976 return
3977
Anas Nashif5f908822019-11-25 08:19:25 -05003978 if not options.device_testing and options.hardware_map:
3979 hwm.load_hardware_map(options.hardware_map)
3980
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003981 logger.info("Available devices:")
Anas Nashif5f908822019-11-25 08:19:25 -05003982 table = []
Anas Nashif0c69c282019-12-18 10:41:27 -05003983 hwm.dump(hwmap=hwm.connected_hardware, connected_only=True)
Anas Nashif5f908822019-11-25 08:19:25 -05003984 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04003985
Michael Scott421ce462019-06-18 09:37:46 -07003986 if options.west_runner and not options.west_flash:
Anas Nashif7a361b82019-12-06 11:37:40 -05003987 logger.error("west-runner requires west-flash to be enabled")
Michael Scott421ce462019-06-18 09:37:46 -07003988 sys.exit(1)
3989
Michael Scott4ca54392019-07-09 14:21:30 -07003990 if options.west_flash and not options.device_testing:
Anas Nashif7a361b82019-12-06 11:37:40 -05003991 logger.error("west-flash requires device-testing to be enabled")
Michael Scott4ca54392019-07-09 14:21:30 -07003992 sys.exit(1)
3993
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003994 if options.coverage:
3995 options.enable_coverage = True
Christian Taedckec415a4e2019-11-23 23:25:36 +01003996
3997 if not options.coverage_platform:
3998 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003999
Anas Nashife10b6512017-12-30 13:01:45 -05004000 if options.size:
4001 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08004002 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07004003 sys.exit(0)
4004
Anas Nashife10b6512017-12-30 13:01:45 -05004005 if options.subset:
4006 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04004007 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif7a361b82019-12-06 11:37:40 -05004008 logger.info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04004009 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004010 logger.error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04004011 return
4012
Anas Nashife10b6512017-12-30 13:01:45 -05004013 if not options.testcase_root:
4014 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Anas Nashifd9882382019-12-12 09:58:28 -05004015 os.path.join(ZEPHYR_BASE, "samples")]
Andrew Boie3d348712016-04-08 11:52:13 -07004016
Anas Nashife0d931f2019-12-09 15:23:43 -05004017 if options.show_footprint or options.compare_report or options.release:
4018 options.enable_size_report = True
4019
Anas Nashif56656842019-12-10 12:26:00 -05004020 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
4021
4022 # Set testsuite options from command line.
4023 suite.build_only = options.build_only
Anas Nashifc5ee3952019-12-10 16:38:45 -05004024 suite.cmake_only = options.cmake_only
4025 suite.test_only = options.test_only
Anas Nashif56656842019-12-10 12:26:00 -05004026 suite.enable_slow = options.enable_slow
4027 suite.device_testing = options.device_testing
4028 suite.fixture = options.fixture
4029 suite.enable_asan = options.enable_asan
4030 suite.enable_lsan = options.enable_lsan
4031 suite.enable_coverage = options.enable_coverage
Anas Nashif56656842019-12-10 12:26:00 -05004032 suite.enable_valgrind = options.enable_valgrind
4033 suite.coverage_platform = options.coverage_platform
Anas Nashife9eb0092019-12-10 16:31:22 -05004034 suite.inline_logs = options.inline_logs
Anas Nashifc5ee3952019-12-10 16:38:45 -05004035 suite.enable_size_report = options.enable_size_report
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004036
4037 # Set number of jobs
4038 if options.jobs:
4039 suite.jobs = options.jobs
4040 elif options.build_only:
4041 suite.jobs = multiprocessing.cpu_count() * 2
4042 else:
4043 suite.jobs = multiprocessing.cpu_count()
Anas Nashif7a361b82019-12-06 11:37:40 -05004044 logger.info("JOBS: %d" % suite.jobs)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004045
Anas Nashif83fc06a2019-06-22 11:04:10 -04004046 suite.add_testcases()
4047 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04004048
Anas Nashif83fc06a2019-06-22 11:04:10 -04004049 if options.device_testing:
4050 if options.hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05004051 hwm.load_hardware_map(options.hardware_map)
4052 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004053 if not options.platform:
4054 options.platform = []
Anas Nashif5f908822019-11-25 08:19:25 -05004055 for platform in hwm.connected_hardware:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004056 if platform['connected']:
4057 options.platform.append(platform['platform'])
4058
Anas Nashifd9882382019-12-12 09:58:28 -05004059 elif options.device_serial: # back-ward compatibility
Anas Nashif83fc06a2019-06-22 11:04:10 -04004060 if options.platform and len(options.platform) == 1:
Anas Nashif5f908822019-11-25 08:19:25 -05004061 hwm.load_device_from_cmdline(options.device_serial, options.platform[0])
Anas Nashifebf8dae2019-12-16 09:22:21 -05004062 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004063 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004064 logger.error("""When --device-testing is used with --device-serial, only one
Anas Nashif83fc06a2019-06-22 11:04:10 -04004065 platform is allowed""")
4066
Anas Nashif83fc06a2019-06-22 11:04:10 -04004067 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05004068 sys.exit(1)
4069
Anas Nashif75547e22018-02-24 08:32:14 -06004070 if options.list_tags:
4071 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004072 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06004073 tags = tags.union(tc.tags)
4074
4075 for t in tags:
4076 print("- {}".format(t))
4077
4078 return
4079
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004080 if options.export_tests:
4081 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004082 tests = suite.get_all_tests()
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004083 export_tests(options.export_tests, tests)
4084 return
4085
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004086 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004087
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004088 if options.test:
4089 run_individual_tests = options.test
4090
Anas Nashif434995c2019-12-01 13:55:11 -05004091 if options.list_tests or options.test_tree or options.list_test_duplicates or options.sub_test:
Anas Nashif49b22d42019-06-14 13:45:34 -04004092 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004093 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004094
Anas Nashif19ca7832019-11-18 08:16:21 -08004095 if options.list_test_duplicates:
4096 import collections
4097 dupes = [item for item, count in collections.Counter(all_tests).items() if count > 1]
4098 if dupes:
4099 print("Tests with duplicate identifiers:")
4100 for dupe in dupes:
4101 print("- {}".format(dupe))
4102 for dc in suite.get_testcase(dupe):
4103 print(" - {}".format(dc))
4104 else:
4105 print("No duplicates found.")
4106 return
4107
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004108 if options.sub_test:
Anas Nashifd11fd782019-11-18 10:22:56 -08004109 for st in options.sub_test:
4110 subtests = suite.get_testcase(st)
4111 for sti in subtests:
4112 run_individual_tests.append(sti.name)
4113
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004114 if run_individual_tests:
Anas Nashif7a361b82019-12-06 11:37:40 -05004115 logger.info("Running the following tests:")
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004116 for test in run_individual_tests:
4117 print(" - {}".format(test))
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004118 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004119 logger.info("Tests not found")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004120 return
4121
Anas Nashif434995c2019-12-01 13:55:11 -05004122 elif options.list_tests or options.test_tree:
4123 if options.test_tree:
4124 testsuite = Node("Testsuite")
4125 samples = Node("Samples", parent=testsuite)
4126 tests = Node("Tests", parent=testsuite)
4127
Anas Nashifc1c3cc62019-12-01 13:24:33 -05004128 for test in sorted(all_tests):
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004129 cnt = cnt + 1
Anas Nashif434995c2019-12-01 13:55:11 -05004130 if options.list_tests:
4131 print(" - {}".format(test))
4132
4133 if options.test_tree:
4134 if test.startswith("sample."):
4135 sec = test.split(".")
4136 area = find(samples, lambda node: node.name == sec[1] and node.parent == samples)
4137 if not area:
4138 area = Node(sec[1], parent=samples)
4139
4140 t = Node(test, parent=area)
4141 else:
4142 sec = test.split(".")
4143 area = find(tests, lambda node: node.name == sec[0] and node.parent == tests)
4144 if not area:
4145 area = Node(sec[0], parent=tests)
4146
4147 if area and len(sec) > 2:
4148 subarea = find(area, lambda node: node.name == sec[1] and node.parent == area)
4149 if not subarea:
4150 subarea = Node(sec[1], parent=area)
4151
4152 t = Node(test, parent=subarea)
4153
4154 if options.list_tests:
4155 print("{} total.".format(cnt))
4156
4157 if options.test_tree:
4158 for pre, _, node in RenderTree(testsuite):
4159 print("%s%s" % (pre, node.name))
4160
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004161 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05004162
Anas Nashifbd166f42017-09-02 12:32:08 -04004163 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04004164
4165 if options.only_failed:
4166 suite.get_last_failed()
Anas Nashif5f908822019-11-25 08:19:25 -05004167 suite.selected_platforms = set(p.platform.name for p in suite.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04004168 elif options.load_tests:
4169 suite.load_from_file(options.load_tests)
4170 elif options.test_only:
4171 last_run = os.path.join(options.outdir, "sanitycheck.csv")
4172 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04004173 else:
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004174 discards = suite.apply_filters(
4175 build_only=options.build_only,
4176 enable_slow=options.enable_slow,
4177 platform=options.platform,
4178 arch=options.arch,
4179 tag=options.tag,
4180 exclude_tag=options.exclude_tag,
4181 force_toolchain=options.force_toolchain,
4182 all=options.all,
4183 run_individual_tests=run_individual_tests,
4184 device_testing=options.device_testing
4185
4186 )
Andrew Boie6acbe632015-07-17 12:03:52 -07004187
Anas Nashif30551f42018-01-12 21:56:59 -05004188 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004189 # if we are using command line platform filter, no need to list every
4190 # other platform as excluded, we know that already.
4191 # Show only the discards that apply to the selected platforms on the
4192 # command line
4193
Andrew Boie08ce5a52016-02-22 13:28:10 -08004194 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004195 if options.platform and i.platform.name not in options.platform:
4196 continue
Anas Nashif7a361b82019-12-06 11:37:40 -05004197 logger.debug(
Anas Nashif3ba1d432017-12-05 15:28:44 -05004198 "{:<25} {:<50} {}SKIPPED{}: {}".format(
4199 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04004200 i.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05004201 Fore.YELLOW,
4202 Fore.RESET,
Anas Nashif3ba1d432017-12-05 15:28:44 -05004203 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07004204
Anas Nashif49b22d42019-06-14 13:45:34 -04004205 if options.report_excluded:
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004206 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004207 to_be_run = set()
Anas Nashifd9882382019-12-12 09:58:28 -05004208 for i, p in suite.instances.items():
Anas Nashif83fc06a2019-06-22 11:04:10 -04004209 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04004210
Anas Nashif83fc06a2019-06-22 11:04:10 -04004211 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004212 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004213 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004214 print("- {}".format(not_run))
4215
4216 return
4217
Anas Nashife10b6512017-12-30 13:01:45 -05004218 if options.subset:
Anas Nashifd9882382019-12-12 09:58:28 -05004219 # suite.instances = OrderedDict(sorted(suite.instances.items(),
Anas Nashif83fc06a2019-06-22 11:04:10 -04004220 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05004221 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004222 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004223 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05004224 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04004225 if subset == sets:
4226 end = total
4227 else:
4228 end = start + per_set
4229
Anas Nashif83fc06a2019-06-22 11:04:10 -04004230 sliced_instances = islice(suite.instances.items(), start, end)
4231 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004232
Anas Nashif83fc06a2019-06-22 11:04:10 -04004233 if options.save_tests:
4234 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07004235 return
4236
Anas Nashif7a361b82019-12-06 11:37:40 -05004237 logger.info("%d test configurations selected, %d configurations discarded due to filters." %
Anas Nashifd9882382019-12-12 09:58:28 -05004238 (len(suite.instances), len(discards)))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004239
Peter Bigot3d46ea52019-11-21 12:00:18 -06004240 if options.device_testing:
4241 print("\nDevice testing on:")
Anas Nashif0c69c282019-12-18 10:41:27 -05004242 hwm.dump(suite.connected_hardware, suite.selected_platforms)
Anas Nashif5f908822019-11-25 08:19:25 -05004243 print("")
Peter Bigot3d46ea52019-11-21 12:00:18 -06004244
Anas Nashif83fc06a2019-06-22 11:04:10 -04004245 if options.dry_run:
4246 duration = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -05004247 logger.info("Completed in %d seconds" % (duration))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004248 return
4249
4250 retries = options.retry_failed + 1
4251 completed = 0
4252
4253 suite.update()
4254 suite.start_time = start_time
4255
4256 while True:
4257 completed += 1
4258
4259 if completed > 1:
Anas Nashifd9882382019-12-12 09:58:28 -05004260 logger.info("%d Iteration:" % (completed))
4261 time.sleep(60) # waiting for the system to settle down
Anas Nashif83fc06a2019-06-22 11:04:10 -04004262 suite.total_done = suite.total_tests - suite.total_failed
4263 suite.total_failed = 0
4264
Anas Nashifc5ee3952019-12-10 16:38:45 -05004265 suite.execute()
Anas Nashif7a361b82019-12-06 11:37:40 -05004266 print("")
Andrew Boie6acbe632015-07-17 12:03:52 -07004267
Anas Nashif83fc06a2019-06-22 11:04:10 -04004268 retries = retries - 1
4269 if retries == 0 or suite.total_failed == 0:
4270 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06004271
Anas Nashif83fc06a2019-06-22 11:04:10 -04004272 suite.misc_reports(options.compare_report, options.show_footprint,
Anas Nashifd9882382019-12-12 09:58:28 -05004273 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07004274
Anas Nashif83a98e52019-11-24 07:42:06 -05004275 suite.duration = time.time() - start_time
4276 suite.summary(options.disable_unrecognized_section_test)
4277
Anas Nashife10b6512017-12-30 13:01:45 -05004278 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004279 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004280 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07004281
4282 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004283 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004284 if ts_plat and (ts_plat.type in {"native", "unit"}):
4285 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07004286
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004287 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07004288 options.gcov_tool = "gcov"
4289 else:
4290 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
Anas Nashifd9882382019-12-12 09:58:28 -05004291 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
Andrew Boie49cf4862019-07-08 12:02:13 -07004292
Anas Nashif7a361b82019-12-06 11:37:40 -05004293 logger.info("Generating coverage files...")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01004294 coverage_tool = CoverageTool.factory(options.coverage_tool)
4295 coverage_tool.add_ignore_file('generated')
4296 coverage_tool.add_ignore_directory('tests')
4297 coverage_tool.add_ignore_directory('samples')
4298 coverage_tool.generate(options.outdir)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03004299
Anas Nashif83fc06a2019-06-22 11:04:10 -04004300 if options.device_testing:
4301 print("\nHardware distribution summary:\n")
Anas Nashif5f908822019-11-25 08:19:25 -05004302 table = []
4303 header = ['Board', 'ID', 'Counter']
4304 for p in hwm.connected_hardware:
4305 if p['connected'] and p['platform'] in suite.selected_platforms:
4306 row = [p['platform'], p.get('id', None), p['counter']]
4307 table.append(row)
4308 print(tabulate(table, headers=header, tablefmt="github"))
4309
Anas Nashif56656842019-12-10 12:26:00 -05004310 suite.save_reports(options.report_name,
4311 options.report_dir,
4312 options.no_update,
4313 options.release,
4314 options.only_failed)
4315
Anas Nashif83fc06a2019-06-22 11:04:10 -04004316 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07004317 sys.exit(1)
4318
Anas Nashifd9882382019-12-12 09:58:28 -05004319
Andrew Boie6acbe632015-07-17 12:03:52 -07004320if __name__ == "__main__":
Andrew Boie1fe1f3a2020-01-02 19:27:40 -08004321 try:
4322 main()
4323 finally:
4324 os.system("stty sane")