blob: b6ab2a1f0112f4259857b301e5e4f1f421f61078 [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():
Anas Nashif52b66c62019-12-20 13:11:44 -0500807 logger.debug("Timed out")
Anas Nashif73440ea2018-02-19 10:57:03 -0600808 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600809
810 if ser.isOpen():
811 ser.close()
812
Anas Nashif17d066b2019-12-17 14:37:16 -0500813 handler_time = time.time() - start_time
814
Anas Nashifd3384fb2018-02-22 06:44:16 -0600815 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400816 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600817 if c not in harness.tests:
818 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500819
Anas Nashif17d066b2019-12-17 14:37:16 -0500820 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400821
Anas Nashif61e21632018-04-08 13:30:16 -0500822 self.instance.results = harness.tests
Anas Nashif17d066b2019-12-17 14:37:16 -0500823
Anas Nashif73440ea2018-02-19 10:57:03 -0600824 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400825 self.set_state(harness.state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500826 if harness.state == "failed":
827 self.instance.reason = "Failed"
Anas Nashif73440ea2018-02-19 10:57:03 -0600828 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400829 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600830
Anas Nashiffc85ff02019-12-19 11:59:54 -0500831 if post_script:
832 with subprocess.Popen(post_script, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
833 try:
834 (stdout, stderr) = proc.communicate(timeout=30)
835 logger.debug(stdout.decode())
836
837 except subprocess.TimeoutExpired:
838 proc.kill()
839 (stdout, stderr) = proc.communicate()
840 logger.error("{} timed out".format(post_script))
841
Anas Nashif83fc06a2019-06-22 11:04:10 -0400842 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500843
Anas Nashif3a00d0c2019-11-12 11:07:15 -0500844 self.record(harness)
845
Anas Nashif11ee5252019-12-04 12:59:10 -0500846
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300847class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700848 """Spawns a thread to monitor QEMU output from pipes
849
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400850 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700851 We need to do this as once qemu starts, it runs forever until killed.
852 Test cases emit special messages to the console as they run, we check
853 for these to collect whether the test passed or failed.
854 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700855
Anas Nashif83fc06a2019-06-22 11:04:10 -0400856 def __init__(self, instance, type_str):
857 """Constructor
858
859 @param instance Test instance
860 """
861
862 super().__init__(instance, type_str)
863 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
864
865 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
866
Andrew Boie6acbe632015-07-17 12:03:52 -0700867 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500868 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700869 fifo_in = fifo_fn + ".in"
870 fifo_out = fifo_fn + ".out"
871
872 # These in/out nodes are named from QEMU's perspective, not ours
873 if os.path.exists(fifo_in):
874 os.unlink(fifo_in)
875 os.mkfifo(fifo_in)
876 if os.path.exists(fifo_out):
877 os.unlink(fifo_out)
878 os.mkfifo(fifo_out)
879
880 # We don't do anything with out_fp but we need to open it for
881 # writing so that QEMU doesn't block, due to the way pipes work
882 out_fp = open(fifo_in, "wb")
883 # Disable internal buffering, we don't
884 # want read() or poll() to ever block if there is data in there
885 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800886 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700887
888 start_time = time.time()
889 timeout_time = start_time + timeout
890 p = select.poll()
891 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400892 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700893
Andrew Boie6acbe632015-07-17 12:03:52 -0700894 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500895 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700896 while True:
897 this_timeout = int((timeout_time - time.time()) * 1000)
898 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400899 if not out_state:
900 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700901 break
902
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500903 try:
904 c = in_fp.read(1).decode("utf-8")
905 except UnicodeDecodeError:
906 # Test is writing something weird, fail
907 out_state = "unexpected byte"
908 break
909
Andrew Boie6acbe632015-07-17 12:03:52 -0700910 if c == "":
911 # EOF, this shouldn't happen unless QEMU crashes
912 out_state = "unexpected eof"
913 break
914 line = line + c
915 if c != "\n":
916 continue
917
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300918 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700919 log_out_fp.write(line)
920 log_out_fp.flush()
921 line = line.strip()
Anas Nashif7a361b82019-12-06 11:37:40 -0500922 logger.debug("QEMU: %s" % line)
Andrew Boie6acbe632015-07-17 12:03:52 -0700923
Anas Nashif576be982017-12-23 20:20:27 -0500924 harness.handle(line)
925 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400926 # if we have registered a fail make sure the state is not
927 # overridden by a false success message coming from the
928 # testsuite
929 if out_state != 'failed':
930 out_state = harness.state
931
932 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700933 # the timeout and wait for 2 more seconds to catch anything
934 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700935 # coverage is enabled since dumping this information can
936 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500937 if not timeout_extended or harness.capture_coverage:
Anas Nashifd9882382019-12-12 09:58:28 -0500938 timeout_extended = True
Anas Nashiff29087e2019-01-25 09:37:38 -0500939 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700940 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500941 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500942 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700943 line = ""
944
Anas Nashif83fc06a2019-06-22 11:04:10 -0400945 handler.record(harness)
946
947 handler_time = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -0500948 logger.debug("QEMU complete (%s) after %f seconds" %
Anas Nashifd9882382019-12-12 09:58:28 -0500949 (out_state, handler_time))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400950 handler.set_state(out_state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500951 if out_state == "timeout":
952 handler.instance.reason = "Timeout"
953 elif out_state == "failed":
954 handler.instance.reason = "Failed"
Andrew Boie6acbe632015-07-17 12:03:52 -0700955
956 log_out_fp.close()
957 out_fp.close()
958 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400959 if os.path.exists(pid_fn):
960 pid = int(open(pid_fn).read())
961 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700962
Anas Nashifd6476ee2019-04-11 11:40:09 -0400963 try:
964 if pid:
965 os.kill(pid, signal.SIGTERM)
966 except ProcessLookupError:
967 # Oh well, as long as it's dead! User probably sent Ctrl-C
968 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800969
Andrew Boie6acbe632015-07-17 12:03:52 -0700970 os.unlink(fifo_in)
971 os.unlink(fifo_out)
972
Anas Nashif83fc06a2019-06-22 11:04:10 -0400973 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700974 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500975 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700976
977 # We pass this to QEMU which looks for fifos with .in and .out
978 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400979 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700980
Anas Nashif83fc06a2019-06-22 11:04:10 -0400981 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700982 if os.path.exists(self.pid_fn):
983 os.unlink(self.pid_fn)
984
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500985 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500986
Anas Nashif83fc06a2019-06-22 11:04:10 -0400987 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500988 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600989 harness.configure(self.instance)
990 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -0400991 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300992 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500993 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600994
995 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700996 self.thread.daemon = True
Anas Nashif7a361b82019-12-06 11:37:40 -0500997 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700998 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400999 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001000
Anas Nashifd9882382019-12-12 09:58:28 -05001001 logger.debug("Running %s (%s)" % (self.name, self.type_str))
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001002 command = [get_generator()[0]]
1003 command += ["-C", self.build_dir, "run"]
1004
1005 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -05001006 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001007 proc.wait()
1008 self.returncode = proc.returncode
1009
1010 if self.returncode != 0:
1011 self.set_state("failed", 0)
1012 self.instance.reason = "Exited with {}".format(self.returncode)
1013
Andrew Boie6acbe632015-07-17 12:03:52 -07001014 def get_fifo(self):
1015 return self.fifo_fn
1016
Anas Nashif11ee5252019-12-04 12:59:10 -05001017
Andrew Boie6acbe632015-07-17 12:03:52 -07001018class SizeCalculator:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001019 alloc_sections = [
1020 "bss",
1021 "noinit",
1022 "app_bss",
1023 "app_noinit",
1024 "ccm_bss",
1025 "ccm_noinit"
Anas Nashifd9882382019-12-12 09:58:28 -05001026 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001027
1028 rw_sections = [
1029 "datas",
1030 "initlevel",
1031 "exceptions",
1032 "initshell",
1033 "_static_thread_area",
1034 "_k_timer_area",
1035 "_k_mem_slab_area",
1036 "_k_mem_pool_area",
1037 "sw_isr_table",
1038 "_k_sem_area",
1039 "_k_mutex_area",
1040 "app_shmem_regions",
1041 "_k_fifo_area",
1042 "_k_lifo_area",
1043 "_k_stack_area",
1044 "_k_msgq_area",
1045 "_k_mbox_area",
1046 "_k_pipe_area",
1047 "net_if",
1048 "net_if_dev",
1049 "net_stack",
1050 "net_l2_data",
1051 "_k_queue_area",
1052 "_net_buf_pool_area",
1053 "app_datas",
1054 "kobject_data",
1055 "mmu_tables",
1056 "app_pad",
1057 "priv_stacks",
1058 "ccm_data",
1059 "usb_descriptor",
1060 "usb_data", "usb_bos_desc",
1061 'log_backends_sections',
1062 'log_dynamic_sections',
1063 'log_const_sections',
1064 "app_smem",
1065 'shell_root_cmds_sections',
1066 'log_const_sections',
1067 "font_entry_sections",
1068 "priv_stacks_noinit",
1069 "_TEXT_SECTION_NAME_2",
1070 "_GCOV_BSS_SECTION_NAME",
1071 "gcov",
1072 "nocache"
Anas Nashifd9882382019-12-12 09:58:28 -05001073 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001074
Andrew Boie73b4ee62015-10-07 11:33:22 -07001075 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001076 ro_sections = [
1077 "text",
1078 "ctors",
1079 "init_array",
1080 "reset",
1081 "object_access",
1082 "rodata",
1083 "devconfig",
1084 "net_l2",
1085 "vector",
1086 "sw_isr_table",
1087 "_settings_handlers_area",
1088 "_bt_channels_area",
1089 "_bt_br_channels_area",
1090 "_bt_services_area",
1091 "vectors",
1092 "net_socket_register",
1093 "net_ppp_proto"
Anas Nashifd9882382019-12-12 09:58:28 -05001094 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001095
Andrew Boie52fef672016-11-29 12:21:59 -08001096 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001097 """Constructor
1098
Andrew Boiebbd670c2015-08-17 13:16:11 -07001099 @param filename Path to the output binary
1100 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001101 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001102 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001103 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001104 magic = f.read(4)
1105
Anas Nashifb4bdd662018-08-15 17:12:28 -05001106 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001107 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001108 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1109 except Exception as e:
1110 print(str(e))
1111 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001112
1113 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001114 # GREP can not be used as it returns an error if the symbol is not
1115 # found.
1116 is_xip_command = "nm " + filename + \
Anas Nashifd9882382019-12-12 09:58:28 -05001117 " | awk '/CONFIG_XIP/ { print $3 }'"
Anas Nashif3ba1d432017-12-05 15:28:44 -05001118 is_xip_output = subprocess.check_output(
1119 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1120 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001121 try:
1122 if is_xip_output.endswith("no symbols"):
1123 raise SanityRuntimeError("%s has no symbol information" % filename)
1124 except Exception as e:
1125 print(str(e))
1126 sys.exit(2)
1127
Andrew Boie6acbe632015-07-17 12:03:52 -07001128 self.is_xip = (len(is_xip_output) != 0)
1129
Andrew Boiebbd670c2015-08-17 13:16:11 -07001130 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001131 self.sections = []
1132 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001133 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001134 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001135
1136 self._calculate_sizes()
1137
1138 def get_ram_size(self):
1139 """Get the amount of RAM the application will use up on the device
1140
1141 @return amount of RAM, in bytes
1142 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001143 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001144
1145 def get_rom_size(self):
1146 """Get the size of the data that this application uses on device's flash
1147
1148 @return amount of ROM, in bytes
1149 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001150 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001151
1152 def unrecognized_sections(self):
1153 """Get a list of sections inside the binary that weren't recognized
1154
David B. Kinder29963c32017-06-16 12:32:42 -07001155 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001156 """
1157 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001158 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001159 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001160 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001161 return slist
1162
1163 def _calculate_sizes(self):
1164 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001165 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001166 objdump_output = subprocess.check_output(
1167 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001168
1169 for line in objdump_output:
1170 words = line.split()
1171
Anas Nashifd9882382019-12-12 09:58:28 -05001172 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001173 continue
1174
1175 index = words[0]
Anas Nashifd9882382019-12-12 09:58:28 -05001176 if not index[0].isdigit(): # Skip lines that do not start
1177 continue # with a digit
Andrew Boie6acbe632015-07-17 12:03:52 -07001178
Anas Nashifd9882382019-12-12 09:58:28 -05001179 name = words[1] # Skip lines with section names
1180 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001181 continue
1182
Andrew Boie73b4ee62015-10-07 11:33:22 -07001183 # TODO this doesn't actually reflect the size in flash or RAM as
1184 # it doesn't include linker-imposed padding between sections.
1185 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001186 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001187 if size == 0:
1188 continue
1189
Andrew Boie73b4ee62015-10-07 11:33:22 -07001190 load_addr = int(words[4], 16)
1191 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001192
1193 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001194 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001195 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001196 if name in SizeCalculator.alloc_sections:
1197 self.ram_size += size
1198 stype = "alloc"
1199 elif name in SizeCalculator.rw_sections:
1200 self.ram_size += size
1201 self.rom_size += size
1202 stype = "rw"
1203 elif name in SizeCalculator.ro_sections:
1204 self.rom_size += size
1205 if not self.is_xip:
1206 self.ram_size += size
1207 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001208 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001209 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001210 if name not in self.extra_sections:
1211 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001212
Anas Nashif3ba1d432017-12-05 15:28:44 -05001213 self.sections.append({"name": name, "load_addr": load_addr,
1214 "size": size, "virt_addr": virt_addr,
1215 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001216
1217
Andrew Boie6acbe632015-07-17 12:03:52 -07001218# "list" - List of strings
1219# "list:<type>" - List of <type>
1220# "set" - Set of unordered, unique strings
1221# "set:<type>" - Set of <type>
1222# "float" - Floating point
1223# "int" - Integer
1224# "bool" - Boolean
1225# "str" - String
1226
1227# XXX Be sure to update __doc__ if you change any of this!!
1228
Anas Nashif83fc06a2019-06-22 11:04:10 -04001229platform_valid_keys = {
Anas Nashifd9882382019-12-12 09:58:28 -05001230 "supported_toolchains": {"type": "list", "default": []},
1231 "env": {"type": "list", "default": []}
1232}
Andrew Boie6acbe632015-07-17 12:03:52 -07001233
Anas Nashif3ba1d432017-12-05 15:28:44 -05001234testcase_valid_keys = {"tags": {"type": "set", "required": False},
1235 "type": {"type": "str", "default": "integration"},
1236 "extra_args": {"type": "list"},
1237 "extra_configs": {"type": "list"},
1238 "build_only": {"type": "bool", "default": False},
1239 "build_on_all": {"type": "bool", "default": False},
1240 "skip": {"type": "bool", "default": False},
1241 "slow": {"type": "bool", "default": False},
1242 "timeout": {"type": "int", "default": 60},
1243 "min_ram": {"type": "int", "default": 8},
1244 "depends_on": {"type": "set"},
1245 "min_flash": {"type": "int", "default": 32},
1246 "arch_whitelist": {"type": "set"},
1247 "arch_exclude": {"type": "set"},
1248 "extra_sections": {"type": "list", "default": []},
1249 "platform_exclude": {"type": "set"},
1250 "platform_whitelist": {"type": "set"},
1251 "toolchain_exclude": {"type": "set"},
1252 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001253 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001254 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301255 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001256 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001257
Anas Nashif11ee5252019-12-04 12:59:10 -05001258
Andrew Boie6acbe632015-07-17 12:03:52 -07001259class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001260 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001261 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001262
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001263 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001264 """Instantiate a new SanityConfigParser object
1265
Anas Nashifa792a3d2017-04-04 18:47:49 -04001266 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001267 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001268 self.data = {}
1269 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001270 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001271 self.tests = {}
1272 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001273
1274 def load(self):
1275 self.data = scl.yaml_load_verify(self.filename, self.schema)
1276
Anas Nashif255625b2017-12-05 15:08:26 -05001277 if 'tests' in self.data:
1278 self.tests = self.data['tests']
1279 if 'common' in self.data:
1280 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001281
1282 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001283 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001284 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001285 if typestr == "str":
1286 return v
1287
1288 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001289 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001290
1291 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001292 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001293
1294 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001295 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001296
Anas Nashif3ba1d432017-12-05 15:28:44 -05001297 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001298 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001299 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001300 vs = v.split()
1301 if len(typestr) > 4 and typestr[4] == ":":
1302 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1303 else:
1304 return vs
1305
1306 elif typestr.startswith("set"):
1307 vs = v.split()
1308 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001309 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001310 else:
1311 return set(vs)
1312
Anas Nashif576be982017-12-23 20:20:27 -05001313 elif typestr.startswith("map"):
1314 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001315 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001316 raise ConfigurationError(
1317 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001318
Anas Nashifb4754ed2017-12-05 17:27:58 -05001319 def get_test(self, name, valid_keys):
1320 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001321
Anas Nashifb4754ed2017-12-05 17:27:58 -05001322 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001323 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001324 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001325 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001326 here, it will generate an error. Each value in this dictionary
1327 is another dictionary containing metadata:
1328
1329 "default" - Default value if not given
1330 "type" - Data type to convert the text value to. Simple types
1331 supported are "str", "float", "int", "bool" which will get
1332 converted to respective Python data types. "set" and "list"
1333 may also be specified which will split the value by
1334 whitespace (but keep the elements as strings). finally,
1335 "list:<type>" and "set:<type>" may be given which will
1336 perform a type conversion after splitting the value up.
1337 "required" - If true, raise an error if not defined. If false
1338 and "default" isn't specified, a type conversion will be
1339 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001340 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001341 type conversion and default values filled in per valid_keys
1342 """
1343
1344 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001345 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001346 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001347
Anas Nashifb4754ed2017-12-05 17:27:58 -05001348 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001349 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001350 raise ConfigurationError(
1351 self.filename,
1352 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001353 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001354
Anas Nashiffa695d22017-10-04 16:14:27 -04001355 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001356 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001357 # By default, we just concatenate string values of keys
1358 # which appear both in "common" and per-test sections,
1359 # but some keys are handled in adhoc way based on their
1360 # semantics.
1361 if k == "filter":
1362 d[k] = "(%s) and (%s)" % (d[k], v)
1363 else:
1364 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001365 else:
1366 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001367
Andrew Boie08ce5a52016-02-22 13:28:10 -08001368 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001369 if k not in d:
1370 if "required" in kinfo:
1371 required = kinfo["required"]
1372 else:
1373 required = False
1374
1375 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001376 raise ConfigurationError(
1377 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001378 "missing required value for '%s' in test '%s'" %
1379 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001380 else:
1381 if "default" in kinfo:
1382 default = kinfo["default"]
1383 else:
1384 default = self._cast_value("", kinfo["type"])
1385 d[k] = default
1386 else:
1387 try:
1388 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001389 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001390 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001391 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
Anas Nashifd9882382019-12-12 09:58:28 -05001392 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001393
1394 return d
1395
1396
1397class Platform:
1398 """Class representing metadata for a particular platform
1399
Anas Nashifc7406082015-12-13 15:00:31 -05001400 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001401
Anas Nashif83fc06a2019-06-22 11:04:10 -04001402 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
Anas Nashifd9882382019-12-12 09:58:28 -05001403 "scripts", "sanity_chk", "platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001404
Anas Nashif83fc06a2019-06-22 11:04:10 -04001405 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001406 """Constructor.
1407
Andrew Boie6acbe632015-07-17 12:03:52 -07001408 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001409
1410 self.name = ""
1411 self.sanitycheck = True
1412 # if no RAM size is specified by the board, take a default of 128K
1413 self.ram = 128
1414
1415 self.ignore_tags = []
1416 self.default = False
1417 # if no flash size is specified by the board, take a default of 512K
1418 self.flash = 512
1419 self.supported = set()
1420
1421 self.arch = ""
1422 self.type = "na"
1423 self.simulation = "na"
1424 self.supported_toolchains = []
1425 self.env = []
1426 self.env_satisfied = True
1427 self.filter_data = dict()
1428
1429 def load(self, platform_file):
1430 scp = SanityConfigParser(platform_file, self.platform_schema)
1431 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001432 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001433
Anas Nashif255625b2017-12-05 15:08:26 -05001434 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001435 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001436 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001437 self.ram = data.get("ram", 128)
1438 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001439 self.ignore_tags = testing.get("ignore_tags", [])
1440 self.default = testing.get("default", False)
1441 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001442 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001443 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001444 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001445 for item in supp_feature.split(":"):
1446 self.supported.add(item)
1447
Anas Nashif255625b2017-12-05 15:08:26 -05001448 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001449 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001450 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001451 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001452 self.env = data.get("env", [])
1453 self.env_satisfied = True
1454 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001455 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001456 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001457
Andrew Boie6acbe632015-07-17 12:03:52 -07001458 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001459 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001460
Anas Nashif11ee5252019-12-04 12:59:10 -05001461
Anas Nashif83fc06a2019-06-22 11:04:10 -04001462class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001463 """Class representing a test application
1464 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001465
Anas Nashif83fc06a2019-06-22 11:04:10 -04001466 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001467 """TestCase constructor.
1468
Anas Nashif877d3ca2017-12-05 17:39:29 -05001469 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001470 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001471 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001472
Andrew Boie6acbe632015-07-17 12:03:52 -07001473 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001474 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001475 the test case is <workdir>/<name>.
1476
Marc Herbert1c8632c2019-04-15 17:58:45 -07001477 @param testcase_root os.path.abspath() of one of the --testcase-root
1478 @param workdir Sub-directory of testcase_root where the
1479 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001480 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001481 in the test case configuration file. For many test cases that just
1482 define one test, can be anything and is usually "test". This is
1483 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001484 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001485 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001486 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001487 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001488
Anas Nashif83fc06a2019-06-22 11:04:10 -04001489 self.id = ""
1490 self.source_dir = ""
1491 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001492 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001493 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001494
Anas Nashif83fc06a2019-06-22 11:04:10 -04001495 self.type = None
1496 self.tags = None
1497 self.extra_args = None
1498 self.extra_configs = None
1499 self.arch_whitelist = None
1500 self.arch_exclude = None
1501 self.skip = None
1502 self.platform_exclude = None
1503 self.platform_whitelist = None
1504 self.toolchain_exclude = None
1505 self.toolchain_whitelist = None
1506 self.tc_filter = None
1507 self.timeout = 60
1508 self.harness = ""
1509 self.harness_config = {}
1510 self.build_only = True
1511 self.build_on_all = False
1512 self.slow = False
1513 self.min_ram = None
1514 self.depends_on = None
1515 self.min_flash = None
1516 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001517
Anas Nashif83fc06a2019-06-22 11:04:10 -04001518 @staticmethod
1519 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001520
Marc Herbert1c8632c2019-04-15 17:58:45 -07001521 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001522 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001523 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001524 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001525 relative_tc_root = os.path.relpath(canonical_testcase_root,
Anas Nashif11ee5252019-12-04 12:59:10 -05001526 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001527 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001528 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001529
Marc Herbert1c8632c2019-04-15 17:58:45 -07001530 # workdir can be "."
1531 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001532 return unique
1533
Anas Nashif83fc06a2019-06-22 11:04:10 -04001534 @staticmethod
1535 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001536 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001537 # do not match until end-of-line, otherwise we won't allow
1538 # stc_regex below to catch the ones that are declared in the same
1539 # line--as we only search starting the end of this match
1540 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001541 re.MULTILINE)
1542 stc_regex = re.compile(
Anas Nashifd9882382019-12-12 09:58:28 -05001543 br"^\s*" # empy space at the beginning is ok
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001544 # catch the case where it is declared in the same sentence, e.g:
1545 #
1546 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1547 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1548 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
Anas Nashif9091a012019-11-24 09:22:22 -05001549 br"ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?"
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001550 # Consume the argument that becomes the extra testcse
1551 br"\(\s*"
1552 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1553 # _setup_teardown() variant has two extra arguments that we ignore
1554 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1555 br"\s*\)",
1556 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001557 re.MULTILINE)
1558 suite_run_regex = re.compile(
1559 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1560 re.MULTILINE)
1561 achtung_regex = re.compile(
1562 br"(#ifdef|#endif)",
1563 re.MULTILINE)
1564 warnings = None
1565
1566 with open(inf_name) as inf:
Anas Nashif19d67e42019-11-21 11:33:12 -05001567 if os.name == 'nt':
Anas Nashifd9882382019-12-12 09:58:28 -05001568 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
Anas Nashif19d67e42019-11-21 11:33:12 -05001569 else:
Anas Nashifd9882382019-12-12 09:58:28 -05001570 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
1571 'offset': 0}
Anas Nashif19d67e42019-11-21 11:33:12 -05001572
1573 with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001574 # contextlib makes pylint think main_c isn't subscriptable
1575 # pylint: disable=unsubscriptable-object
1576
Anas Nashifaae71d72018-04-21 22:26:48 -05001577 suite_regex_match = suite_regex.search(main_c)
1578 if not suite_regex_match:
1579 # can't find ztest_test_suite, maybe a client, because
1580 # it includes ztest.h
1581 return None, None
1582
1583 suite_run_match = suite_run_regex.search(main_c)
1584 if not suite_run_match:
1585 raise ValueError("can't find ztest_run_test_suite")
1586
1587 achtung_matches = re.findall(
1588 achtung_regex,
1589 main_c[suite_regex_match.end():suite_run_match.start()])
1590 if achtung_matches:
1591 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001592 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001593 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001594 stc_regex,
1595 main_c[suite_regex_match.end():suite_run_match.start()])
Anas Nashifd9882382019-12-12 09:58:28 -05001596 matches = [match.decode().replace("test_", "") for match in _matches]
Anas Nashifaae71d72018-04-21 22:26:48 -05001597 return matches, warnings
1598
1599 def scan_path(self, path):
1600 subcases = []
1601 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1602 try:
1603 _subcases, warnings = self.scan_file(filename)
1604 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001605 logger.error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001606 if _subcases:
1607 subcases += _subcases
1608 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001609 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashif434995c2019-12-01 13:55:11 -05001610 for filename in glob.glob(os.path.join(path, "*.c")):
1611 try:
1612 _subcases, warnings = self.scan_file(filename)
1613 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001614 logger.error("%s: %s" % (filename, warnings))
Anas Nashif434995c2019-12-01 13:55:11 -05001615 if _subcases:
1616 subcases += _subcases
1617 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001618 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001619 return subcases
1620
Anas Nashif83fc06a2019-06-22 11:04:10 -04001621 def parse_subcases(self, test_path):
Anas Nashif9091a012019-11-24 09:22:22 -05001622 results = self.scan_path(test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001623 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001624 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001625 self.cases.append(name)
1626
Anas Nashiff16e92c2019-03-31 16:58:12 -04001627 if not results:
1628 self.cases.append(self.id)
1629
Anas Nashif75547e22018-02-24 08:32:14 -06001630 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001631 return self.name
1632
1633
Andrew Boie6acbe632015-07-17 12:03:52 -07001634class TestInstance:
1635 """Class representing the execution of a particular TestCase on a platform
1636
1637 @param test The TestCase object we want to build/execute
1638 @param platform Platform object that we want to build and run against
1639 @param base_outdir Base directory for all test results. The actual
1640 out directory used is <outdir>/<platform>/<test case name>
1641 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001642
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001643 def __init__(self, testcase, platform, outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001644
Anas Nashif83fc06a2019-06-22 11:04:10 -04001645 self.testcase = testcase
1646 self.platform = platform
1647
1648 self.status = None
Anas Nashif813deb72019-12-20 10:42:59 -05001649 self.reason = "Unknown"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001650 self.metrics = dict()
1651 self.handler = None
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001652 self.outdir = outdir
Anas Nashif83fc06a2019-06-22 11:04:10 -04001653
1654 self.name = os.path.join(platform.name, testcase.name)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001655 self.build_dir = os.path.join(outdir, platform.name, testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001656
Anas Nashif56656842019-12-10 12:26:00 -05001657 self.build_only = True
1658 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001659
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001660 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001661
Marc Herbert0f7255c2019-04-05 14:14:21 -07001662 def __lt__(self, other):
1663 return self.name < other.name
1664
Anas Nashif56656842019-12-10 12:26:00 -05001665 def check_build_or_run(self, build_only=False, enable_slow=False, device_testing=False, fixture=[]):
1666
Anas Nashif19d67e42019-11-21 11:33:12 -05001667 # right now we only support building on windows. running is still work
1668 # in progress.
Anas Nashif19d67e42019-11-21 11:33:12 -05001669 if os.name == 'nt':
Anas Nashif56656842019-12-10 12:26:00 -05001670 self.build_only = True
1671 self.run = False
1672 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001673
Anas Nashif56656842019-12-10 12:26:00 -05001674 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001675
1676 # we asked for build-only on the command line
Anas Nashif56656842019-12-10 12:26:00 -05001677 if build_only or self.testcase.build_only:
1678 self.build_only = True
1679 self.run = False
1680 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001681
1682 # Do not run slow tests:
Anas Nashif56656842019-12-10 12:26:00 -05001683 skip_slow = self.testcase.slow and not enable_slow
Anas Nashif83fc06a2019-06-22 11:04:10 -04001684 if skip_slow:
Anas Nashif56656842019-12-10 12:26:00 -05001685 self.build_only = True
1686 self.run = False
1687 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001688
Anas Nashifd9882382019-12-12 09:58:28 -05001689 runnable = bool(self.testcase.type == "unit" or \
1690 self.platform.type == "native" or \
1691 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1692 device_testing)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001693
1694 if self.platform.simulation == "nsim":
1695 if not find_executable("nsimdrv"):
1696 runnable = False
1697
1698 if self.platform.simulation == "renode":
1699 if not find_executable("renode"):
1700 runnable = False
1701
1702 # console harness allows us to run the test and capture data.
1703 if self.testcase.harness == 'console':
1704
1705 # if we have a fixture that is also being supplied on the
1706 # command-line, then we need to run the test, not just build it.
1707 if "fixture" in self.testcase.harness_config:
Anas Nashifd9882382019-12-12 09:58:28 -05001708 fixture_cfg = self.testcase.harness_config['fixture']
1709 if fixture_cfg in fixture:
Anas Nashif56656842019-12-10 12:26:00 -05001710 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001711 else:
Anas Nashif56656842019-12-10 12:26:00 -05001712 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001713 else:
Anas Nashif56656842019-12-10 12:26:00 -05001714 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001715 elif self.testcase.harness:
Anas Nashif56656842019-12-10 12:26:00 -05001716 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001717 else:
Anas Nashif56656842019-12-10 12:26:00 -05001718 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001719
Anas Nashif56656842019-12-10 12:26:00 -05001720 self.build_only = not (not _build_only and runnable)
1721 self.run = not self.build_only
1722 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001723
Anas Nashif56656842019-12-10 12:26:00 -05001724 def create_overlay(self, platform, enable_asan=False, enable_coverage=False, coverage_platform=[]):
Marc Herbertc7633de2019-07-06 15:52:31 -07001725 # Create this in a "sanitycheck/" subdirectory otherwise this
1726 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1727 # will silently give that second time precedence over any
1728 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001729 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001730 os.makedirs(subdir, exist_ok=True)
1731 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashif56656842019-12-10 12:26:00 -05001732
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001733 with open(file, "w") as f:
1734 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001735
Anas Nashif83fc06a2019-06-22 11:04:10 -04001736 if self.testcase.extra_configs:
1737 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001738
Anas Nashif56656842019-12-10 12:26:00 -05001739 if enable_coverage:
1740 if platform.name in coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001741 content = content + "\nCONFIG_COVERAGE=y"
1742
Anas Nashif56656842019-12-10 12:26:00 -05001743 if enable_asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001744 if platform.type == "native":
1745 content = content + "\nCONFIG_ASAN=y"
1746
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001747 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001748
Andrew Boie6acbe632015-07-17 12:03:52 -07001749 def calculate_sizes(self):
1750 """Get the RAM/ROM sizes of a test case.
1751
1752 This can only be run after the instance has been executed by
1753 MakeGenerator, otherwise there won't be any binaries to measure.
1754
1755 @return A SizeCalculator object
1756 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001757 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1758 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001759 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001760 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001761 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001762
1763 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001764
1765 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001766 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001767
1768
Anas Nashif83fc06a2019-06-22 11:04:10 -04001769class CMake():
Anas Nashif83fc06a2019-06-22 11:04:10 -04001770 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1771 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1772
1773 def __init__(self, testcase, platform, source_dir, build_dir):
1774
1775 self.cwd = None
1776 self.capture_output = True
1777
1778 self.defconfig = {}
1779 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001780
1781 self.instance = None
1782 self.testcase = testcase
1783 self.platform = platform
1784 self.source_dir = source_dir
1785 self.build_dir = build_dir
1786 self.log = "build.log"
1787
1788 def parse_generated(self):
1789 self.defconfig = {}
1790 return {}
1791
1792 def run_build(self, args=[]):
1793
Anas Nashif7a361b82019-12-06 11:37:40 -05001794 logger.debug("Building %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001795
1796 cmake_args = []
1797 cmake_args.extend(args)
1798 cmake = shutil.which('cmake')
1799 cmd = [cmake] + cmake_args
1800 kwargs = dict()
1801
1802 if self.capture_output:
1803 kwargs['stdout'] = subprocess.PIPE
1804 # CMake sends the output of message() to stderr unless it's STATUS
1805 kwargs['stderr'] = subprocess.STDOUT
1806
1807 if self.cwd:
1808 kwargs['cwd'] = self.cwd
1809
1810 p = subprocess.Popen(cmd, **kwargs)
1811 out, _ = p.communicate()
1812
1813 results = {}
1814 if p.returncode == 0:
Anas Nashifd9882382019-12-12 09:58:28 -05001815 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001816
1817 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001818 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1819
1820 if out:
1821 log_msg = out.decode(sys.getdefaultencoding())
1822 with open(os.path.join(self.build_dir, self.log), "a") as log:
1823 log.write(log_msg)
1824
1825 else:
1826 return None
1827 else:
1828 # A real error occurred, raise an exception
1829 if out:
1830 log_msg = out.decode(sys.getdefaultencoding())
1831 with open(os.path.join(self.build_dir, self.log), "a") as log:
1832 log.write(log_msg)
1833
1834 overflow_flash = "region `FLASH' overflowed by"
1835 overflow_ram = "region `RAM' overflowed by"
1836
1837 if log_msg:
1838 if log_msg.find(overflow_flash) > 0 or log_msg.find(overflow_ram) > 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05001839 logger.debug("RAM/ROM Overflow")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001840 self.instance.status = "skipped"
1841 self.instance.reason = "overflow"
1842 else:
1843 self.instance.status = "failed"
1844 self.instance.reason = "Build failure"
1845
1846 results = {
Anas Nashifd9882382019-12-12 09:58:28 -05001847 "returncode": p.returncode,
1848 "instance": self.instance,
1849 }
Anas Nashif83fc06a2019-06-22 11:04:10 -04001850
1851 return results
1852
1853 def run_cmake(self, args=[]):
1854
Anas Nashif11ee5252019-12-04 12:59:10 -05001855 ldflags = "-Wl,--fatal-warnings"
Anas Nashifd9882382019-12-12 09:58:28 -05001856 logger.debug("Running cmake on %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001857
Anas Nashif11ee5252019-12-04 12:59:10 -05001858 # fixme: add additional cflags based on options
Anas Nashifa5984ab2019-10-22 07:36:24 -07001859 cmake_args = [
Anas Nashifd9882382019-12-12 09:58:28 -05001860 '-B{}'.format(self.build_dir),
1861 '-S{}'.format(self.source_dir),
1862 '-DEXTRA_CFLAGS="-Werror ',
1863 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1864 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1865 '-G{}'.format(get_generator()[1])
1866 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001867
Anas Nashifd91f9932019-11-30 10:15:23 -05001868 if options.cmake_only:
1869 cmake_args.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=1")
1870
Anas Nashif83fc06a2019-06-22 11:04:10 -04001871 args = ["-D{}".format(a.replace('"', '')) for a in args]
1872 cmake_args.extend(args)
1873
1874 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1875 cmake_args.extend(cmake_opts)
1876
1877 cmake = shutil.which('cmake')
1878 cmd = [cmake] + cmake_args
1879 kwargs = dict()
1880
1881 if self.capture_output:
1882 kwargs['stdout'] = subprocess.PIPE
1883 # CMake sends the output of message() to stderr unless it's STATUS
1884 kwargs['stderr'] = subprocess.STDOUT
1885
1886 if self.cwd:
1887 kwargs['cwd'] = self.cwd
1888
1889 p = subprocess.Popen(cmd, **kwargs)
1890 out, _ = p.communicate()
1891
1892 if p.returncode == 0:
1893 filter_results = self.parse_generated()
Anas Nashifd9882382019-12-12 09:58:28 -05001894 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001895
1896 results = {'msg': msg, 'filter': filter_results}
1897
1898 else:
1899 self.instance.status = "failed"
1900 self.instance.reason = "Cmake build failure"
1901 results = {"returncode": p.returncode}
1902
Anas Nashif83fc06a2019-06-22 11:04:10 -04001903 if out:
1904 with open(os.path.join(self.build_dir, self.log), "a") as log:
1905 log_msg = out.decode(sys.getdefaultencoding())
1906 log.write(log_msg)
1907
1908 return results
1909
1910
1911class FilterBuilder(CMake):
1912
1913 def __init__(self, testcase, platform, source_dir, build_dir):
1914 super().__init__(testcase, platform, source_dir, build_dir)
1915
1916 self.log = "config-sanitycheck.log"
1917
1918 def parse_generated(self):
1919
1920 if self.platform.name == "unit_testing":
1921 return {}
1922
Anas Nashif83fc06a2019-06-22 11:04:10 -04001923 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001924 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1925
1926 with open(defconfig_path, "r") as fp:
1927 defconfig = {}
1928 for line in fp.readlines():
1929 m = self.config_re.match(line)
1930 if not m:
1931 if line.strip() and not line.startswith("#"):
1932 sys.stderr.write("Unrecognized line %s\n" % line)
1933 continue
1934 defconfig[m.group(1)] = m.group(2).strip()
1935
1936 self.defconfig = defconfig
1937
1938 cmake_conf = {}
1939 try:
1940 cache = CMakeCache.from_file(cmake_cache_path)
1941 except FileNotFoundError:
1942 cache = {}
1943
1944 for k in iter(cache):
1945 cmake_conf[k.name] = k.value
1946
1947 self.cmake_cache = cmake_conf
1948
Anas Nashif83fc06a2019-06-22 11:04:10 -04001949 filter_data = {
1950 "ARCH": self.platform.arch,
1951 "PLATFORM": self.platform.name
1952 }
1953 filter_data.update(os.environ)
1954 filter_data.update(self.defconfig)
1955 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001956
Anas Nashif556f3cb2019-11-05 15:36:15 -08001957 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001958 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001959 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001960 if os.path.exists(dts_path):
1961 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1962 else:
1963 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001964 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1965
Anas Nashif83fc06a2019-06-22 11:04:10 -04001966 except (ValueError, SyntaxError) as se:
1967 sys.stderr.write(
1968 "Failed processing %s\n" % self.testcase.yamlfile)
1969 raise se
1970
1971 if not res:
1972 return {os.path.join(self.platform.name, self.testcase.name): True}
1973 else:
1974 return {os.path.join(self.platform.name, self.testcase.name): False}
1975 else:
1976 self.platform.filter_data = filter_data
1977 return filter_data
1978
1979
1980class ProjectBuilder(FilterBuilder):
1981
Anas Nashif56656842019-12-10 12:26:00 -05001982 def __init__(self, suite, instance, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001983 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1984
1985 self.log = "build.log"
1986 self.instance = instance
1987 self.suite = suite
1988
Anas Nashif56656842019-12-10 12:26:00 -05001989 self.lsan = kwargs.get('lsan', False)
1990 self.asan = kwargs.get('asan', False)
1991 self.valgrind = kwargs.get('valgrind', False)
1992 self.extra_args = kwargs.get('extra_args', [])
1993 self.device_testing = kwargs.get('device_testing', False)
1994 self.cmake_only = kwargs.get('cmake_only', False)
1995 self.coverage = kwargs.get('coverage', False)
Anas Nashife9eb0092019-12-10 16:31:22 -05001996 self.inline_logs = kwargs.get('inline_logs', False)
1997
Anas Nashifd9882382019-12-12 09:58:28 -05001998 @staticmethod
1999 def log_info(filename, inline_logs):
Andrew Boiebd137102020-01-02 18:56:17 -08002000 filename = os.path.abspath(os.path.realpath(filename))
Anas Nashife9eb0092019-12-10 16:31:22 -05002001 if inline_logs:
2002 logger.info("{:-^100}".format(filename))
2003
2004 try:
2005 with open(filename) as fp:
2006 data = fp.read()
2007 except Exception as e:
2008 data = "Unable to read log data (%s)\n" % (str(e))
2009
2010 logger.error(data)
2011
2012 logger.info("{:-^100}".format(filename))
2013 else:
Anas Nashif97445682019-12-16 09:36:40 -05002014 logger.error("see: " + Fore.YELLOW + filename + Fore.RESET)
Anas Nashife9eb0092019-12-10 16:31:22 -05002015
2016 def log_info_file(self, inline_logs):
2017 build_dir = self.instance.build_dir
2018 h_log = "{}/handler.log".format(build_dir)
2019 b_log = "{}/build.log".format(build_dir)
2020 v_log = "{}/valgrind.log".format(build_dir)
2021 d_log = "{}/device.log".format(build_dir)
2022
2023 if os.path.exists(v_log) and "Valgrind" in self.instance.reason:
2024 self.log_info("{}".format(v_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05002025 elif os.path.exists(h_log) and os.path.getsize(h_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05002026 self.log_info("{}".format(h_log), inline_logs)
Anas Nashif52b66c62019-12-20 13:11:44 -05002027 elif os.path.exists(d_log) and os.path.getsize(d_log) > 0:
2028 self.log_info("{}".format(d_log), inline_logs)
Anas Nashife9eb0092019-12-10 16:31:22 -05002029 else:
2030 self.log_info("{}".format(b_log), inline_logs)
Anas Nashif56656842019-12-10 12:26:00 -05002031
Anas Nashif83fc06a2019-06-22 11:04:10 -04002032 def setup_handler(self):
2033
2034 instance = self.instance
2035 args = []
2036
2037 # FIXME: Needs simplification
2038 if instance.platform.simulation == "qemu":
2039 instance.handler = QEMUHandler(instance, "qemu")
2040 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
2041 instance.handler.call_make_run = True
2042 elif instance.testcase.type == "unit":
2043 instance.handler = BinaryHandler(instance, "unit")
2044 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
2045 elif instance.platform.type == "native":
Anas Nashif6c0e1702019-12-05 15:24:52 -05002046 handler = BinaryHandler(instance, "native")
2047
Anas Nashif56656842019-12-10 12:26:00 -05002048 handler.asan = self.asan
2049 handler.valgrind = self.valgrind
2050 handler.lsan = self.lsan
2051 handler.coverage = self.coverage
Anas Nashif6c0e1702019-12-05 15:24:52 -05002052
2053 handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
2054 instance.handler = handler
Anas Nashif83fc06a2019-06-22 11:04:10 -04002055 elif instance.platform.simulation == "nsim":
2056 if find_executable("nsimdrv"):
2057 instance.handler = BinaryHandler(instance, "nsim")
2058 instance.handler.call_make_run = True
2059 elif instance.platform.simulation == "renode":
2060 if find_executable("renode"):
2061 instance.handler = BinaryHandler(instance, "renode")
2062 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
2063 instance.handler.call_make_run = True
Anas Nashif56656842019-12-10 12:26:00 -05002064 elif self.device_testing:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002065 instance.handler = DeviceHandler(instance, "device")
2066
2067 if instance.handler:
2068 instance.handler.args = args
2069
2070 def process(self, message):
2071 op = message.get('op')
2072
2073 if not self.instance.handler:
2074 self.setup_handler()
2075
2076 # The build process, call cmake and build with configured generator
2077 if op == "cmake":
2078 results = self.cmake()
2079 if self.instance.status == "failed":
2080 pipeline.put({"op": "report", "test": self.instance})
Anas Nashif56656842019-12-10 12:26:00 -05002081 elif self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002082 pipeline.put({"op": "report", "test": self.instance})
2083 else:
2084 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
Anas Nashif7a361b82019-12-06 11:37:40 -05002085 logger.debug("filtering %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002086 self.instance.status = "skipped"
2087 self.instance.reason = "filter"
2088 pipeline.put({"op": "report", "test": self.instance})
2089 else:
2090 pipeline.put({"op": "build", "test": self.instance})
2091
Anas Nashif83fc06a2019-06-22 11:04:10 -04002092 elif op == "build":
Anas Nashifd9882382019-12-12 09:58:28 -05002093 logger.debug("build test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002094 results = self.build()
2095
2096 if results.get('returncode', 1) > 0:
2097 pipeline.put({"op": "report", "test": self.instance})
2098 else:
2099 if self.instance.run:
2100 pipeline.put({"op": "run", "test": self.instance})
2101 else:
2102 pipeline.put({"op": "report", "test": self.instance})
2103 # Run the generated binary using one of the supported handlers
2104 elif op == "run":
Anas Nashifd9882382019-12-12 09:58:28 -05002105 logger.debug("run test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002106 self.run()
2107 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002108 pipeline.put({
2109 "op": "report",
2110 "test": self.instance,
2111 "state": "executed",
2112 "status": self.instance.status,
Stephanos Ioannidis93889002019-11-11 20:31:03 +09002113 "reason": self.instance.reason}
Anas Nashifd9882382019-12-12 09:58:28 -05002114 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002115
2116 # Report results and output progress to screen
2117 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002118 with report_lock:
2119 self.report_out()
2120
Anas Nashif83fc06a2019-06-22 11:04:10 -04002121 def report_out(self):
2122 total_tests_width = len(str(self.suite.total_tests))
2123 self.suite.total_done += 1
2124 instance = self.instance
2125
2126 if instance.status in ["failed", "timeout"]:
2127 self.suite.total_failed += 1
Anas Nashif97445682019-12-16 09:36:40 -05002128 if VERBOSE:
2129 status = Fore.RED + "FAILED " + Fore.RESET + instance.reason
Anas Nashif83fc06a2019-06-22 11:04:10 -04002130 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002131 print("")
2132 logger.error(
2133 "{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002134 instance.platform.name,
2135 instance.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05002136 Fore.RED,
2137 Fore.RESET,
Anas Nashif7a361b82019-12-06 11:37:40 -05002138 instance.reason))
Anas Nashifc1ea4522019-10-11 07:32:45 -07002139 if not VERBOSE:
Anas Nashife9eb0092019-12-10 16:31:22 -05002140 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002141 elif instance.status == "skipped":
2142 self.suite.total_skipped += 1
Anas Nashif97445682019-12-16 09:36:40 -05002143 status = Fore.YELLOW + "SKIPPED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002144 else:
Anas Nashif97445682019-12-16 09:36:40 -05002145 status = Fore.GREEN + "PASSED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002146
Anas Nashif97445682019-12-16 09:36:40 -05002147 if VERBOSE:
Anas Nashif56656842019-12-10 12:26:00 -05002148 if self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002149 more_info = "cmake"
2150 elif instance.status == "skipped":
2151 more_info = instance.reason
2152 else:
2153 if instance.handler and instance.run:
2154 more_info = instance.handler.type_str
2155 htime = instance.handler.duration
2156 if htime:
2157 more_info += " {:.3f}s".format(htime)
2158 else:
2159 more_info = "build"
2160
Anas Nashif7a361b82019-12-06 11:37:40 -05002161 logger.info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002162 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2163 instance.testcase.name, status, more_info))
2164
2165 if instance.status in ["failed", "timeout"]:
Anas Nashifd9882382019-12-12 09:58:28 -05002166 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002167 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002168 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 -05002169 Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002170 self.suite.total_done,
2171 self.suite.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002172 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002173 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
Anas Nashif97445682019-12-16 09:36:40 -05002174 Fore.YELLOW if self.suite.total_skipped > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002175 self.suite.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002176 Fore.RESET,
2177 Fore.RED if self.suite.total_failed > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002178 self.suite.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002179 Fore.RESET
Anas Nashifd9882382019-12-12 09:58:28 -05002180 )
2181 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002182 sys.stdout.flush()
2183
2184 def cmake(self):
2185
2186 instance = self.instance
2187 args = self.testcase.extra_args[:]
Anas Nashif56656842019-12-10 12:26:00 -05002188 args += self.extra_args
Anas Nashif83fc06a2019-06-22 11:04:10 -04002189
2190 if instance.handler:
2191 args += instance.handler.args
2192
2193 # merge overlay files into one variable
2194 overlays = ""
2195 idx = 0
2196 for arg in args:
2197 match = re.search('OVERLAY_CONFIG="(.*)"', arg)
2198 if match:
2199 overlays += match.group(1)
2200 del args[idx]
2201 idx += 1
2202
Anas Nashif56656842019-12-10 12:26:00 -05002203 if (self.testcase.extra_configs or self.coverage or
2204 self.asan):
Anas Nashifd9882382019-12-12 09:58:28 -05002205 args.append("OVERLAY_CONFIG=\"%s %s\"" % (overlays,
2206 os.path.join(instance.build_dir,
2207 "sanitycheck", "testcase_extra.conf")))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002208
2209 results = self.run_cmake(args)
2210 return results
2211
2212 def build(self):
2213 results = self.run_build(['--build', self.build_dir])
2214 return results
2215
2216 def run(self):
2217
2218 instance = self.instance
2219
2220 if instance.handler.type_str == "device":
2221 instance.handler.suite = self.suite
2222
2223 instance.handler.handle()
2224
Anas Nashif83fc06a2019-06-22 11:04:10 -04002225 sys.stdout.flush()
2226
2227
2228pipeline = queue.LifoQueue()
2229
Anas Nashif11ee5252019-12-04 12:59:10 -05002230
Anas Nashif83fc06a2019-06-22 11:04:10 -04002231class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2232 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2233 calls to submit() once the limit given as "bound" work items are queued for
2234 execution.
2235 :param bound: Integer - the maximum number of items in the work queue
2236 :param max_workers: Integer - the size of the thread pool
2237 """
Anas Nashifd9882382019-12-12 09:58:28 -05002238
Anas Nashif83fc06a2019-06-22 11:04:10 -04002239 def __init__(self, bound, max_workers, **kwargs):
2240 super().__init__(max_workers)
Anas Nashif11ee5252019-12-04 12:59:10 -05002241 # self.executor = ThreadPoolExecutor(max_workers=max_workers)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002242 self.semaphore = BoundedSemaphore(bound + max_workers)
2243
2244 def submit(self, fn, *args, **kwargs):
2245 self.semaphore.acquire()
2246 try:
2247 future = super().submit(fn, *args, **kwargs)
Anas Nashif11ee5252019-12-04 12:59:10 -05002248 except Exception:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002249 self.semaphore.release()
2250 raise
2251 else:
2252 future.add_done_callback(lambda x: self.semaphore.release())
2253 return future
2254
Andrew Boie6acbe632015-07-17 12:03:52 -07002255
2256class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002257 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002258 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002259
Anas Nashif83fc06a2019-06-22 11:04:10 -04002260 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002261 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002262 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002263
Anas Nashif37f9dc52018-02-23 08:53:46 -06002264 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002265
2266 self.roots = testcase_roots
2267 if not isinstance(board_root_list, list):
Anas Nashif11ee5252019-12-04 12:59:10 -05002268 self.board_roots = [board_root_list]
Anas Nashif83fc06a2019-06-22 11:04:10 -04002269 else:
2270 self.board_roots = board_root_list
2271
Anas Nashif56656842019-12-10 12:26:00 -05002272 # Testsuite Options
2273 self.coverage_platform = []
2274 self.build_only = False
2275 self.cmake_only = False
2276 self.enable_slow = False
2277 self.device_testing = False
2278 self.fixture = []
2279 self.enable_coverage = False
2280 self.enable_lsan = False
2281 self.enable_asan = False
2282 self.enable_valgrind = False
2283 self.extra_args = []
Anas Nashife9eb0092019-12-10 16:31:22 -05002284 self.inline_logs = False
Anas Nashifc5ee3952019-12-10 16:38:45 -05002285 self.enable_sizes_report = False
Anas Nashif56656842019-12-10 12:26:00 -05002286
Andrew Boie6acbe632015-07-17 12:03:52 -07002287 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002288 self.testcases = {}
2289 self.platforms = []
Anas Nashif5f908822019-11-25 08:19:25 -05002290 self.selected_platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002291 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002292 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002293 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002294 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002295 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002296
Anas Nashif11ee5252019-12-04 12:59:10 -05002297 self.total_tests = 0 # number of test instances
2298 self.total_cases = 0 # number of test cases
2299 self.total_done = 0 # tests completed
Anas Nashif83fc06a2019-06-22 11:04:10 -04002300 self.total_failed = 0
2301 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002302
Anas Nashif83fc06a2019-06-22 11:04:10 -04002303 self.total_platforms = 0
2304 self.start_time = 0
2305 self.duration = 0
2306 self.warnings = 0
2307 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002308
Anas Nashif83fc06a2019-06-22 11:04:10 -04002309 # hardcoded for now
2310 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002311
Anas Nashif56656842019-12-10 12:26:00 -05002312 def config(self):
2313 logger.info("coverage platform: {}".format(self.coverage_platform))
2314
Anas Nashiff16ed8e2019-12-09 16:22:27 -05002315 # Debug Functions
2316 @staticmethod
2317 def info(what):
2318 sys.stdout.write(what + "\n")
2319 sys.stdout.flush()
2320
Anas Nashif83fc06a2019-06-22 11:04:10 -04002321 def update(self):
2322 self.total_tests = len(self.instances)
2323 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002324
Andrew Boie6acbe632015-07-17 12:03:52 -07002325 def compare_metrics(self, filename):
2326 # name, datatype, lower results better
2327 interesting_metrics = [("ram_size", int, True),
2328 ("rom_size", int, True)]
2329
Andrew Boie6acbe632015-07-17 12:03:52 -07002330 if not os.path.exists(filename):
Anas Nashif7a361b82019-12-06 11:37:40 -05002331 logger.info("Cannot compare metrics, %s not found" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -07002332 return []
2333
2334 results = []
2335 saved_metrics = {}
2336 with open(filename) as fp:
2337 cr = csv.DictReader(fp)
2338 for row in cr:
2339 d = {}
2340 for m, _, _ in interesting_metrics:
2341 d[m] = row[m]
2342 saved_metrics[(row["test"], row["platform"])] = d
2343
Anas Nashif83fc06a2019-06-22 11:04:10 -04002344 for instance in self.instances.values():
2345 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002346 if mkey not in saved_metrics:
2347 continue
2348 sm = saved_metrics[mkey]
2349 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002350 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002351 continue
2352 if sm[metric] == "":
2353 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002354 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002355 if delta == 0:
2356 continue
Anas Nashifd9882382019-12-12 09:58:28 -05002357 results.append((instance, metric, instance.metrics.get(metric, 0), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002358 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002359 return results
2360
Anas Nashif83fc06a2019-06-22 11:04:10 -04002361 def misc_reports(self, report, show_footprint, all_deltas,
2362 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002363
Anas Nashif83fc06a2019-06-22 11:04:10 -04002364 if not report:
2365 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002366
Anas Nashif83fc06a2019-06-22 11:04:10 -04002367 deltas = self.compare_metrics(report)
2368 warnings = 0
2369 if deltas and show_footprint:
2370 for i, metric, value, delta, lower_better in deltas:
2371 if not all_deltas and ((delta < 0 and lower_better) or
Anas Nashifd9882382019-12-12 09:58:28 -05002372 (delta > 0 and not lower_better)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002373 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002374
Anas Nashif83fc06a2019-06-22 11:04:10 -04002375 percentage = (float(delta) / float(value - delta))
2376 if not all_deltas and (percentage <
Anas Nashifd9882382019-12-12 09:58:28 -05002377 (footprint_threshold / 100.0)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002378 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002379
Anas Nashifd9882382019-12-12 09:58:28 -05002380 logger.info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Anas Nashif97445682019-12-16 09:36:40 -05002381 i.platform.name, i.testcase.name, Fore.YELLOW,
2382 "INFO" if all_deltas else "WARNING", Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002383 metric, delta, value, percentage))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002384 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002385
Anas Nashif83fc06a2019-06-22 11:04:10 -04002386 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05002387 logger.warning("Deltas based on metrics from last %s" %
Anas Nashifd9882382019-12-12 09:58:28 -05002388 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002389
Anas Nashif83fc06a2019-06-22 11:04:10 -04002390 def summary(self, unrecognized_sections):
2391 failed = 0
2392 for instance in self.instances.values():
2393 if instance.status == "failed":
2394 failed += 1
2395 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
Anas Nashif7a361b82019-12-06 11:37:40 -05002396 logger.error("%sFAILED%s: %s has unrecognized binary sections: %s" %
Anas Nashif97445682019-12-16 09:36:40 -05002397 (Fore.RED, Fore.RESET, instance.name,
Anas Nashifd9882382019-12-12 09:58:28 -05002398 str(instance.metrics.get("unrecognized", []))))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002399 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002400
Anas Nashif83fc06a2019-06-22 11:04:10 -04002401 if self.total_tests and self.total_tests != self.total_skipped:
Anas Nashifd9882382019-12-12 09:58:28 -05002402 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped) / float(
2403 self.total_tests - self.total_skipped))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002404 else:
2405 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002406
Anas Nashifd9882382019-12-12 09:58:28 -05002407 logger.info(
2408 "{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
Anas Nashif97445682019-12-16 09:36:40 -05002409 Fore.RED if failed else Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002410 self.total_tests - self.total_failed - self.total_skipped,
2411 self.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002412 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002413 pass_rate,
Anas Nashif97445682019-12-16 09:36:40 -05002414 Fore.RED if self.total_failed else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002415 self.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002416 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002417 self.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002418 Fore.YELLOW if self.warnings else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002419 self.warnings,
Anas Nashif97445682019-12-16 09:36:40 -05002420 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002421 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002422
Anas Nashif83fc06a2019-06-22 11:04:10 -04002423 self.total_platforms = len(self.platforms)
2424 if self.platforms:
Anas Nashif7a361b82019-12-06 11:37:40 -05002425 logger.info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002426 self.total_cases,
Anas Nashif5f908822019-11-25 08:19:25 -05002427 len(self.selected_platforms),
Anas Nashif83fc06a2019-06-22 11:04:10 -04002428 self.total_platforms,
Anas Nashif5f908822019-11-25 08:19:25 -05002429 (100 * len(self.selected_platforms) / len(self.platforms))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002430 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002431
Anas Nashif56656842019-12-10 12:26:00 -05002432 def save_reports(self, name, report_dir, no_update, release, only_failed):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002433 if not self.instances:
2434 return
Anas Nashif61e21632018-04-08 13:30:16 -05002435
Anas Nashif56656842019-12-10 12:26:00 -05002436 if name:
2437 report_name = name
2438 else:
2439 report_name = "sanitycheck"
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002440
Anas Nashif56656842019-12-10 12:26:00 -05002441 if report_dir:
2442 os.makedirs(report_dir, exist_ok=True)
2443 filename = os.path.join(report_dir, report_name)
2444 outdir = report_dir
Anas Nashif83fc06a2019-06-22 11:04:10 -04002445 else:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002446 filename = os.path.join(self.outdir, report_name)
2447 outdir = self.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002448
Anas Nashif56656842019-12-10 12:26:00 -05002449 if not no_update:
2450 self.xunit_report(filename + ".xml", only_failed)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002451 self.csv_report(filename + ".csv")
2452 self.target_report(outdir)
2453 if self.discards:
2454 self.discard_report(filename + "_discard.csv")
2455
Anas Nashif56656842019-12-10 12:26:00 -05002456 if release:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002457 self.csv_report(RELEASE_DATA)
2458
Anas Nashif83fc06a2019-06-22 11:04:10 -04002459 def add_configurations(self):
2460
2461 for board_root in self.board_roots:
2462 board_root = os.path.abspath(board_root)
2463
Anas Nashif7a361b82019-12-06 11:37:40 -05002464 logger.debug("Reading platform configuration files under %s..." %
Anas Nashifd9882382019-12-12 09:58:28 -05002465 board_root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002466
2467 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
Anas Nashif7a361b82019-12-06 11:37:40 -05002468 logger.debug("Found plaform configuration " + file)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002469 try:
2470 platform = Platform()
2471 platform.load(file)
2472 if platform.sanitycheck:
2473 self.platforms.append(platform)
2474 if platform.default:
2475 self.default_platforms.append(platform.name)
2476
2477 except RuntimeError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002478 logger.error("E: %s: can't load: %s" % (file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002479 self.load_errors += 1
2480
Anas Nashifeabaa7f2019-11-18 07:49:17 -08002481 def get_all_tests(self):
2482 tests = []
2483 for _, tc in self.testcases.items():
2484 for case in tc.cases:
2485 tests.append(case)
2486
2487 return tests
2488
Anas Nashif83fc06a2019-06-22 11:04:10 -04002489 @staticmethod
2490 def get_toolchain():
2491 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2492 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2493
2494 if toolchain == "gccarmemb":
2495 # Remove this translation when gccarmemb is no longer supported.
2496 toolchain = "gnuarmemb"
2497
Anas Nashifb4bdd662018-08-15 17:12:28 -05002498 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002499 if not toolchain:
2500 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002501 except Exception as e:
2502 print(str(e))
2503 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002504
Anas Nashif83fc06a2019-06-22 11:04:10 -04002505 return toolchain
2506
Anas Nashif83fc06a2019-06-22 11:04:10 -04002507 def add_testcases(self):
2508 for root in self.roots:
2509 root = os.path.abspath(root)
2510
Anas Nashifd9882382019-12-12 09:58:28 -05002511 logger.debug("Reading test case configuration files under %s..." % root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002512
2513 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
Anas Nashif7a361b82019-12-06 11:37:40 -05002514 logger.debug("scanning %s" % dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002515 if 'sample.yaml' in filenames:
2516 filename = 'sample.yaml'
2517 elif 'testcase.yaml' in filenames:
2518 filename = 'testcase.yaml'
2519 else:
2520 continue
2521
Anas Nashif7a361b82019-12-06 11:37:40 -05002522 logger.debug("Found possible test case in " + dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002523
2524 dirnames[:] = []
2525 tc_path = os.path.join(dirpath, filename)
2526 self.add_testcase(tc_path, root)
2527
2528 def add_testcase(self, tc_data_file, root):
2529 try:
2530 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2531 parsed_data.load()
2532
2533 tc_path = os.path.dirname(tc_data_file)
2534 workdir = os.path.relpath(tc_path, root)
2535
2536 for name in parsed_data.tests.keys():
2537 tc = TestCase()
2538 tc.name = tc.get_unique(root, workdir, name)
2539
2540 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2541
2542 tc.source_dir = tc_path
2543 tc.yamlfile = tc_data_file
2544
2545 tc.id = name
2546 tc.type = tc_dict["type"]
2547 tc.tags = tc_dict["tags"]
2548 tc.extra_args = tc_dict["extra_args"]
2549 tc.extra_configs = tc_dict["extra_configs"]
2550 tc.arch_whitelist = tc_dict["arch_whitelist"]
2551 tc.arch_exclude = tc_dict["arch_exclude"]
2552 tc.skip = tc_dict["skip"]
2553 tc.platform_exclude = tc_dict["platform_exclude"]
2554 tc.platform_whitelist = tc_dict["platform_whitelist"]
2555 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2556 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2557 tc.tc_filter = tc_dict["filter"]
2558 tc.timeout = tc_dict["timeout"]
2559 tc.harness = tc_dict["harness"]
2560 tc.harness_config = tc_dict["harness_config"]
2561 tc.build_only = tc_dict["build_only"]
2562 tc.build_on_all = tc_dict["build_on_all"]
2563 tc.slow = tc_dict["slow"]
2564 tc.min_ram = tc_dict["min_ram"]
2565 tc.depends_on = tc_dict["depends_on"]
2566 tc.min_flash = tc_dict["min_flash"]
2567 tc.extra_sections = tc_dict["extra_sections"]
2568
2569 tc.parse_subcases(tc_path)
2570
2571 if tc.name:
2572 self.testcases[tc.name] = tc
2573
2574 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002575 logger.error("%s: can't load (skipping): %s" % (tc_data_file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002576 self.load_errors += 1
2577 return False
2578
2579 return True
2580
Anas Nashif83fc06a2019-06-22 11:04:10 -04002581 def get_platform(self, name):
2582 selected_platform = None
2583 for platform in self.platforms:
2584 if platform.name == name:
2585 selected_platform = platform
2586 break
2587 return selected_platform
2588
2589 def get_last_failed(self):
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002590 last_run = os.path.join(self.outdir, "sanitycheck.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002591 try:
2592 if not os.path.exists(last_run):
Anas Nashifd9882382019-12-12 09:58:28 -05002593 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" % last_run)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002594 except Exception as e:
2595 print(str(e))
2596 sys.exit(2)
2597
2598 total_tests = 0
2599 with open(last_run, "r") as fp:
2600 cr = csv.DictReader(fp)
2601 instance_list = []
2602 for row in cr:
2603 total_tests += 1
2604 if row["passed"] == "True":
2605 continue
2606 test = row["test"]
2607 platform = self.get_platform(row["platform"])
2608 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002609 instance.check_build_or_run(
2610 self.build_only,
2611 self.enable_slow,
2612 self.device_testing,
2613 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002614 )
Anas Nashif56656842019-12-10 12:26:00 -05002615 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002616 instance_list.append(instance)
2617 self.add_instances(instance_list)
2618
2619 tests_to_run = len(self.instances)
Anas Nashifd9882382019-12-12 09:58:28 -05002620 logger.info("%d tests passed already, retrying %d tests" % (total_tests - tests_to_run, tests_to_run))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002621
2622 def load_from_file(self, file):
2623 try:
2624 if not os.path.exists(file):
2625 raise SanityRuntimeError(
2626 "Couldn't find input file with list of tests.")
2627 except Exception as e:
2628 print(str(e))
2629 sys.exit(2)
2630
2631 with open(file, "r") as fp:
2632 cr = csv.DictReader(fp)
2633 instance_list = []
2634 for row in cr:
2635 if row["arch"] == "arch":
2636 continue
2637 test = row["test"]
2638 platform = self.get_platform(row["platform"])
2639 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002640 instance.check_build_or_run(
2641 self.build_only,
2642 self.enable_slow,
2643 self.device_testing,
2644 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002645 )
Anas Nashif56656842019-12-10 12:26:00 -05002646 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002647 instance_list.append(instance)
2648 self.add_instances(instance_list)
2649
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002650 def apply_filters(self, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002651
2652 toolchain = self.get_toolchain()
2653
2654 discards = {}
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002655 platform_filter = kwargs.get('platform')
2656 testcase_filter = kwargs.get('run_individual_tests')
2657 arch_filter = kwargs.get('arch')
2658 tag_filter = kwargs.get('tag')
2659 exclude_tag = kwargs.get('exclude_tag')
2660 all_filter = kwargs.get('all')
2661 device_testing_filter = kwargs.get('device_testing')
2662 force_toolchain = kwargs.get('force_toolchain')
Anas Nashif83fc06a2019-06-22 11:04:10 -04002663
Anas Nashif7a361b82019-12-06 11:37:40 -05002664 logger.debug("platform filter: " + str(platform_filter))
2665 logger.debug(" arch_filter: " + str(arch_filter))
2666 logger.debug(" tag_filter: " + str(tag_filter))
2667 logger.debug(" exclude_tag: " + str(exclude_tag))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002668
2669 default_platforms = False
2670
2671 if platform_filter:
2672 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2673 else:
2674 platforms = self.platforms
2675
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002676 if all_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002677 logger.info("Selecting all possible platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002678 # When --all used, any --platform arguments ignored
2679 platform_filter = []
2680 elif not platform_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002681 logger.info("Selecting default platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002682 default_platforms = True
2683
Anas Nashif7a361b82019-12-06 11:37:40 -05002684 logger.info("Building initial testcase list...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002685
2686 for tc_name, tc in self.testcases.items():
2687 # list of instances per testcase, aka configurations.
2688 instance_list = []
2689 for plat in platforms:
2690 instance = TestInstance(tc, plat, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002691 instance.check_build_or_run(
2692 self.build_only,
2693 self.enable_slow,
2694 self.device_testing,
2695 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002696 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002697
2698 if (plat.arch == "unit") != (tc.type == "unit"):
2699 # Discard silently
2700 continue
2701
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002702 if device_testing_filter and instance.build_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002703 discards[instance] = "Not runnable on device"
2704 continue
2705
2706 if tc.skip:
2707 discards[instance] = "Skip filter"
2708 continue
2709
2710 if tc.build_on_all and not platform_filter:
2711 platform_filter = []
2712
2713 if tag_filter and not tc.tags.intersection(tag_filter):
2714 discards[instance] = "Command line testcase tag filter"
2715 continue
2716
2717 if exclude_tag and tc.tags.intersection(exclude_tag):
2718 discards[instance] = "Command line testcase exclude filter"
2719 continue
2720
2721 if testcase_filter and tc_name not in testcase_filter:
2722 discards[instance] = "Testcase name filter"
2723 continue
2724
2725 if arch_filter and plat.arch not in arch_filter:
2726 discards[instance] = "Command line testcase arch filter"
2727 continue
2728
2729 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2730 discards[instance] = "Not in test case arch whitelist"
2731 continue
2732
2733 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2734 discards[instance] = "In test case arch exclude"
2735 continue
2736
2737 if tc.platform_exclude and plat.name in tc.platform_exclude:
2738 discards[instance] = "In test case platform exclude"
2739 continue
2740
2741 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2742 discards[instance] = "In test case toolchain exclude"
2743 continue
2744
2745 if platform_filter and plat.name not in platform_filter:
2746 discards[instance] = "Command line platform filter"
2747 continue
2748
2749 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2750 discards[instance] = "Not in testcase platform whitelist"
2751 continue
2752
2753 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2754 discards[instance] = "Not in testcase toolchain whitelist"
2755 continue
2756
2757 if not plat.env_satisfied:
2758 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2759 continue
2760
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002761 if not force_toolchain \
Anas Nashifd9882382019-12-12 09:58:28 -05002762 and toolchain and (toolchain not in plat.supported_toolchains) \
2763 and tc.type != 'unit':
Anas Nashif83fc06a2019-06-22 11:04:10 -04002764 discards[instance] = "Not supported by the toolchain"
2765 continue
2766
2767 if plat.ram < tc.min_ram:
2768 discards[instance] = "Not enough RAM"
2769 continue
2770
2771 if tc.depends_on:
2772 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2773 if dep_intersection != set(tc.depends_on):
2774 discards[instance] = "No hardware support"
2775 continue
2776
2777 if plat.flash < tc.min_flash:
2778 discards[instance] = "Not enough FLASH"
2779 continue
2780
2781 if set(plat.ignore_tags) & tc.tags:
2782 discards[instance] = "Excluded tags per platform"
2783 continue
2784
2785 # if nothing stopped us until now, it means this configuration
2786 # needs to be added.
2787 instance_list.append(instance)
2788
2789 # no configurations, so jump to next testcase
2790 if not instance_list:
2791 continue
2792
2793 # if sanitycheck was launched with no platform options at all, we
2794 # take all default platforms
2795 if default_platforms and not tc.build_on_all:
2796 if tc.platform_whitelist:
2797 a = set(self.default_platforms)
2798 b = set(tc.platform_whitelist)
2799 c = a.intersection(b)
2800 if c:
Anas Nashifd9882382019-12-12 09:58:28 -05002801 aa = list(filter(lambda tc: tc.platform.name in c, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002802 self.add_instances(aa)
2803 else:
2804 self.add_instances(instance_list[:1])
2805 else:
Anas Nashifd9882382019-12-12 09:58:28 -05002806 instances = list(filter(lambda tc: tc.platform.default, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002807 self.add_instances(instances)
2808
2809 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2810 discards[instance] = "Not a default test platform"
2811
2812 else:
2813 self.add_instances(instance_list)
2814
2815 for _, case in self.instances.items():
Anas Nashif56656842019-12-10 12:26:00 -05002816 case.create_overlay(case.platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002817
2818 self.discards = discards
Anas Nashif5f908822019-11-25 08:19:25 -05002819 self.selected_platforms = set(p.platform.name for p in self.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04002820
2821 return discards
2822
2823 def add_instances(self, instance_list):
2824 for instance in instance_list:
2825 self.instances[instance.name] = instance
2826
Anas Nashif56656842019-12-10 12:26:00 -05002827 def add_tasks_to_queue(self, test_only=False):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002828 for instance in self.instances.values():
Anas Nashif56656842019-12-10 12:26:00 -05002829 if test_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002830 if instance.run:
2831 pipeline.put({"op": "run", "test": instance, "status": "built"})
2832 else:
2833 if instance.status not in ['passed', 'skipped']:
2834 instance.status = None
2835 pipeline.put({"op": "cmake", "test": instance})
2836
2837 return "DONE FEEDING"
2838
Anas Nashifc5ee3952019-12-10 16:38:45 -05002839 def execute(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002840 def calc_one_elf_size(instance):
2841 if instance.status not in ["failed", "skipped"]:
2842 if instance.platform.type != "native":
2843 size_calc = instance.calculate_sizes()
2844 instance.metrics["ram_size"] = size_calc.get_ram_size()
2845 instance.metrics["rom_size"] = size_calc.get_rom_size()
2846 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2847 else:
2848 instance.metrics["ram_size"] = 0
2849 instance.metrics["rom_size"] = 0
2850 instance.metrics["unrecognized"] = []
2851
2852 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2853
Anas Nashif7a361b82019-12-06 11:37:40 -05002854 logger.info("Adding tasks to the queue...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002855 # We can use a with statement to ensure threads are cleaned up promptly
2856 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2857
2858 # start a future for a thread which sends work in through the queue
2859 future_to_test = {
Anas Nashifd9882382019-12-12 09:58:28 -05002860 executor.submit(self.add_tasks_to_queue, self.test_only): 'FEEDER DONE'}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002861
2862 while future_to_test:
2863 # check for status of the futures which are currently working
2864 done, _ = concurrent.futures.wait(
Anas Nashifd9882382019-12-12 09:58:28 -05002865 future_to_test, timeout=0.25,
2866 return_when=concurrent.futures.FIRST_COMPLETED)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002867
2868 # if there is incoming work, start a new future
2869 while not pipeline.empty():
2870 # fetch a url from the queue
2871 message = pipeline.get()
2872 test = message['test']
2873
2874 # Start the load operation and mark the future with its URL
Anas Nashif56656842019-12-10 12:26:00 -05002875 pb = ProjectBuilder(self,
Anas Nashifd9882382019-12-12 09:58:28 -05002876 test,
2877 lsan=self.enable_lsan,
2878 asan=self.enable_asan,
2879 coverage=self.enable_coverage,
2880 extra_args=self.extra_args,
2881 device_testing=self.device_testing,
2882 cmake_only=self.cmake_only,
2883 valgrind=self.enable_valgrind,
2884 inline_logs=self.inline_logs
2885 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002886 future_to_test[executor.submit(pb.process, message)] = test.name
2887
2888 # process any completed futures
2889 for future in done:
2890 test = future_to_test[future]
2891 try:
2892 data = future.result()
2893 except Exception as exc:
Anas Nashif4f043862019-11-05 06:01:49 -08002894 sys.exit('%r generated an exception: %s' % (test, exc))
2895
Anas Nashif83fc06a2019-06-22 11:04:10 -04002896 else:
2897 if data:
Anas Nashif7a361b82019-12-06 11:37:40 -05002898 logger.debug(data)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002899
2900 # remove the now completed future
2901 del future_to_test[future]
2902
Anas Nashifc5ee3952019-12-10 16:38:45 -05002903 if self.enable_size_report and not self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002904 # Parallelize size calculation
2905 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2906 futures = [executor.submit(calc_one_elf_size, instance)
2907 for instance in self.instances.values()]
2908 concurrent.futures.wait(futures)
2909 else:
2910 for instance in self.instances.values():
2911 instance.metrics["ram_size"] = 0
2912 instance.metrics["rom_size"] = 0
2913 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2914 instance.metrics["unrecognized"] = []
2915
Anas Nashif83fc06a2019-06-22 11:04:10 -04002916 def discard_report(self, filename):
2917
2918 try:
2919 if self.discards is None:
2920 raise SanityRuntimeError("apply_filters() hasn't been run!")
2921 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002922 logger.error(str(e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002923 sys.exit(2)
2924
2925 with open(filename, "wt") as csvfile:
2926 fieldnames = ["test", "arch", "platform", "reason"]
2927 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2928 cw.writeheader()
2929 for instance, reason in sorted(self.discards.items()):
2930 rowdict = {"test": instance.testcase.name,
2931 "arch": instance.platform.arch,
2932 "platform": instance.platform.name,
2933 "reason": reason}
2934 cw.writerow(rowdict)
2935
Anas Nashif83fc06a2019-06-22 11:04:10 -04002936 def target_report(self, outdir):
2937 run = "Sanitycheck"
2938 eleTestsuite = None
2939
Anas Nashifd9882382019-12-12 09:58:28 -05002940 platforms = {inst.platform.name for _, inst in self.instances.items()}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002941 for platform in platforms:
2942 errors = 0
2943 passes = 0
2944 fails = 0
2945 duration = 0
2946 skips = 0
2947 for _, instance in self.instances.items():
2948 if instance.platform.name != platform:
2949 continue
2950
2951 handler_time = instance.metrics.get('handler_time', 0)
2952 duration += handler_time
2953 for k in instance.results.keys():
2954 if instance.results[k] == 'PASS':
2955 passes += 1
2956 elif instance.results[k] == 'BLOCK':
2957 errors += 1
2958 elif instance.results[k] == 'SKIP':
2959 skips += 1
2960 else:
2961 fails += 1
2962
2963 eleTestsuites = ET.Element('testsuites')
2964 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashifd9882382019-12-12 09:58:28 -05002965 name=run, time="%f" % duration,
2966 tests="%d" % (errors + passes + fails),
2967 failures="%d" % fails,
2968 errors="%d" % errors, skipped="%d" % skips)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002969
2970 handler_time = 0
2971
2972 # print out test results
2973 for _, instance in self.instances.items():
2974 if instance.platform.name != platform:
2975 continue
2976 handler_time = instance.metrics.get('handler_time', 0)
2977 for k in instance.results.keys():
2978 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05002979 eleTestsuite, 'testcase',
2980 classname="%s:%s" % (instance.platform.name, os.path.basename(instance.testcase.name)),
2981 name="%s" % (k), time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002982 if instance.results[k] in ['FAIL', 'BLOCK']:
2983 el = None
2984
2985 if instance.results[k] == 'FAIL':
2986 el = ET.SubElement(
2987 eleTestcase,
2988 'failure',
2989 type="failure",
2990 message="failed")
2991 elif instance.results[k] == 'BLOCK':
2992 el = ET.SubElement(
2993 eleTestcase,
2994 'error',
2995 type="failure",
2996 message="failed")
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002997 p = os.path.join(self.outdir, instance.platform.name, instance.testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002998 log_file = os.path.join(p, "handler.log")
2999
3000 if os.path.exists(log_file):
3001 with open(log_file, "rb") as f:
3002 log = f.read().decode("utf-8")
3003 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3004 el.text = filtered_string
3005
3006 elif instance.results[k] == 'SKIP':
3007 el = ET.SubElement(
3008 eleTestcase,
3009 'skipped',
3010 type="skipped",
3011 message="Skipped")
3012
Anas Nashif83fc06a2019-06-22 11:04:10 -04003013 result = ET.tostring(eleTestsuites)
3014 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
3015 f.write(result)
3016
Anas Nashif56656842019-12-10 12:26:00 -05003017 def xunit_report(self, filename, append=False):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003018 fails = 0
3019 passes = 0
3020 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04003021 skips = 0
3022 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04003023
Anas Nashif83fc06a2019-06-22 11:04:10 -04003024 for instance in self.instances.values():
3025 handler_time = instance.metrics.get('handler_time', 0)
3026 duration += handler_time
3027 if instance.status == "failed":
3028 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003029 errors += 1
3030 else:
3031 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04003032 elif instance.status == 'skipped':
3033 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04003034 else:
3035 passes += 1
3036
3037 run = "Sanitycheck"
3038 eleTestsuite = None
Anas Nashifb3311ed2017-04-13 14:44:48 -04003039
Anas Nashif83fc06a2019-06-22 11:04:10 -04003040 # When we re-run the tests, we re-use the results and update only with
3041 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04003042 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003043 tree = ET.parse(filename)
3044 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05003045 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04003046 else:
3047 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05003048 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04003049 name=run, time="%f" % duration,
3050 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05003051 failures="%d" % fails,
Anas Nashifd9882382019-12-12 09:58:28 -05003052 errors="%d" % (errors), skip="%s" % (skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003053
Anas Nashif83fc06a2019-06-22 11:04:10 -04003054 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04003055
Anas Nashif83fc06a2019-06-22 11:04:10 -04003056 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04003057 if append:
3058 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003059 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04003060 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003061 eleTestsuite.remove(tc)
3062
Anas Nashif83fc06a2019-06-22 11:04:10 -04003063 handler_time = 0
3064 if instance.status != "failed" and instance.handler:
3065 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003066
Anas Nashifd9882382019-12-12 09:58:28 -05003067
Anas Nashif3ba1d432017-12-05 15:28:44 -05003068 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05003069 eleTestsuite,
3070 'testcase',
3071 classname="%s:%s" % (instance.platform.name, instance.testcase.name),
3072 name="%s" % (instance.testcase.name),
3073 time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003074
3075 if instance.status == "failed":
Anas Nashif3ba1d432017-12-05 15:28:44 -05003076 failure = ET.SubElement(
3077 eleTestcase,
3078 'failure',
3079 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003080 message=instance.reason)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003081 p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003082 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05003083 hl = os.path.join(p, "handler.log")
3084 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04003085 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05003086 if os.path.exists(hl):
3087 log_file = hl
3088 else:
3089 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04003090
Anas Nashifc96c90a2019-02-05 07:38:32 -05003091 if os.path.exists(log_file):
3092 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05003093 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04003094 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3095 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05003096 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003097 elif instance.status == "skipped":
Anas Nashifd9882382019-12-12 09:58:28 -05003098 ET.SubElement(eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04003099
3100 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003101 with open(filename, 'wb') as report:
3102 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003103
Anas Nashif83fc06a2019-06-22 11:04:10 -04003104 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08003105 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07003106 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003107 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07003108 "rom_size"]
3109 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3110 cw.writeheader()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003111 for instance in sorted(self.instances.values()):
3112 rowdict = {"test": instance.testcase.name,
3113 "arch": instance.platform.arch,
3114 "platform": instance.platform.name,
3115 "extra_args": " ".join(instance.testcase.extra_args),
3116 "handler": instance.platform.simulation}
3117
3118 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07003119 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04003120 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07003121 else:
3122 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04003123 if instance.handler:
3124 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
3125 ram_size = instance.metrics.get("ram_size", 0)
3126 rom_size = instance.metrics.get("rom_size", 0)
3127 rowdict["ram_size"] = ram_size
3128 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003129 cw.writerow(rowdict)
3130
Anas Nashif19ca7832019-11-18 08:16:21 -08003131 def get_testcase(self, identifier):
3132 results = []
3133 for _, tc in self.testcases.items():
3134 for case in tc.cases:
3135 if case == identifier:
3136 results.append(tc)
3137 return results
3138
3139
Andrew Boie6acbe632015-07-17 12:03:52 -07003140def parse_arguments():
Anas Nashif3ba1d432017-12-05 15:28:44 -05003141 parser = argparse.ArgumentParser(
3142 description=__doc__,
3143 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003144 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003145
Marc Herbert932a33a2019-03-12 11:37:53 -07003146 case_select = parser.add_argument_group("Test case selection",
3147 """
3148Artificially long but functional example:
3149 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003150 --testcase-root tests/ztest/base \\
3151 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003152 --test tests/ztest/base/testing.ztest.verbose_0 \\
3153 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3154
3155 "kernel.fifo.poll" is one of the test section names in
3156 __/fifo_api/testcase.yaml
3157 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003158
Anas Nashif07d54c02018-07-21 19:29:08 -05003159 parser.add_argument("--force-toolchain", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003160 help="Do not filter based on toolchain, use the set "
3161 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003162 parser.add_argument(
3163 "-p", "--platform", action="append",
3164 help="Platform filter for testing. This option may be used multiple "
Anas Nashifd9882382019-12-12 09:58:28 -05003165 "times. Testcases will only be built/run on the platforms "
3166 "specified. If this option is not used, then platforms marked "
3167 "as default in the platform metadata file will be chosen "
3168 "to build and test. ")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003169 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003170 "-a", "--arch", action="append",
3171 help="Arch filter for testing. Takes precedence over --platform. "
Anas Nashifd9882382019-12-12 09:58:28 -05003172 "If unspecified, test all arches. Multiple invocations "
3173 "are treated as a logical 'or' relationship")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003174 parser.add_argument(
3175 "-t", "--tag", action="append",
3176 help="Specify tags to restrict which tests to run by tag value. "
Anas Nashifd9882382019-12-12 09:58:28 -05003177 "Default is to not do any tag filtering. Multiple invocations "
3178 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003179 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003180 help="Specify tags of tests that should not run. "
Anas Nashifd9882382019-12-12 09:58:28 -05003181 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003182 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003183 "-f",
3184 "--only-failed",
3185 action="store_true",
3186 help="Run only those tests that failed the previous sanity check "
Anas Nashifd9882382019-12-12 09:58:28 -05003187 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003188
Anas Nashif3ba1d432017-12-05 15:28:44 -05003189 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003190 "--retry-failed", type=int, default=0,
3191 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003192
Marc Herbert0c465bb2019-03-11 17:28:36 -07003193 test_xor_subtest = case_select.add_mutually_exclusive_group()
3194
3195 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003196 "-s", "--test", action="append",
3197 help="Run only the specified test cases. These are named by "
Anas Nashifd9882382019-12-12 09:58:28 -05003198 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003199
Marc Herbert0c465bb2019-03-11 17:28:36 -07003200 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003201 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003202 help="""Recursively find sub-test functions and run the entire
3203 test section where they were found, including all sibling test
3204 functions. Sub-tests are named by:
3205 section.name.in.testcase.yaml.function_name_without_test_prefix
3206 Example: kernel.fifo.poll.fifo_loop
3207 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003208
Anas Nashif3ba1d432017-12-05 15:28:44 -05003209 parser.add_argument(
3210 "-l", "--all", action="store_true",
3211 help="Build/test on all platforms. Any --platform arguments "
Anas Nashifd9882382019-12-12 09:58:28 -05003212 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003213
Anas Nashif3ba1d432017-12-05 15:28:44 -05003214 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003215 "-o", "--report-dir",
3216 help="""Output reports containing results of the test run into the
3217 specified directory.
3218 The output will be both in CSV and JUNIT format
3219 (sanitycheck.csv and sanitycheck.xml).
3220 """)
3221
Anas Nashif3ba1d432017-12-05 15:28:44 -05003222 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003223 "--report-name",
3224 help="""Create a report with a custom name.
3225 """)
3226
Anas Nashif83fc06a2019-06-22 11:04:10 -04003227 parser.add_argument("--report-excluded",
Anas Nashifd9882382019-12-12 09:58:28 -05003228 action="store_true",
3229 help="""List all tests that are never run based on current scope and
Anas Nashif83fc06a2019-06-22 11:04:10 -04003230 coverage. If you are looking for accurate results, run this with
3231 --all, but this will take a while...""")
3232
Daniel Leung7f850102016-04-08 11:07:32 -07003233 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003234 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003235
Anas Nashif3ba1d432017-12-05 15:28:44 -05003236 parser.add_argument(
3237 "-B", "--subset",
3238 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
Anas Nashifd9882382019-12-12 09:58:28 -05003239 "3/5 means run the 3rd fifth of the total. "
3240 "This option is useful when running a large number of tests on "
3241 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003242
3243 parser.add_argument(
3244 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003245 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003246
Anas Nashif3ba1d432017-12-05 15:28:44 -05003247 parser.add_argument(
3248 "-y", "--dry-run", action="store_true",
Anas Nashif12d8cce2019-11-20 03:47:27 -08003249 help="""Create the filtered list of test cases, but don't actually
3250 run them. Useful if you're just interested in the discard report
3251 generated for every run and saved in the specified output
3252 directory (sanitycheck_discard.csv).
3253 """)
Andrew Boie6acbe632015-07-17 12:03:52 -07003254
Anas Nashif75547e22018-02-24 08:32:14 -06003255 parser.add_argument("--list-tags", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003256 help="list all tags in selected tests")
Anas Nashif75547e22018-02-24 08:32:14 -06003257
Marc Herbertedf17592019-03-08 12:39:11 -08003258 case_select.add_argument("--list-tests", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003259 help="""List of all sub-test functions recursively found in
Marc Herbert932a33a2019-03-12 11:37:53 -07003260 all --testcase-root arguments. Note different sub-tests can share
3261 the same section name and come from different directories.
3262 The output is flattened and reports --sub-test names only,
3263 not their directories. For instance net.socket.getaddrinfo_ok
3264 and net.socket.fd_set belong to different directories.
3265 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003266
Anas Nashif434995c2019-12-01 13:55:11 -05003267 case_select.add_argument("--test-tree", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003268 help="""Output the testsuite in a tree form""")
Anas Nashif434995c2019-12-01 13:55:11 -05003269
Anas Nashif19ca7832019-11-18 08:16:21 -08003270 case_select.add_argument("--list-test-duplicates", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003271 help="""List tests with duplicate identifiers.
Anas Nashif19ca7832019-11-18 08:16:21 -08003272 """)
3273
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003274 parser.add_argument("--export-tests", action="store",
Anas Nashifd9882382019-12-12 09:58:28 -05003275 metavar="FILENAME",
3276 help="Export tests case meta-data to a file in CSV format.")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003277
Anas Nashif654ec5982019-04-11 08:38:21 -04003278 parser.add_argument("--timestamps",
Anas Nashifd9882382019-12-12 09:58:28 -05003279 action="store_true",
3280 help="Print all messages with time stamps")
Anas Nashif654ec5982019-04-11 08:38:21 -04003281
Anas Nashif3ba1d432017-12-05 15:28:44 -05003282 parser.add_argument(
3283 "-r", "--release", action="store_true",
3284 help="Update the benchmark database with the results of this test "
Anas Nashifd9882382019-12-12 09:58:28 -05003285 "run. Intended to be run by CI when tagging an official "
3286 "release. This database is used as a basis for comparison "
3287 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003288 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003289 help="Treat warning conditions as errors")
3290 parser.add_argument(
3291 "-v",
3292 "--verbose",
3293 action="count",
3294 default=0,
3295 help="Emit debugging information, call multiple times to increase "
Anas Nashifd9882382019-12-12 09:58:28 -05003296 "verbosity")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003297 parser.add_argument(
3298 "-i", "--inline-logs", action="store_true",
3299 help="Upon test failure, print relevant log data to stdout "
Anas Nashifd9882382019-12-12 09:58:28 -05003300 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003301 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003302 help="log also to file")
3303 parser.add_argument(
3304 "-m", "--last-metrics", action="store_true",
3305 help="Instead of comparing metrics from the last --release, "
Anas Nashifd9882382019-12-12 09:58:28 -05003306 "compare with the results of the previous sanity check "
3307 "invocation")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003308 parser.add_argument(
3309 "-u",
3310 "--no-update",
3311 action="store_true",
3312 help="do not update the results of the last run of the sanity "
Anas Nashifd9882382019-12-12 09:58:28 -05003313 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003314 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003315 "-F",
3316 "--load-tests",
3317 metavar="FILENAME",
3318 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003319 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003320
Marc Herbertedf17592019-03-08 12:39:11 -08003321 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003322 "-E",
3323 "--save-tests",
3324 metavar="FILENAME",
3325 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003326 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003327
Andy Doancbecadd2019-02-08 10:19:10 -06003328 test_or_build = parser.add_mutually_exclusive_group()
3329 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003330 "-b", "--build-only", action="store_true",
3331 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003332
Andy Doancbecadd2019-02-08 10:19:10 -06003333 test_or_build.add_argument(
3334 "--test-only", action="store_true",
3335 help="""Only run device tests with current artifacts, do not build
3336 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003337 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003338 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003339 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003340
3341 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003342 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003343 help="Number of jobs for building, defaults to number of CPU threads, "
Anas Nashifd9882382019-12-12 09:58:28 -05003344 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003345
3346 parser.add_argument(
Anas Nashifd9882382019-12-12 09:58:28 -05003347 "--show-footprint", action="store_true",
3348 help="Show footprint statistics and deltas since last release."
3349 )
Anas Nashif424a3db2018-02-20 08:37:24 -06003350 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003351 "-H", "--footprint-threshold", type=float, default=5,
3352 help="When checking test case footprint sizes, warn the user if "
Anas Nashifd9882382019-12-12 09:58:28 -05003353 "the new app size is greater then the specified percentage "
3354 "from the last release. Default is 5. 0 to warn on any "
3355 "increase on app size")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003356 parser.add_argument(
3357 "-D", "--all-deltas", action="store_true",
3358 help="Show all footprint deltas, positive or negative. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003359 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003360 parser.add_argument(
3361 "-O", "--outdir",
Anas Nashifd9882382019-12-12 09:58:28 -05003362 default=os.path.join(os.getcwd(), "sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003363 help="Output directory for logs and binaries. "
Anas Nashifd9882382019-12-12 09:58:28 -05003364 "Default is 'sanity-out' in the current directory. "
Andrew Boie114c01b2020-01-02 18:44:22 -08003365 "This directory will be cleaned unless '--no-clean' is set. "
3366 "The '--clobber-output' option controls what cleaning does.")
3367 parser.add_argument(
3368 "-c", "--clobber-output", action="store_true",
3369 help="Cleaning the output directory will simply delete it instead "
3370 "of the default policy of renaming.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003371 parser.add_argument(
3372 "-n", "--no-clean", action="store_true",
Andrew Boie114c01b2020-01-02 18:44:22 -08003373 help="Re-use the outdir before building. Will result in "
3374 "faster compilation since builds will be incremental.")
Marc Herbertedf17592019-03-08 12:39:11 -08003375 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003376 "-T", "--testcase-root", action="append", default=[],
3377 help="Base directory to recursively search for test cases. All "
Anas Nashifd9882382019-12-12 09:58:28 -05003378 "testcase.yaml files under here will be processed. May be "
3379 "called multiple times. Defaults to the 'samples/' and "
3380 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003381
Anas Nashif3ba1d432017-12-05 15:28:44 -05003382 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3383 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003384
Anas Nashif3ba1d432017-12-05 15:28:44 -05003385 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003386 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003387 help="""Directory to search for board configuration files. All .yaml
3388files in the directory will be processed. The directory should have the same
3389structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3390
Anas Nashif3ba1d432017-12-05 15:28:44 -05003391 parser.add_argument(
3392 "-z", "--size", action="append",
3393 help="Don't run sanity checks. Instead, produce a report to "
Anas Nashifd9882382019-12-12 09:58:28 -05003394 "stdout detailing RAM/ROM sizes on the specified filenames. "
3395 "All other command line arguments ignored.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003396 parser.add_argument(
3397 "-S", "--enable-slow", action="store_true",
3398 help="Execute time-consuming test cases that have been marked "
Anas Nashifd9882382019-12-12 09:58:28 -05003399 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003400 parser.add_argument(
3401 "--disable-unrecognized-section-test", action="store_true",
3402 default=False,
3403 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003404 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003405 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003406 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003407 parser.add_argument("--disable-asserts", action="store_false",
3408 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003409 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003410 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003411 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003412 parser.add_argument("--enable-size-report", action="store_true",
3413 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003414
3415 parser.add_argument(
3416 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003417 help="""Extra CMake cache entries to define when building test cases.
3418 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003419 prefixed with -D before being passed to CMake.
3420
3421 E.g
3422 "sanitycheck -x=USE_CCACHE=0"
3423 will translate to
3424 "cmake -DUSE_CCACHE=0"
3425
3426 which will ultimately disable ccache.
3427 """
3428 )
Michael Scott421ce462019-06-18 09:37:46 -07003429
Andy Doan79c48842019-02-08 10:09:04 -06003430 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003431 "--device-testing", action="store_true",
3432 help="Test on device directly. Specify the serial device to "
3433 "use with the --device-serial option.")
3434
3435 parser.add_argument(
3436 "-X", "--fixture", action="append", default=[],
3437 help="Specify a fixture that a board might support")
3438 parser.add_argument(
3439 "--device-serial",
3440 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3441
3442 parser.add_argument("--generate-hardware-map",
3443 help="""Probe serial devices connected to this platform
3444 and create a hardware map file to be used with
3445 --device-testing
3446 """)
3447
3448 parser.add_argument("--hardware-map",
3449 help="""Load hardware map from a file. This will be used
3450 for testing on hardware that is listed in the file.
3451 """)
3452
3453 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003454 "--west-flash", nargs='?', const=[],
3455 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003456 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003457
Michael Scott4ca54392019-07-09 14:21:30 -07003458 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003459 --west-flash="--board-id=foobar,--erase"
3460 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003461
3462 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003463 """
3464 )
Michael Scott421ce462019-06-18 09:37:46 -07003465 parser.add_argument(
3466 "--west-runner",
3467 help="""Uses the specified west runner instead of default when running
3468 with --west-flash.
3469
3470 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3471 --west-flash --west-runner=pyocd"
3472 will translate to "west flash --runner pyocd"
3473
3474 NOTE: west-flash must be enabled to use this option.
3475 """
3476 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003477
3478 valgrind_asan_group = parser.add_mutually_exclusive_group()
3479
3480 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003481 "--enable-valgrind", action="store_true",
3482 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003483 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003484 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003485 configuration and is mutual exclusive with --enable-asan.
3486 """)
3487
3488 valgrind_asan_group.add_argument(
3489 "--enable-asan", action="store_true",
3490 help="""Enable address sanitizer to check for several memory access
3491 errors. Libasan needs to be installed on the host. This option only
3492 works with host binaries such as those generated for the native_posix
3493 configuration and is mutual exclusive with --enable-valgrind.
3494 """)
3495
3496 parser.add_argument(
3497 "--enable-lsan", action="store_true",
3498 help="""Enable leak sanitizer to check for heap memory leaks.
3499 Libasan needs to be installed on the host. This option only
3500 works with host binaries such as those generated for the native_posix
3501 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003502 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003503
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003504 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003505 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003506
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003507 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003508 help="Generate coverage reports. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003509 "--enable_coverage.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003510
Andrew Boie8047a6f2019-07-02 15:43:29 -07003511 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003512 help="Plarforms to run coverage reports on. "
Anas Nashifd9882382019-12-12 09:58:28 -05003513 "This option may be used multiple times. "
3514 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003515
Anas Nashif83fc06a2019-06-22 11:04:10 -04003516 parser.add_argument("--gcov-tool", default=None,
3517 help="Path to the gcov tool to use for code coverage "
Anas Nashifd9882382019-12-12 09:58:28 -05003518 "reports")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003519
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003520 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='lcov',
3521 help="Tool to use to generate coverage report.")
3522
Andrew Boie6acbe632015-07-17 12:03:52 -07003523 return parser.parse_args()
3524
Anas Nashifd9882382019-12-12 09:58:28 -05003525
Andrew Boiebbd670c2015-08-17 13:16:11 -07003526def size_report(sc):
Anas Nashif7a361b82019-12-06 11:37:40 -05003527 logger.info(sc.filename)
3528 logger.info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003529 for i in range(len(sc.sections)):
3530 v = sc.sections[i]
3531
Anas Nashif7a361b82019-12-06 11:37:40 -05003532 logger.info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
Anas Nashifd9882382019-12-12 09:58:28 -05003533 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3534 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003535
Anas Nashif7a361b82019-12-06 11:37:40 -05003536 logger.info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashifd9882382019-12-12 09:58:28 -05003537 (sc.rom_size, sc.ram_size))
Anas Nashif7a361b82019-12-06 11:37:40 -05003538 logger.info("")
Andrew Boiebbd670c2015-08-17 13:16:11 -07003539
Anas Nashifd9882382019-12-12 09:58:28 -05003540
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003541class CoverageTool:
3542 """ Base class for every supported coverage tool
3543 """
3544
3545 def __init__(self):
3546 self.gcov_tool = options.gcov_tool
3547
3548 @staticmethod
3549 def factory(tool):
3550 if tool == 'lcov':
3551 return Lcov()
3552 if tool == 'gcovr':
3553 return Gcovr()
Anas Nashif7a361b82019-12-06 11:37:40 -05003554 logger.error("Unsupported coverage tool specified: {}".format(tool))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003555
3556 @staticmethod
3557 def retrieve_gcov_data(intput_file):
3558 if VERBOSE:
Anas Nashifd9882382019-12-12 09:58:28 -05003559 logger.debug("Working on %s" % intput_file)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003560 extracted_coverage_info = {}
3561 capture_data = False
3562 capture_complete = False
3563 with open(intput_file, 'r') as fp:
3564 for line in fp.readlines():
3565 if re.search("GCOV_COVERAGE_DUMP_START", line):
3566 capture_data = True
3567 continue
3568 if re.search("GCOV_COVERAGE_DUMP_END", line):
3569 capture_complete = True
3570 break
3571 # Loop until the coverage data is found.
3572 if not capture_data:
3573 continue
3574 if line.startswith("*"):
3575 sp = line.split("<")
3576 if len(sp) > 1:
3577 # Remove the leading delimiter "*"
3578 file_name = sp[0][1:]
3579 # Remove the trailing new line char
3580 hex_dump = sp[1][:-1]
3581 else:
3582 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003583 else:
3584 continue
Anas Nashifd9882382019-12-12 09:58:28 -05003585 extracted_coverage_info.update({file_name: hex_dump})
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003586 if not capture_data:
3587 capture_complete = True
3588 return {'complete': capture_complete, 'data': extracted_coverage_info}
3589
3590 @staticmethod
3591 def create_gcda_files(extracted_coverage_info):
3592 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003593 logger.debug("Generating gcda files")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003594 for filename, hexdump_val in extracted_coverage_info.items():
3595 # if kobject_hash is given for coverage gcovr fails
3596 # hence skipping it problem only in gcovr v4.1
3597 if "kobject_hash" in filename:
Anas Nashifd9882382019-12-12 09:58:28 -05003598 filename = (filename[:-4]) + "gcno"
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003599 try:
3600 os.remove(filename)
3601 except Exception:
3602 pass
Anas Nashifdb9592a2018-10-08 10:19:41 -04003603 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003604
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003605 with open(filename, 'wb') as fp:
3606 fp.write(bytes.fromhex(hexdump_val))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003607
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003608 def generate(self, outdir):
3609 for filename in glob.glob("%s/**/handler.log" % outdir, recursive=True):
3610 gcov_data = self.__class__.retrieve_gcov_data(filename)
3611 capture_complete = gcov_data['complete']
3612 extracted_coverage_info = gcov_data['data']
3613 if capture_complete:
3614 self.__class__.create_gcda_files(extracted_coverage_info)
Anas Nashif7a361b82019-12-06 11:37:40 -05003615 logger.debug("Gcov data captured: {}".format(filename))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003616 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003617 logger.error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003618
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003619 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3620 ret = self._generate(outdir, coveragelog)
3621 if ret == 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05003622 logger.info("HTML report generated: {}".format(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003623 os.path.join(outdir, "coverage", "index.html")))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003624
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003625
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003626class Lcov(CoverageTool):
3627
3628 def __init__(self):
3629 super().__init__()
3630 self.ignores = []
3631
3632 def add_ignore_file(self, pattern):
3633 self.ignores.append('*' + pattern + '*')
3634
3635 def add_ignore_directory(self, pattern):
3636 self.ignores.append(pattern + '/*')
3637
3638 def _generate(self, outdir, coveragelog):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003639 coveragefile = os.path.join(outdir, "coverage.info")
3640 ztestfile = os.path.join(outdir, "ztest.info")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003641 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool,
3642 "--capture", "--directory", outdir,
3643 "--rc", "lcov_branch_coverage=1",
3644 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003645 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003646 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--extract",
3647 coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003648 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003649 "--output-file", ztestfile,
3650 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3651
Anas Nashif3cbffef2018-11-07 23:50:54 -05003652 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003653 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3654 ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003655 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3656 "--output-file", ztestfile,
3657 "--rc", "lcov_branch_coverage=1"],
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003658 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003659 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003660 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003661 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003662
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003663 for i in self.ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003664 subprocess.call(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003665 ["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3666 coveragefile, i, "--output-file",
3667 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003668 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003669
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003670 # The --ignore-errors source option is added to avoid it exiting due to
3671 # samples/application_development/external_lib/
3672 return subprocess.call(["genhtml", "--legend", "--branch-coverage",
3673 "--ignore-errors", "source",
3674 "-output-directory",
3675 os.path.join(outdir, "coverage")] + files,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003676 stdout=coveragelog)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003677
3678
3679class Gcovr(CoverageTool):
3680
3681 def __init__(self):
3682 super().__init__()
3683 self.ignores = []
3684
3685 def add_ignore_file(self, pattern):
3686 self.ignores.append('.*' + pattern + '.*')
3687
3688 def add_ignore_directory(self, pattern):
3689 self.ignores.append(pattern + '/.*')
3690
3691 @staticmethod
3692 def _interleave_list(prefix, list):
3693 tuple_list = [(prefix, item) for item in list]
3694 return [item for sublist in tuple_list for item in sublist]
3695
3696 def _generate(self, outdir, coveragelog):
3697 coveragefile = os.path.join(outdir, "coverage.json")
3698 ztestfile = os.path.join(outdir, "ztest.json")
3699
3700 excludes = Gcovr._interleave_list("-e", self.ignores)
3701
3702 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
3703 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3704 self.gcov_tool, "-e", "tests/*"] + excludes +
3705 ["--json", "-o", coveragefile, outdir],
3706 stdout=coveragelog)
3707
3708 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3709 self.gcov_tool, "-f", "tests/ztest", "-e",
3710 "tests/ztest/test/*", "--json", "-o", ztestfile,
3711 outdir], stdout=coveragelog)
3712
3713 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
3714 files = [coveragefile, ztestfile]
3715 else:
3716 files = [coveragefile]
3717
3718 subdir = os.path.join(outdir, "coverage")
3719 os.makedirs(subdir, exist_ok=True)
3720
3721 tracefiles = self._interleave_list("--add-tracefile", files)
3722
3723 return subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--html",
3724 "--html-details"] + tracefiles +
3725 ["-o", os.path.join(subdir, "index.html")],
3726 stdout=coveragelog)
3727
Anas Nashif3ba1d432017-12-05 15:28:44 -05003728
Anas Nashif83fc06a2019-06-22 11:04:10 -04003729def get_generator():
3730 if options.ninja:
3731 generator_cmd = "ninja"
3732 generator = "Ninja"
3733 else:
3734 generator_cmd = "make"
3735 generator = "Unix Makefiles"
3736 return generator_cmd, generator
3737
3738
3739def export_tests(filename, tests):
3740 with open(filename, "wt") as csvfile:
3741 fieldnames = ['section', 'subsection', 'title', 'reference']
3742 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3743 for test in tests:
3744 data = test.split(".")
Anas Nashif19ca7832019-11-18 08:16:21 -08003745 if len(data) > 1:
3746 subsec = " ".join(data[1].split("_")).title()
3747 rowdict = {
Anas Nashifd9882382019-12-12 09:58:28 -05003748 "section": data[0].capitalize(),
3749 "subsection": subsec,
3750 "title": test,
3751 "reference": test
3752 }
Anas Nashif19ca7832019-11-18 08:16:21 -08003753 cw.writerow(rowdict)
3754 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003755 logger.info("{} can't be exported".format(test))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003756
Anas Nashif83fc06a2019-06-22 11:04:10 -04003757
3758def native_and_unit_first(a, b):
3759 if a[0].startswith('unit_testing'):
3760 return -1
3761 if b[0].startswith('unit_testing'):
3762 return 1
3763 if a[0].startswith('native_posix'):
3764 return -1
3765 if b[0].startswith('native_posix'):
3766 return 1
Anas Nashifd9882382019-12-12 09:58:28 -05003767 if a[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003768 return -1
Anas Nashifd9882382019-12-12 09:58:28 -05003769 if b[0].split("/", 1)[0].endswith("_bsim"):
Anas Nashif83fc06a2019-06-22 11:04:10 -04003770 return 1
3771
3772 return (a > b) - (a < b)
3773
3774
Anas Nashif5f908822019-11-25 08:19:25 -05003775class HardwareMap:
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003776
3777 schema_path = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk", "hwmap-schema.yaml")
3778
Anas Nashif5f908822019-11-25 08:19:25 -05003779 manufacturer = [
3780 'ARM',
3781 'SEGGER',
3782 'MBED',
3783 'STMicroelectronics',
3784 'Atmel Corp.',
3785 'Texas Instruments',
3786 'Silicon Labs',
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003787 'NXP Semiconductors',
3788 'Microchip Technology Inc.',
Anas Nashif5c331f92019-12-20 10:42:27 -05003789 'FTDI',
3790 'Digilent'
Anas Nashifd9882382019-12-12 09:58:28 -05003791 ]
Anas Nashif5f908822019-11-25 08:19:25 -05003792
3793 runner_mapping = {
3794 'pyocd': [
3795 'DAPLink CMSIS-DAP',
3796 'MBED CMSIS-DAP'
3797 ],
3798 'jlink': [
3799 'J-Link',
3800 'J-Link OB'
3801 ],
3802 'openocd': [
3803 'STM32 STLink', '^XDS110.*'
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003804 ],
3805 'dediprog': [
3806 'TTL232R-3V3',
3807 'MCP2200 USB Serial Port Emulator'
Anas Nashif5f908822019-11-25 08:19:25 -05003808 ]
3809 }
3810
3811 def __init__(self):
3812 self.detected = []
3813 self.connected_hardware = []
3814
3815 def load_device_from_cmdline(self, serial, platform):
3816 device = {
3817 "serial": serial,
3818 "platform": platform,
3819 "counter": 0,
3820 "available": True,
3821 "connected": True
3822 }
3823 self.connected_hardware.append(device)
3824
3825 def load_hardware_map(self, map_file):
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003826 hwm_schema = scl.yaml_load(self.schema_path)
3827 self.connected_hardware = scl.yaml_load_verify(map_file, hwm_schema)
Anas Nashif5f908822019-11-25 08:19:25 -05003828 for i in self.connected_hardware:
3829 i['counter'] = 0
3830
3831 def scan_hw(self):
3832 from serial.tools import list_ports
3833
3834 serial_devices = list_ports.comports()
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003835 logger.info("Scanning connected hardware...")
Anas Nashif5f908822019-11-25 08:19:25 -05003836 for d in serial_devices:
3837 if d.manufacturer in self.manufacturer:
3838
3839 # TI XDS110 can have multiple serial devices for a single board
3840 # assume endpoint 0 is the serial, skip all others
3841 if d.manufacturer == 'Texas Instruments' and not d.location.endswith('0'):
3842 continue
3843 s_dev = {}
3844 s_dev['platform'] = "unknown"
3845 s_dev['id'] = d.serial_number
3846 s_dev['serial'] = d.device
3847 s_dev['product'] = d.product
3848 s_dev['runner'] = 'unknown'
Anas Nashifd9882382019-12-12 09:58:28 -05003849 for runner, _ in self.runner_mapping.items():
Anas Nashif5f908822019-11-25 08:19:25 -05003850 products = self.runner_mapping.get(runner)
3851 if d.product in products:
3852 s_dev['runner'] = runner
3853 continue
3854 # Try regex matching
3855 for p in products:
3856 if re.match(p, d.product):
3857 s_dev['runner'] = runner
3858
3859 s_dev['available'] = True
3860 s_dev['connected'] = True
3861 self.detected.append(s_dev)
3862 else:
Anas Nashifd9882382019-12-12 09:58:28 -05003863 logger.warning("Unsupported device (%s): %s" % (d.manufacturer, d))
Anas Nashif5f908822019-11-25 08:19:25 -05003864
3865 def write_map(self, hwm_file):
3866 # use existing map
3867 if os.path.exists(hwm_file):
3868 with open(hwm_file, 'r') as yaml_file:
3869 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3870 # disconnect everything
3871 for h in hwm:
3872 h['connected'] = False
3873 h['serial'] = None
3874
3875 for d in self.detected:
3876 for h in hwm:
3877 if d['id'] == h['id'] and d['product'] == h['product']:
Anas Nashif5f908822019-11-25 08:19:25 -05003878 h['connected'] = True
3879 h['serial'] = d['serial']
3880 d['match'] = True
3881
Anas Nashifd9882382019-12-12 09:58:28 -05003882 new = list(filter(lambda n: not n.get('match', False), self.detected))
Anas Nashif5f908822019-11-25 08:19:25 -05003883 hwm = hwm + new
3884
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003885 logger.info("Registered devices:")
Anas Nashif0c69c282019-12-18 10:41:27 -05003886 self.dump(hwm)
Anas Nashife5006d12019-12-01 11:41:22 -05003887
Anas Nashif5f908822019-11-25 08:19:25 -05003888 with open(hwm_file, 'w') as yaml_file:
3889 yaml.dump(hwm, yaml_file, default_flow_style=False)
3890
3891 else:
3892 # create new file
3893 with open(hwm_file, 'w') as yaml_file:
3894 yaml.dump(self.detected, yaml_file, default_flow_style=False)
Anas Nashif0c69c282019-12-18 10:41:27 -05003895 logger.info("Detected devices:")
3896 self.dump(self.detected)
Anas Nashif5f908822019-11-25 08:19:25 -05003897
Anas Nashif0c69c282019-12-18 10:41:27 -05003898 @staticmethod
3899 def dump(hwmap=[], filtered=[], header=[], connected_only=False):
3900 print("")
3901 table = []
3902 if not header:
3903 header = ["Platform", "ID", "Serial device"]
3904 for p in sorted(hwmap, key=lambda i: i['platform']):
3905 platform = p.get('platform')
3906 connected = p.get('connected', False)
3907 if filtered and platform not in filtered:
3908 continue
3909
3910 if not connected_only or connected:
3911 table.append([platform, p.get('id', None), p.get('serial')])
3912
3913 print(tabulate(table, headers=header, tablefmt="github"))
Anas Nashifd9882382019-12-12 09:58:28 -05003914
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003915options = None
Anas Nashif5f908822019-11-25 08:19:25 -05003916
Anas Nashifd9882382019-12-12 09:58:28 -05003917
Andrew Boie6acbe632015-07-17 12:03:52 -07003918def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003919 start_time = time.time()
Anas Nashif7a361b82019-12-06 11:37:40 -05003920 global VERBOSE
Anas Nashife10b6512017-12-30 13:01:45 -05003921 global options
Andrew Boie1578ef72019-07-03 10:19:29 -07003922
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003923 options = parse_arguments()
Anas Nashif7a361b82019-12-06 11:37:40 -05003924
3925 # Cleanup
3926 if options.no_clean or options.only_failed or options.test_only:
3927 if os.path.exists(options.outdir):
Andrew Boie2d8d4c52020-01-02 19:31:20 -08003928 print("Keeping artifacts untouched")
Anas Nashif7a361b82019-12-06 11:37:40 -05003929 elif os.path.exists(options.outdir):
Andrew Boie114c01b2020-01-02 18:44:22 -08003930 if options.clobber_output:
3931 print("Deleting output directory {}".format(options.outdir))
3932 shutil.rmtree(options.outdir)
3933 else:
3934 for i in range(1, 100):
3935 new_out = options.outdir + ".{}".format(i)
3936 if not os.path.exists(new_out):
3937 print("Renaming output directory to {}".format(new_out))
3938 shutil.move(options.outdir, new_out)
3939 break
Anas Nashif7a361b82019-12-06 11:37:40 -05003940
3941 os.makedirs(options.outdir, exist_ok=True)
3942
3943 # create file handler which logs even debug messages
3944 if options.log_file:
3945 fh = logging.FileHandler(options.log_file)
3946 else:
3947 fh = logging.FileHandler(os.path.join(options.outdir, "sanitycheck.log"))
3948
3949 fh.setLevel(logging.DEBUG)
3950
3951 # create console handler with a higher log level
3952 ch = logging.StreamHandler()
3953
Anas Nashif7a361b82019-12-06 11:37:40 -05003954 VERBOSE += options.verbose
3955 if VERBOSE > 1:
3956 ch.setLevel(logging.DEBUG)
3957 else:
3958 ch.setLevel(logging.INFO)
3959
Anas Nashif7a361b82019-12-06 11:37:40 -05003960 # create formatter and add it to the handlers
Anas Nashifd4cef072019-12-08 11:58:00 -05003961 if options.timestamps:
3962 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
3963 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003964 formatter = logging.Formatter('%(levelname)-7s - %(message)s')
Anas Nashifd4cef072019-12-08 11:58:00 -05003965
Anas Nashif7a361b82019-12-06 11:37:40 -05003966 formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
3967 ch.setFormatter(formatter)
3968 fh.setFormatter(formatter_file)
3969
3970 # add the handlers to logger
3971 logger.addHandler(ch)
3972 logger.addHandler(fh)
Andrew Boiebbd670c2015-08-17 13:16:11 -07003973
Anas Nashif5f908822019-11-25 08:19:25 -05003974 hwm = HardwareMap()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003975 if options.generate_hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05003976 hwm.scan_hw()
3977 hwm.write_map(options.generate_hardware_map)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003978 return
3979
Anas Nashif5f908822019-11-25 08:19:25 -05003980 if not options.device_testing and options.hardware_map:
3981 hwm.load_hardware_map(options.hardware_map)
3982
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003983 logger.info("Available devices:")
Anas Nashif5f908822019-11-25 08:19:25 -05003984 table = []
Anas Nashif0c69c282019-12-18 10:41:27 -05003985 hwm.dump(hwmap=hwm.connected_hardware, connected_only=True)
Anas Nashif5f908822019-11-25 08:19:25 -05003986 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04003987
Michael Scott421ce462019-06-18 09:37:46 -07003988 if options.west_runner and not options.west_flash:
Anas Nashif7a361b82019-12-06 11:37:40 -05003989 logger.error("west-runner requires west-flash to be enabled")
Michael Scott421ce462019-06-18 09:37:46 -07003990 sys.exit(1)
3991
Michael Scott4ca54392019-07-09 14:21:30 -07003992 if options.west_flash and not options.device_testing:
Anas Nashif7a361b82019-12-06 11:37:40 -05003993 logger.error("west-flash requires device-testing to be enabled")
Michael Scott4ca54392019-07-09 14:21:30 -07003994 sys.exit(1)
3995
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003996 if options.coverage:
3997 options.enable_coverage = True
Christian Taedckec415a4e2019-11-23 23:25:36 +01003998
3999 if not options.coverage_platform:
4000 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02004001
Anas Nashife10b6512017-12-30 13:01:45 -05004002 if options.size:
4003 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08004004 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07004005 sys.exit(0)
4006
Anas Nashife10b6512017-12-30 13:01:45 -05004007 if options.subset:
4008 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04004009 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif7a361b82019-12-06 11:37:40 -05004010 logger.info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04004011 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004012 logger.error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04004013 return
4014
Anas Nashife10b6512017-12-30 13:01:45 -05004015 if not options.testcase_root:
4016 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Anas Nashifd9882382019-12-12 09:58:28 -05004017 os.path.join(ZEPHYR_BASE, "samples")]
Andrew Boie3d348712016-04-08 11:52:13 -07004018
Anas Nashife0d931f2019-12-09 15:23:43 -05004019 if options.show_footprint or options.compare_report or options.release:
4020 options.enable_size_report = True
4021
Anas Nashif56656842019-12-10 12:26:00 -05004022 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
4023
4024 # Set testsuite options from command line.
4025 suite.build_only = options.build_only
Anas Nashifc5ee3952019-12-10 16:38:45 -05004026 suite.cmake_only = options.cmake_only
4027 suite.test_only = options.test_only
Anas Nashif56656842019-12-10 12:26:00 -05004028 suite.enable_slow = options.enable_slow
4029 suite.device_testing = options.device_testing
4030 suite.fixture = options.fixture
4031 suite.enable_asan = options.enable_asan
4032 suite.enable_lsan = options.enable_lsan
4033 suite.enable_coverage = options.enable_coverage
Anas Nashif56656842019-12-10 12:26:00 -05004034 suite.enable_valgrind = options.enable_valgrind
4035 suite.coverage_platform = options.coverage_platform
Anas Nashife9eb0092019-12-10 16:31:22 -05004036 suite.inline_logs = options.inline_logs
Anas Nashifc5ee3952019-12-10 16:38:45 -05004037 suite.enable_size_report = options.enable_size_report
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004038
4039 # Set number of jobs
4040 if options.jobs:
4041 suite.jobs = options.jobs
4042 elif options.build_only:
4043 suite.jobs = multiprocessing.cpu_count() * 2
4044 else:
4045 suite.jobs = multiprocessing.cpu_count()
Anas Nashif7a361b82019-12-06 11:37:40 -05004046 logger.info("JOBS: %d" % suite.jobs)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004047
Anas Nashif83fc06a2019-06-22 11:04:10 -04004048 suite.add_testcases()
4049 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04004050
Anas Nashif83fc06a2019-06-22 11:04:10 -04004051 if options.device_testing:
4052 if options.hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05004053 hwm.load_hardware_map(options.hardware_map)
4054 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004055 if not options.platform:
4056 options.platform = []
Anas Nashif5f908822019-11-25 08:19:25 -05004057 for platform in hwm.connected_hardware:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004058 if platform['connected']:
4059 options.platform.append(platform['platform'])
4060
Anas Nashifd9882382019-12-12 09:58:28 -05004061 elif options.device_serial: # back-ward compatibility
Anas Nashif83fc06a2019-06-22 11:04:10 -04004062 if options.platform and len(options.platform) == 1:
Anas Nashif5f908822019-11-25 08:19:25 -05004063 hwm.load_device_from_cmdline(options.device_serial, options.platform[0])
Anas Nashifebf8dae2019-12-16 09:22:21 -05004064 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004065 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004066 logger.error("""When --device-testing is used with --device-serial, only one
Anas Nashif83fc06a2019-06-22 11:04:10 -04004067 platform is allowed""")
4068
Anas Nashif83fc06a2019-06-22 11:04:10 -04004069 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05004070 sys.exit(1)
4071
Anas Nashif75547e22018-02-24 08:32:14 -06004072 if options.list_tags:
4073 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004074 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06004075 tags = tags.union(tc.tags)
4076
4077 for t in tags:
4078 print("- {}".format(t))
4079
4080 return
4081
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004082 if options.export_tests:
4083 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004084 tests = suite.get_all_tests()
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004085 export_tests(options.export_tests, tests)
4086 return
4087
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004088 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004089
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004090 if options.test:
4091 run_individual_tests = options.test
4092
Anas Nashif434995c2019-12-01 13:55:11 -05004093 if options.list_tests or options.test_tree or options.list_test_duplicates or options.sub_test:
Anas Nashif49b22d42019-06-14 13:45:34 -04004094 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004095 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004096
Anas Nashif19ca7832019-11-18 08:16:21 -08004097 if options.list_test_duplicates:
4098 import collections
4099 dupes = [item for item, count in collections.Counter(all_tests).items() if count > 1]
4100 if dupes:
4101 print("Tests with duplicate identifiers:")
4102 for dupe in dupes:
4103 print("- {}".format(dupe))
4104 for dc in suite.get_testcase(dupe):
4105 print(" - {}".format(dc))
4106 else:
4107 print("No duplicates found.")
4108 return
4109
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004110 if options.sub_test:
Anas Nashifd11fd782019-11-18 10:22:56 -08004111 for st in options.sub_test:
4112 subtests = suite.get_testcase(st)
4113 for sti in subtests:
4114 run_individual_tests.append(sti.name)
4115
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004116 if run_individual_tests:
Anas Nashif7a361b82019-12-06 11:37:40 -05004117 logger.info("Running the following tests:")
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004118 for test in run_individual_tests:
4119 print(" - {}".format(test))
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004120 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004121 logger.info("Tests not found")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004122 return
4123
Anas Nashif434995c2019-12-01 13:55:11 -05004124 elif options.list_tests or options.test_tree:
4125 if options.test_tree:
4126 testsuite = Node("Testsuite")
4127 samples = Node("Samples", parent=testsuite)
4128 tests = Node("Tests", parent=testsuite)
4129
Anas Nashifc1c3cc62019-12-01 13:24:33 -05004130 for test in sorted(all_tests):
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004131 cnt = cnt + 1
Anas Nashif434995c2019-12-01 13:55:11 -05004132 if options.list_tests:
4133 print(" - {}".format(test))
4134
4135 if options.test_tree:
4136 if test.startswith("sample."):
4137 sec = test.split(".")
4138 area = find(samples, lambda node: node.name == sec[1] and node.parent == samples)
4139 if not area:
4140 area = Node(sec[1], parent=samples)
4141
4142 t = Node(test, parent=area)
4143 else:
4144 sec = test.split(".")
4145 area = find(tests, lambda node: node.name == sec[0] and node.parent == tests)
4146 if not area:
4147 area = Node(sec[0], parent=tests)
4148
4149 if area and len(sec) > 2:
4150 subarea = find(area, lambda node: node.name == sec[1] and node.parent == area)
4151 if not subarea:
4152 subarea = Node(sec[1], parent=area)
4153
4154 t = Node(test, parent=subarea)
4155
4156 if options.list_tests:
4157 print("{} total.".format(cnt))
4158
4159 if options.test_tree:
4160 for pre, _, node in RenderTree(testsuite):
4161 print("%s%s" % (pre, node.name))
4162
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004163 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05004164
Anas Nashifbd166f42017-09-02 12:32:08 -04004165 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04004166
4167 if options.only_failed:
4168 suite.get_last_failed()
Anas Nashif5f908822019-11-25 08:19:25 -05004169 suite.selected_platforms = set(p.platform.name for p in suite.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04004170 elif options.load_tests:
4171 suite.load_from_file(options.load_tests)
4172 elif options.test_only:
4173 last_run = os.path.join(options.outdir, "sanitycheck.csv")
4174 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04004175 else:
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004176 discards = suite.apply_filters(
4177 build_only=options.build_only,
4178 enable_slow=options.enable_slow,
4179 platform=options.platform,
4180 arch=options.arch,
4181 tag=options.tag,
4182 exclude_tag=options.exclude_tag,
4183 force_toolchain=options.force_toolchain,
4184 all=options.all,
4185 run_individual_tests=run_individual_tests,
4186 device_testing=options.device_testing
4187
4188 )
Andrew Boie6acbe632015-07-17 12:03:52 -07004189
Anas Nashif30551f42018-01-12 21:56:59 -05004190 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004191 # if we are using command line platform filter, no need to list every
4192 # other platform as excluded, we know that already.
4193 # Show only the discards that apply to the selected platforms on the
4194 # command line
4195
Andrew Boie08ce5a52016-02-22 13:28:10 -08004196 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004197 if options.platform and i.platform.name not in options.platform:
4198 continue
Anas Nashif7a361b82019-12-06 11:37:40 -05004199 logger.debug(
Anas Nashif3ba1d432017-12-05 15:28:44 -05004200 "{:<25} {:<50} {}SKIPPED{}: {}".format(
4201 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04004202 i.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05004203 Fore.YELLOW,
4204 Fore.RESET,
Anas Nashif3ba1d432017-12-05 15:28:44 -05004205 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07004206
Anas Nashif49b22d42019-06-14 13:45:34 -04004207 if options.report_excluded:
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004208 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004209 to_be_run = set()
Anas Nashifd9882382019-12-12 09:58:28 -05004210 for i, p in suite.instances.items():
Anas Nashif83fc06a2019-06-22 11:04:10 -04004211 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04004212
Anas Nashif83fc06a2019-06-22 11:04:10 -04004213 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004214 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004215 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004216 print("- {}".format(not_run))
4217
4218 return
4219
Anas Nashife10b6512017-12-30 13:01:45 -05004220 if options.subset:
Anas Nashifd9882382019-12-12 09:58:28 -05004221 # suite.instances = OrderedDict(sorted(suite.instances.items(),
Anas Nashif83fc06a2019-06-22 11:04:10 -04004222 # key=cmp_to_key(native_and_unit_first)))
Anas Nashife10b6512017-12-30 13:01:45 -05004223 subset, sets = options.subset.split("/")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004224 total = len(suite.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004225 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05004226 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04004227 if subset == sets:
4228 end = total
4229 else:
4230 end = start + per_set
4231
Anas Nashif83fc06a2019-06-22 11:04:10 -04004232 sliced_instances = islice(suite.instances.items(), start, end)
4233 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004234
Anas Nashif83fc06a2019-06-22 11:04:10 -04004235 if options.save_tests:
4236 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07004237 return
4238
Anas Nashif7a361b82019-12-06 11:37:40 -05004239 logger.info("%d test configurations selected, %d configurations discarded due to filters." %
Anas Nashifd9882382019-12-12 09:58:28 -05004240 (len(suite.instances), len(discards)))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004241
Peter Bigot3d46ea52019-11-21 12:00:18 -06004242 if options.device_testing:
4243 print("\nDevice testing on:")
Anas Nashif0c69c282019-12-18 10:41:27 -05004244 hwm.dump(suite.connected_hardware, suite.selected_platforms)
Anas Nashif5f908822019-11-25 08:19:25 -05004245 print("")
Peter Bigot3d46ea52019-11-21 12:00:18 -06004246
Anas Nashif83fc06a2019-06-22 11:04:10 -04004247 if options.dry_run:
4248 duration = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -05004249 logger.info("Completed in %d seconds" % (duration))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004250 return
4251
4252 retries = options.retry_failed + 1
4253 completed = 0
4254
4255 suite.update()
4256 suite.start_time = start_time
4257
4258 while True:
4259 completed += 1
4260
4261 if completed > 1:
Anas Nashifd9882382019-12-12 09:58:28 -05004262 logger.info("%d Iteration:" % (completed))
4263 time.sleep(60) # waiting for the system to settle down
Anas Nashif83fc06a2019-06-22 11:04:10 -04004264 suite.total_done = suite.total_tests - suite.total_failed
4265 suite.total_failed = 0
4266
Anas Nashifc5ee3952019-12-10 16:38:45 -05004267 suite.execute()
Anas Nashif7a361b82019-12-06 11:37:40 -05004268 print("")
Andrew Boie6acbe632015-07-17 12:03:52 -07004269
Anas Nashif83fc06a2019-06-22 11:04:10 -04004270 retries = retries - 1
4271 if retries == 0 or suite.total_failed == 0:
4272 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06004273
Anas Nashif83fc06a2019-06-22 11:04:10 -04004274 suite.misc_reports(options.compare_report, options.show_footprint,
Anas Nashifd9882382019-12-12 09:58:28 -05004275 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07004276
Anas Nashif83a98e52019-11-24 07:42:06 -05004277 suite.duration = time.time() - start_time
4278 suite.summary(options.disable_unrecognized_section_test)
4279
Anas Nashife10b6512017-12-30 13:01:45 -05004280 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004281 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004282 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07004283
4284 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004285 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004286 if ts_plat and (ts_plat.type in {"native", "unit"}):
4287 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07004288
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004289 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07004290 options.gcov_tool = "gcov"
4291 else:
4292 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
Anas Nashifd9882382019-12-12 09:58:28 -05004293 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
Andrew Boie49cf4862019-07-08 12:02:13 -07004294
Anas Nashif7a361b82019-12-06 11:37:40 -05004295 logger.info("Generating coverage files...")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01004296 coverage_tool = CoverageTool.factory(options.coverage_tool)
4297 coverage_tool.add_ignore_file('generated')
4298 coverage_tool.add_ignore_directory('tests')
4299 coverage_tool.add_ignore_directory('samples')
4300 coverage_tool.generate(options.outdir)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03004301
Anas Nashif83fc06a2019-06-22 11:04:10 -04004302 if options.device_testing:
4303 print("\nHardware distribution summary:\n")
Anas Nashif5f908822019-11-25 08:19:25 -05004304 table = []
4305 header = ['Board', 'ID', 'Counter']
4306 for p in hwm.connected_hardware:
4307 if p['connected'] and p['platform'] in suite.selected_platforms:
4308 row = [p['platform'], p.get('id', None), p['counter']]
4309 table.append(row)
4310 print(tabulate(table, headers=header, tablefmt="github"))
4311
Anas Nashif56656842019-12-10 12:26:00 -05004312 suite.save_reports(options.report_name,
4313 options.report_dir,
4314 options.no_update,
4315 options.release,
4316 options.only_failed)
4317
Anas Nashif83fc06a2019-06-22 11:04:10 -04004318 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07004319 sys.exit(1)
4320
Anas Nashifd9882382019-12-12 09:58:28 -05004321
Andrew Boie6acbe632015-07-17 12:03:52 -07004322if __name__ == "__main__":
Andrew Boie1fe1f3a2020-01-02 19:27:40 -08004323 try:
4324 main()
4325 finally:
4326 os.system("stty sane")