blob: 979e22d92cd46f1da7f2f50fee828d83edb6ead1 [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
Daniel Leung6b170072016-04-07 12:10:25 -0700188import concurrent
Anas Nashifb3311ed2017-04-13 14:44:48 -0400189import xml.etree.ElementTree as ET
Anas Nashif7a361b82019-12-06 11:37:40 -0500190import logging
Anas Nashif97445682019-12-16 09:36:40 -0500191from colorama import Fore
Anas Nashif035799f2017-05-13 21:31:53 -0400192from collections import OrderedDict
193from itertools import islice
Anas Nashife24350c2018-07-11 15:09:22 -0500194from pathlib import Path
195from distutils.spawn import find_executable
Anas Nashifd9882382019-12-12 09:58:28 -0500196
Anas Nashif434995c2019-12-01 13:55:11 -0500197try:
Kumar Galab81eec92020-02-07 17:38:16 -0600198 import serial
199except ImportError:
200 print("Install pyserial python module with pip to use --device-testing option.")
201
202try:
Anas Nashif434995c2019-12-01 13:55:11 -0500203 from anytree import Node, RenderTree, find
204except ImportError:
205 print("Install the anytree module to use the --test-tree option")
206
Anas Nashif5f908822019-11-25 08:19:25 -0500207try:
208 from tabulate import tabulate
209except ImportError:
210 print("Install tabulate python module with pip to use --device-testing option.")
Andrew Boie6acbe632015-07-17 12:03:52 -0700211
Kumar Gala7733b942019-09-12 17:08:43 -0500212ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
213if not ZEPHYR_BASE:
214 sys.exit("$ZEPHYR_BASE environment variable undefined")
215
216sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "dts"))
217import edtlib
Anas Nashif83fc06a2019-06-22 11:04:10 -0400218
Anas Nashif83fc06a2019-06-22 11:04:10 -0400219hw_map_local = threading.Lock()
Anas Nashifc1ea4522019-10-11 07:32:45 -0700220report_lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400221
Marc Herbert1c8632c2019-04-15 17:58:45 -0700222# Use this for internal comparisons; that's what canonicalization is
223# for. Don't use it when invoking other components of the build system
224# to avoid confusing and hard to trace inconsistencies in error messages
225# and logs, generated Makefiles, etc. compared to when users invoke these
226# components directly.
227# Note "normalization" is different from canonicalization, see os.path.
228canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
229
Andrew Boie3ea78922016-03-24 14:46:00 -0700230sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
231
Anas Nashif83fc06a2019-06-22 11:04:10 -0400232from sanity_chk import scl
233from sanity_chk import expr_parser
234
Andrew Boie6acbe632015-07-17 12:03:52 -0700235VERBOSE = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400236
Andrew Boie6acbe632015-07-17 12:03:52 -0700237RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
238 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700239
Andrew Boie6acbe632015-07-17 12:03:52 -0700240
Anas Nashif7a361b82019-12-06 11:37:40 -0500241logger = logging.getLogger('sanitycheck')
Anas Nashif7a361b82019-12-06 11:37:40 -0500242logger.setLevel(logging.DEBUG)
243
244
Anas Nashif45a97862019-01-09 08:46:42 -0500245class CMakeCacheEntry:
246 '''Represents a CMake cache entry.
247
248 This class understands the type system in a CMakeCache.txt, and
249 converts the following cache types to Python types:
250
251 Cache Type Python type
252 ---------- -------------------------------------------
253 FILEPATH str
254 PATH str
255 STRING str OR list of str (if ';' is in the value)
256 BOOL bool
257 INTERNAL str OR list of str (if ';' is in the value)
258 ---------- -------------------------------------------
259 '''
260
261 # Regular expression for a cache entry.
262 #
263 # CMake variable names can include escape characters, allowing a
264 # wider set of names than is easy to match with a regular
265 # expression. To be permissive here, use a non-greedy match up to
266 # the first colon (':'). This breaks if the variable name has a
267 # colon inside, but it's good enough.
268 CACHE_ENTRY = re.compile(
269 r'''(?P<name>.*?) # name
270 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
271 =(?P<value>.*) # value
272 ''', re.X)
273
274 @classmethod
275 def _to_bool(cls, val):
276 # Convert a CMake BOOL string into a Python bool.
277 #
278 # "True if the constant is 1, ON, YES, TRUE, Y, or a
279 # non-zero number. False if the constant is 0, OFF, NO,
280 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
281 # the suffix -NOTFOUND. Named boolean constants are
282 # case-insensitive. If the argument is not one of these
283 # constants, it is treated as a variable."
284 #
285 # https://cmake.org/cmake/help/v3.0/command/if.html
286 val = val.upper()
287 if val in ('ON', 'YES', 'TRUE', 'Y'):
288 return 1
289 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
290 return 0
291 elif val.endswith('-NOTFOUND'):
292 return 0
293 else:
294 try:
295 v = int(val)
296 return v != 0
297 except ValueError as exc:
298 raise ValueError('invalid bool {}'.format(val)) from exc
299
300 @classmethod
301 def from_line(cls, line, line_no):
302 # Comments can only occur at the beginning of a line.
303 # (The value of an entry could contain a comment character).
304 if line.startswith('//') or line.startswith('#'):
305 return None
306
307 # Whitespace-only lines do not contain cache entries.
308 if not line.strip():
309 return None
310
311 m = cls.CACHE_ENTRY.match(line)
312 if not m:
313 return None
314
315 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
316 if type_ == 'BOOL':
317 try:
318 value = cls._to_bool(value)
319 except ValueError as exc:
320 args = exc.args + ('on line {}: {}'.format(line_no, line),)
321 raise ValueError(args) from exc
Anas Nashifd9882382019-12-12 09:58:28 -0500322 elif type_ in ['STRING', 'INTERNAL']:
Anas Nashif45a97862019-01-09 08:46:42 -0500323 # If the value is a CMake list (i.e. is a string which
324 # contains a ';'), convert to a Python list.
325 if ';' in value:
326 value = value.split(';')
327
328 return CMakeCacheEntry(name, value)
329
330 def __init__(self, name, value):
331 self.name = name
332 self.value = value
333
334 def __str__(self):
335 fmt = 'CMakeCacheEntry(name={}, value={})'
336 return fmt.format(self.name, self.value)
337
338
339class CMakeCache:
340 '''Parses and represents a CMake cache file.'''
341
342 @staticmethod
343 def from_file(cache_file):
344 return CMakeCache(cache_file)
345
346 def __init__(self, cache_file):
347 self.cache_file = cache_file
348 self.load(cache_file)
349
350 def load(self, cache_file):
351 entries = []
352 with open(cache_file, 'r') as cache:
353 for line_no, line in enumerate(cache):
354 entry = CMakeCacheEntry.from_line(line, line_no)
355 if entry:
356 entries.append(entry)
357 self._entries = OrderedDict((e.name, e) for e in entries)
358
359 def get(self, name, default=None):
360 entry = self._entries.get(name)
361 if entry is not None:
362 return entry.value
363 else:
364 return default
365
366 def get_list(self, name, default=None):
367 if default is None:
368 default = []
369 entry = self._entries.get(name)
370 if entry is not None:
371 value = entry.value
372 if isinstance(value, list):
373 return value
374 elif isinstance(value, str):
375 return [value] if value else []
376 else:
377 msg = 'invalid value {} type {}'
378 raise RuntimeError(msg.format(value, type(value)))
379 else:
380 return default
381
382 def __contains__(self, name):
383 return name in self._entries
384
385 def __getitem__(self, name):
386 return self._entries[name].value
387
388 def __setitem__(self, name, entry):
389 if not isinstance(entry, CMakeCacheEntry):
390 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
391 raise TypeError(msg.format(type(entry), entry))
392 self._entries[name] = entry
393
394 def __delitem__(self, name):
395 del self._entries[name]
396
397 def __iter__(self):
398 return iter(self._entries.values())
399
Anas Nashif11ee5252019-12-04 12:59:10 -0500400
Andrew Boie6acbe632015-07-17 12:03:52 -0700401class SanityCheckException(Exception):
402 pass
403
Anas Nashif3ba1d432017-12-05 15:28:44 -0500404
Andrew Boie6acbe632015-07-17 12:03:52 -0700405class SanityRuntimeError(SanityCheckException):
406 pass
407
Anas Nashif11ee5252019-12-04 12:59:10 -0500408
Andrew Boie6acbe632015-07-17 12:03:52 -0700409class ConfigurationError(SanityCheckException):
410 def __init__(self, cfile, message):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400411 SanityCheckException.__init__(self, cfile + ": " + message)
Andrew Boie6acbe632015-07-17 12:03:52 -0700412
Anas Nashif11ee5252019-12-04 12:59:10 -0500413
Anas Nashif83fc06a2019-06-22 11:04:10 -0400414class BuildError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700415 pass
416
Anas Nashif3ba1d432017-12-05 15:28:44 -0500417
Anas Nashif83fc06a2019-06-22 11:04:10 -0400418class ExecutionError(SanityCheckException):
Andrew Boie6acbe632015-07-17 12:03:52 -0700419 pass
420
Anas Nashif11ee5252019-12-04 12:59:10 -0500421
Anas Nashif576be982017-12-23 20:20:27 -0500422class HarnessImporter:
423
424 def __init__(self, name):
425 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
426 module = __import__("harness")
427 if name:
428 my_class = getattr(module, name)
429 else:
430 my_class = getattr(module, "Test")
431
432 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500433
Anas Nashif11ee5252019-12-04 12:59:10 -0500434
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300435class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400436 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300437 """Constructor
438
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300439 """
440 self.lock = threading.Lock()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400441
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300442 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500443 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -0400444 self.duration = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400445 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300446
Anas Nashifdf7ee612018-07-07 06:09:01 -0500447 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100448 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500449 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500450
Anas Nashifd3384fb2018-02-22 06:44:16 -0600451 self.name = instance.name
452 self.instance = instance
Anas Nashif83fc06a2019-06-22 11:04:10 -0400453 self.timeout = instance.testcase.timeout
454 self.sourcedir = instance.testcase.source_dir
455 self.build_dir = instance.build_dir
456 self.log = os.path.join(self.build_dir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600457 self.returncode = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -0400458 self.set_state("running", self.duration)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600459
Anas Nashif83fc06a2019-06-22 11:04:10 -0400460 self.args = []
461
462 def set_state(self, state, duration):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300463 self.lock.acquire()
464 self.state = state
Anas Nashif83fc06a2019-06-22 11:04:10 -0400465 self.duration = duration
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300466 self.lock.release()
467
468 def get_state(self):
469 self.lock.acquire()
Anas Nashif83fc06a2019-06-22 11:04:10 -0400470 ret = (self.state, self.duration)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300471 self.lock.release()
472 return ret
473
Anas Nashif83fc06a2019-06-22 11:04:10 -0400474 def record(self, harness):
475 if harness.recording:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -0500476 filename = os.path.join(self.build_dir, "recording.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -0400477 with open(filename, "at") as csvfile:
478 cw = csv.writer(csvfile, harness.fieldnames, lineterminator=os.linesep)
479 cw.writerow(harness.fieldnames)
480 for instance in harness.recording:
481 cw.writerow(instance)
482
Anas Nashif11ee5252019-12-04 12:59:10 -0500483
Anas Nashifdf7ee612018-07-07 06:09:01 -0500484class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400485 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500486 """Constructor
487
488 @param instance Test Instance
489 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400490 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500491
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100492 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500493
Anas Nashif6c0e1702019-12-05 15:24:52 -0500494 # Tool options
495 self.valgrind = False
496 self.lsan = False
497 self.asan = False
498 self.coverage = False
499
Jan Kowalewski265895b2019-01-07 16:40:24 +0100500 def try_kill_process_by_pid(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -0400501 if self.pid_fn:
Jan Kowalewski265895b2019-01-07 16:40:24 +0100502 pid = int(open(self.pid_fn).read())
503 os.unlink(self.pid_fn)
504 self.pid_fn = None # clear so we don't try to kill the binary twice
505 try:
506 os.kill(pid, signal.SIGTERM)
507 except ProcessLookupError:
508 pass
509
Kumar Gala34b1ef82019-12-12 04:38:42 -0600510 def terminate(self, proc):
511 # encapsulate terminate functionality so we do it consistently where ever
512 # we might want to terminate the proc. We need try_kill_process_by_pid
513 # because of both how newer ninja (1.6.0 or greater) and .NET / renode
514 # work. Newer ninja's don't seem to pass SIGTERM down to the children
515 # so we need to use try_kill_process_by_pid.
516 self.try_kill_process_by_pid()
517 proc.terminate()
518 self.terminated = True
519
Anas Nashifdf7ee612018-07-07 06:09:01 -0500520 def _output_reader(self, proc, harness):
521 log_out_fp = open(self.log, "wt")
522 for line in iter(proc.stdout.readline, b''):
Anas Nashif7a361b82019-12-06 11:37:40 -0500523 logger.debug("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
Anas Nashifdf7ee612018-07-07 06:09:01 -0500524 log_out_fp.write(line.decode('utf-8'))
525 log_out_fp.flush()
526 harness.handle(line.decode('utf-8').rstrip())
527 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100528 try:
Anas Nashifd9882382019-12-12 09:58:28 -0500529 # POSIX arch based ztests end on their own,
530 # so let's give it up to 100ms to do so
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100531 proc.wait(0.1)
532 except subprocess.TimeoutExpired:
Kumar Gala34b1ef82019-12-12 04:38:42 -0600533 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500534 break
535
536 log_out_fp.close()
537
538 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500539
Anas Nashif83fc06a2019-06-22 11:04:10 -0400540 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500541 harness_import = HarnessImporter(harness_name)
542 harness = harness_import.instance
543 harness.configure(self.instance)
544
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500545 if self.call_make_run:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400546 command = [get_generator()[0], "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500547 else:
548 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500549
Anas Nashifc1ea4522019-10-11 07:32:45 -0700550 run_valgrind = False
Anas Nashif6c0e1702019-12-05 15:24:52 -0500551 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500552 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100553 "--leak-check=full",
Anas Nashifd9882382019-12-12 09:58:28 -0500554 "--suppressions=" + ZEPHYR_BASE + "/scripts/valgrind.supp",
555 "--log-file=" + self.build_dir + "/valgrind.log"
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100556 ] + command
Anas Nashifc1ea4522019-10-11 07:32:45 -0700557 run_valgrind = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500558
Anas Nashif7a361b82019-12-06 11:37:40 -0500559 logger.debug("Spawning process: " +
Anas Nashifd9882382019-12-12 09:58:28 -0500560 " ".join(shlex.quote(word) for word in command) + os.linesep +
561 "in directory: " + self.build_dir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200562
Anas Nashif83fc06a2019-06-22 11:04:10 -0400563 start_time = time.time()
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200564
Anas Nashif89c83042019-11-05 05:55:39 -0800565 env = os.environ.copy()
Anas Nashif6c0e1702019-12-05 15:24:52 -0500566 if self.asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200567 env["ASAN_OPTIONS"] = "log_path=stdout:" + \
Anas Nashifd9882382019-12-12 09:58:28 -0500568 env.get("ASAN_OPTIONS", "")
Anas Nashif6c0e1702019-12-05 15:24:52 -0500569 if not self.lsan:
Jan Van Winkel21212f32019-09-12 00:03:35 +0200570 env["ASAN_OPTIONS"] += "detect_leaks=0"
Anas Nashif32e92442019-12-21 16:15:46 -0500571
Jan Van Winkel21212f32019-09-12 00:03:35 +0200572 with subprocess.Popen(command, stdout=subprocess.PIPE,
Anas Nashifd9882382019-12-12 09:58:28 -0500573 stderr=subprocess.PIPE, cwd=self.build_dir, env=env) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -0500574 logger.debug("Spawning BinaryHandler Thread for %s" % self.name)
Anas Nashifd9882382019-12-12 09:58:28 -0500575 t = threading.Thread(target=self._output_reader, args=(proc, harness,), daemon=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500576 t.start()
577 t.join(self.timeout)
578 if t.is_alive():
Kumar Gala34b1ef82019-12-12 04:38:42 -0600579 self.terminate(proc)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500580 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500581 proc.wait()
582 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500583
Anas Nashif83fc06a2019-06-22 11:04:10 -0400584 handler_time = time.time() - start_time
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200585
Anas Nashif6c0e1702019-12-05 15:24:52 -0500586 if self.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400587 subprocess.call(["GCOV_PREFIX=" + self.build_dir,
Anas Nashifd9882382019-12-12 09:58:28 -0500588 "gcov", self.sourcedir, "-b", "-s", self.build_dir], shell=True)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500589
Jan Kowalewski265895b2019-01-07 16:40:24 +0100590 self.try_kill_process_by_pid()
591
Anas Nashif83fc06a2019-06-22 11:04:10 -0400592 # FIXME: This is needed when killing the simulator, the console is
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500593 # garbled and needs to be reset. Did not find a better way to do that.
594
595 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500596 self.instance.results = harness.tests
Anas Nashifc1ea4522019-10-11 07:32:45 -0700597
Anas Nashif83fc06a2019-06-22 11:04:10 -0400598 if not self.terminated and self.returncode != 0:
Anas Nashifd9882382019-12-12 09:58:28 -0500599 # When a process is killed, the default handler returns 128 + SIGTERM
600 # so in that case the return code itself is not meaningful
Anas Nashiff72d1902019-10-18 17:20:44 -0400601 self.set_state("failed", handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500602 self.instance.reason = "Failed"
Anas Nashifc1ea4522019-10-11 07:32:45 -0700603 elif run_valgrind and self.returncode == 2:
604 self.set_state("failed", handler_time)
605 self.instance.reason = "Valgrind error"
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100606 elif harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400607 self.set_state(harness.state, handler_time)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500608 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400609 self.set_state("timeout", handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500610 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400611
612 self.record(harness)
Anas Nashif73440ea2018-02-19 10:57:03 -0600613
Anas Nashif11ee5252019-12-04 12:59:10 -0500614
Anas Nashif73440ea2018-02-19 10:57:03 -0600615class DeviceHandler(Handler):
616
Anas Nashifd18ec532019-04-11 23:20:39 -0400617 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600618 """Constructor
619
620 @param instance Test Instance
621 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400622 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600623
Anas Nashif83fc06a2019-06-22 11:04:10 -0400624 self.suite = None
625
Marti Bolivar5591ca22019-02-07 15:53:39 -0700626 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500627 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600628
Marti Bolivar5591ca22019-02-07 15:53:39 -0700629 ser_fileno = ser.fileno()
630 readlist = [halt_fileno, ser_fileno]
631
Anas Nashif73440ea2018-02-19 10:57:03 -0600632 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700633 readable, _, _ = select.select(readlist, [], [], self.timeout)
634
635 if halt_fileno in readable:
Anas Nashif7a361b82019-12-06 11:37:40 -0500636 logger.debug('halted')
Marti Bolivar5591ca22019-02-07 15:53:39 -0700637 ser.close()
638 break
639 if ser_fileno not in readable:
Anas Nashifd9882382019-12-12 09:58:28 -0500640 continue # Timeout.
Marti Bolivar5591ca22019-02-07 15:53:39 -0700641
642 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500643 try:
644 serial_line = ser.readline()
645 except TypeError:
646 pass
Anas Nashif83fc06a2019-06-22 11:04:10 -0400647 except serial.SerialException:
Anas Nashif59237602019-03-03 10:36:35 -0500648 ser.close()
649 break
Anas Nashif61e21632018-04-08 13:30:16 -0500650
Marti Bolivar5591ca22019-02-07 15:53:39 -0700651 # Just because ser_fileno has data doesn't mean an entire line
652 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600653 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600654 sl = serial_line.decode('utf-8', 'ignore')
Anas Nashif7a361b82019-12-06 11:37:40 -0500655 logger.debug("DEVICE: {0}".format(sl.rstrip()))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600656
657 log_out_fp.write(sl)
658 log_out_fp.flush()
659 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700660
Anas Nashif73440ea2018-02-19 10:57:03 -0600661 if harness.state:
662 ser.close()
663 break
664
665 log_out_fp.close()
666
Anas Nashif83fc06a2019-06-22 11:04:10 -0400667 def device_is_available(self, device):
668 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500669 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400670 return True
671
672 return False
673
674 def get_available_device(self, device):
675 for i in self.suite.connected_hardware:
Anas Nashif5f908822019-11-25 08:19:25 -0500676 if i['platform'] == device and i['available'] and i['serial']:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400677 i['available'] = False
678 i['counter'] += 1
679 return i
680
681 return None
682
683 def make_device_available(self, serial):
684 with hw_map_local:
685 for i in self.suite.connected_hardware:
686 if i['serial'] == serial:
687 i['available'] = True
688
Anas Nashif1b1a7e22020-01-03 08:44:29 -0500689 @staticmethod
690 def run_custom_script(script, timeout):
691 with subprocess.Popen(script, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
692 try:
693 stdout, _ = proc.communicate(timeout=timeout)
694 logger.debug(stdout.decode())
695
696 except subprocess.TimeoutExpired:
697 proc.kill()
698 proc.communicate()
699 logger.error("{} timed out".format(script))
700
Anas Nashif73440ea2018-02-19 10:57:03 -0600701 def handle(self):
702 out_state = "failed"
703
Anas Nashif83fc06a2019-06-22 11:04:10 -0400704 if options.west_flash:
705 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
706 if options.west_runner:
Michael Scott421ce462019-06-18 09:37:46 -0700707 command.append("--runner")
708 command.append(options.west_runner)
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200709 # There are three ways this option is used.
Andy Doan79c48842019-02-08 10:09:04 -0600710 # 1) bare: --west-flash
711 # This results in options.west_flash == []
712 # 2) with a value: --west-flash="--board-id=42"
713 # This results in options.west_flash == "--board-id=42"
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200714 # 3) Multiple values: --west-flash="--board-id=42,--erase"
715 # This results in options.west_flash == "--board-id=42 --erase"
Andy Doan79c48842019-02-08 10:09:04 -0600716 if options.west_flash != []:
717 command.append('--')
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +0200718 command.extend(options.west_flash.split(','))
Anas Nashifd3384fb2018-02-22 06:44:16 -0600719 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400720 command = [get_generator()[0], "-C", self.build_dir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600721
Anas Nashif83fc06a2019-06-22 11:04:10 -0400722 while not self.device_is_available(self.instance.platform.name):
Anas Nashif64c84b12020-01-03 10:12:54 -0500723 logger.debug("Waiting for device {} to become available".format(self.instance.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400724 time.sleep(1)
725
726 hardware = self.get_available_device(self.instance.platform.name)
727
728 runner = hardware.get('runner', None)
729 if runner:
Peter Bigotda738482019-11-21 11:55:26 -0600730 board_id = hardware.get("probe_id", hardware.get("id", None))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400731 product = hardware.get("product", None)
732 command = ["west", "flash", "--skip-rebuild", "-d", self.build_dir]
733 command.append("--runner")
734 command.append(hardware.get('runner', None))
735 if runner == "pyocd":
736 command.append("--board-id")
737 command.append(board_id)
738 elif runner == "nrfjprog":
739 command.append('--')
740 command.append("--snr")
741 command.append(board_id)
742 elif runner == "openocd" and product == "STM32 STLink":
743 command.append('--')
744 command.append("--cmd-pre-init")
Anas Nashifd9882382019-12-12 09:58:28 -0500745 command.append("hla_serial %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400746 elif runner == "openocd" and product == "EDBG CMSIS-DAP":
747 command.append('--')
748 command.append("--cmd-pre-init")
Anas Nashifd9882382019-12-12 09:58:28 -0500749 command.append("cmsis_dap_serial %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400750 elif runner == "jlink":
Anas Nashifd9882382019-12-12 09:58:28 -0500751 command.append("--tool-opt=-SelectEmuBySN %s" % (board_id))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400752
753 serial_device = hardware['serial']
754
755 try:
756 ser = serial.Serial(
Anas Nashifd9882382019-12-12 09:58:28 -0500757 serial_device,
758 baudrate=115200,
759 parity=serial.PARITY_NONE,
760 stopbits=serial.STOPBITS_ONE,
761 bytesize=serial.EIGHTBITS,
762 timeout=self.timeout
763 )
Anas Nashif83fc06a2019-06-22 11:04:10 -0400764 except serial.SerialException as e:
765 self.set_state("failed", 0)
Anas Nashif17d066b2019-12-17 14:37:16 -0500766 self.instance.reason = "Failed"
Anas Nashifd9882382019-12-12 09:58:28 -0500767 logger.error("Serial device error: %s" % (str(e)))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400768 self.make_device_available(serial_device)
769 return
Anas Nashif73440ea2018-02-19 10:57:03 -0600770
771 ser.flush()
772
Anas Nashif83fc06a2019-06-22 11:04:10 -0400773 harness_name = self.instance.testcase.harness.capitalize()
Anas Nashif73440ea2018-02-19 10:57:03 -0600774 harness_import = HarnessImporter(harness_name)
775 harness = harness_import.instance
776 harness.configure(self.instance)
Anas Nashif83fc06a2019-06-22 11:04:10 -0400777 read_pipe, write_pipe = os.pipe()
778 start_time = time.time()
Anas Nashif73440ea2018-02-19 10:57:03 -0600779
Anas Nashiffc85ff02019-12-19 11:59:54 -0500780 pre_script = hardware.get('pre_script')
Anas Nashife9450062020-01-05 22:43:52 -0500781 post_flash_script = hardware.get('post_flash_script')
Anas Nashiffc85ff02019-12-19 11:59:54 -0500782 post_script = hardware.get('post_script')
783
784 if pre_script:
Anas Nashif1b1a7e22020-01-03 08:44:29 -0500785 self.run_custom_script(pre_script, 30)
Anas Nashiffc85ff02019-12-19 11:59:54 -0500786
Marti Bolivar5591ca22019-02-07 15:53:39 -0700787 t = threading.Thread(target=self.monitor_serial, daemon=True,
Anas Nashifd9882382019-12-12 09:58:28 -0500788 args=(ser, read_pipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600789 t.start()
790
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500791 d_log = "{}/device.log".format(self.instance.build_dir)
Anas Nashif7a361b82019-12-06 11:37:40 -0500792 logger.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500793 try:
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500794 stdout = stderr = None
795 with subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE) as proc:
796 try:
797 (stdout, stderr) = proc.communicate(timeout=30)
Anas Nashiffa8085e2019-12-09 16:42:58 -0500798 logger.debug(stdout.decode())
799
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500800 if proc.returncode != 0:
801 self.instance.reason = "Device issue (Flash?)"
802 with open(d_log, "w") as dlog_fp:
803 dlog_fp.write(stderr.decode())
804 except subprocess.TimeoutExpired:
805 proc.kill()
Anas Nashifd9882382019-12-12 09:58:28 -0500806 (stdout, stderr) = proc.communicate()
Anas Nashif33ed7aa2019-11-25 12:18:23 -0500807 self.instance.reason = "Device issue (Timeout)"
808
809 with open(d_log, "w") as dlog_fp:
810 dlog_fp.write(stderr.decode())
Anas Nashif83fc06a2019-06-22 11:04:10 -0400811
Anas Nashif61e21632018-04-08 13:30:16 -0500812 except subprocess.CalledProcessError:
Anas Nashifd9882382019-12-12 09:58:28 -0500813 os.write(write_pipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600814
Anas Nashife9450062020-01-05 22:43:52 -0500815 if post_flash_script:
816 self.run_custom_script(post_flash_script, 30)
817
818
Anas Nashif73440ea2018-02-19 10:57:03 -0600819 t.join(self.timeout)
820 if t.is_alive():
Anas Nashifbdffb882020-01-03 16:29:46 -0500821 logger.debug("Timed out while monitoring serial output on {}".format(self.instance.platform.name))
Anas Nashif73440ea2018-02-19 10:57:03 -0600822 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600823
824 if ser.isOpen():
825 ser.close()
826
Anas Nashifbdffb882020-01-03 16:29:46 -0500827 os.close(write_pipe)
828 os.close(read_pipe)
829
Anas Nashif17d066b2019-12-17 14:37:16 -0500830 handler_time = time.time() - start_time
831
Anas Nashifd3384fb2018-02-22 06:44:16 -0600832 if out_state == "timeout":
Anas Nashif83fc06a2019-06-22 11:04:10 -0400833 for c in self.instance.testcase.cases:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600834 if c not in harness.tests:
835 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500836
Anas Nashif17d066b2019-12-17 14:37:16 -0500837 self.instance.reason = "Timeout"
Anas Nashif83fc06a2019-06-22 11:04:10 -0400838
Anas Nashif61e21632018-04-08 13:30:16 -0500839 self.instance.results = harness.tests
Anas Nashif17d066b2019-12-17 14:37:16 -0500840
Anas Nashif73440ea2018-02-19 10:57:03 -0600841 if harness.state:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400842 self.set_state(harness.state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500843 if harness.state == "failed":
844 self.instance.reason = "Failed"
Anas Nashif73440ea2018-02-19 10:57:03 -0600845 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -0400846 self.set_state(out_state, handler_time)
Anas Nashif73440ea2018-02-19 10:57:03 -0600847
Anas Nashiffc85ff02019-12-19 11:59:54 -0500848 if post_script:
Anas Nashif1b1a7e22020-01-03 08:44:29 -0500849 self.run_custom_script(post_script, 30)
Anas Nashiffc85ff02019-12-19 11:59:54 -0500850
Anas Nashif83fc06a2019-06-22 11:04:10 -0400851 self.make_device_available(serial_device)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500852
Anas Nashif3a00d0c2019-11-12 11:07:15 -0500853 self.record(harness)
854
Anas Nashif11ee5252019-12-04 12:59:10 -0500855
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300856class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700857 """Spawns a thread to monitor QEMU output from pipes
858
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400859 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700860 We need to do this as once qemu starts, it runs forever until killed.
861 Test cases emit special messages to the console as they run, we check
862 for these to collect whether the test passed or failed.
863 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700864
Anas Nashif83fc06a2019-06-22 11:04:10 -0400865 def __init__(self, instance, type_str):
866 """Constructor
867
868 @param instance Test instance
869 """
870
871 super().__init__(instance, type_str)
872 self.fifo_fn = os.path.join(instance.build_dir, "qemu-fifo")
873
874 self.pid_fn = os.path.join(instance.build_dir, "qemu.pid")
875
Andrew Boie6acbe632015-07-17 12:03:52 -0700876 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500877 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700878 fifo_in = fifo_fn + ".in"
879 fifo_out = fifo_fn + ".out"
880
881 # These in/out nodes are named from QEMU's perspective, not ours
882 if os.path.exists(fifo_in):
883 os.unlink(fifo_in)
884 os.mkfifo(fifo_in)
885 if os.path.exists(fifo_out):
886 os.unlink(fifo_out)
887 os.mkfifo(fifo_out)
888
889 # We don't do anything with out_fp but we need to open it for
890 # writing so that QEMU doesn't block, due to the way pipes work
891 out_fp = open(fifo_in, "wb")
892 # Disable internal buffering, we don't
893 # want read() or poll() to ever block if there is data in there
894 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800895 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700896
897 start_time = time.time()
898 timeout_time = start_time + timeout
899 p = select.poll()
900 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400901 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700902
Andrew Boie6acbe632015-07-17 12:03:52 -0700903 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500904 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700905 while True:
906 this_timeout = int((timeout_time - time.time()) * 1000)
907 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400908 if not out_state:
909 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700910 break
911
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500912 try:
913 c = in_fp.read(1).decode("utf-8")
914 except UnicodeDecodeError:
915 # Test is writing something weird, fail
916 out_state = "unexpected byte"
917 break
918
Andrew Boie6acbe632015-07-17 12:03:52 -0700919 if c == "":
920 # EOF, this shouldn't happen unless QEMU crashes
921 out_state = "unexpected eof"
922 break
923 line = line + c
924 if c != "\n":
925 continue
926
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300927 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700928 log_out_fp.write(line)
929 log_out_fp.flush()
930 line = line.strip()
Anas Nashif7a361b82019-12-06 11:37:40 -0500931 logger.debug("QEMU: %s" % line)
Andrew Boie6acbe632015-07-17 12:03:52 -0700932
Anas Nashif576be982017-12-23 20:20:27 -0500933 harness.handle(line)
934 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400935 # if we have registered a fail make sure the state is not
936 # overridden by a false success message coming from the
937 # testsuite
938 if out_state != 'failed':
939 out_state = harness.state
940
941 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700942 # the timeout and wait for 2 more seconds to catch anything
943 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700944 # coverage is enabled since dumping this information can
945 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500946 if not timeout_extended or harness.capture_coverage:
Anas Nashifd9882382019-12-12 09:58:28 -0500947 timeout_extended = True
Anas Nashiff29087e2019-01-25 09:37:38 -0500948 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700949 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500950 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500951 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700952 line = ""
953
Anas Nashif83fc06a2019-06-22 11:04:10 -0400954 handler.record(harness)
955
956 handler_time = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -0500957 logger.debug("QEMU complete (%s) after %f seconds" %
Anas Nashifd9882382019-12-12 09:58:28 -0500958 (out_state, handler_time))
Anas Nashif83fc06a2019-06-22 11:04:10 -0400959 handler.set_state(out_state, handler_time)
Anas Nashif17d066b2019-12-17 14:37:16 -0500960 if out_state == "timeout":
961 handler.instance.reason = "Timeout"
962 elif out_state == "failed":
963 handler.instance.reason = "Failed"
Andrew Boie6acbe632015-07-17 12:03:52 -0700964
965 log_out_fp.close()
966 out_fp.close()
967 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400968 if os.path.exists(pid_fn):
969 pid = int(open(pid_fn).read())
970 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700971
Anas Nashifd6476ee2019-04-11 11:40:09 -0400972 try:
973 if pid:
974 os.kill(pid, signal.SIGTERM)
975 except ProcessLookupError:
976 # Oh well, as long as it's dead! User probably sent Ctrl-C
977 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800978
Andrew Boie6acbe632015-07-17 12:03:52 -0700979 os.unlink(fifo_in)
980 os.unlink(fifo_out)
981
Anas Nashif83fc06a2019-06-22 11:04:10 -0400982 def handle(self):
Andrew Boie6acbe632015-07-17 12:03:52 -0700983 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500984 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700985
986 # We pass this to QEMU which looks for fifos with .in and .out
987 # suffixes.
Anas Nashif83fc06a2019-06-22 11:04:10 -0400988 self.fifo_fn = os.path.join(self.instance.build_dir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700989
Anas Nashif83fc06a2019-06-22 11:04:10 -0400990 self.pid_fn = os.path.join(self.instance.build_dir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700991 if os.path.exists(self.pid_fn):
992 os.unlink(self.pid_fn)
993
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500994 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500995
Anas Nashif83fc06a2019-06-22 11:04:10 -0400996 harness_import = HarnessImporter(self.instance.testcase.harness.capitalize())
Anas Nashif576be982017-12-23 20:20:27 -0500997 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600998 harness.configure(self.instance)
999 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
Anas Nashif83fc06a2019-06-22 11:04:10 -04001000 args=(self, self.timeout, self.build_dir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001001 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -05001002 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001003
1004 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -07001005 self.thread.daemon = True
Anas Nashif7a361b82019-12-06 11:37:40 -05001006 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001007 self.thread.start()
Anas Nashif83fc06a2019-06-22 11:04:10 -04001008 subprocess.call(["stty", "sane"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001009
Anas Nashifd9882382019-12-12 09:58:28 -05001010 logger.debug("Running %s (%s)" % (self.name, self.type_str))
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001011 command = [get_generator()[0]]
1012 command += ["-C", self.build_dir, "run"]
1013
1014 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.build_dir) as proc:
Anas Nashif7a361b82019-12-06 11:37:40 -05001015 logger.debug("Spawning QEMUHandler Thread for %s" % self.name)
Stephanos Ioannidise1827c02019-11-12 14:11:31 +09001016 proc.wait()
1017 self.returncode = proc.returncode
1018
1019 if self.returncode != 0:
1020 self.set_state("failed", 0)
1021 self.instance.reason = "Exited with {}".format(self.returncode)
1022
Andrew Boie6acbe632015-07-17 12:03:52 -07001023 def get_fifo(self):
1024 return self.fifo_fn
1025
Anas Nashif11ee5252019-12-04 12:59:10 -05001026
Andrew Boie6acbe632015-07-17 12:03:52 -07001027class SizeCalculator:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001028 alloc_sections = [
1029 "bss",
1030 "noinit",
1031 "app_bss",
1032 "app_noinit",
1033 "ccm_bss",
1034 "ccm_noinit"
Anas Nashifd9882382019-12-12 09:58:28 -05001035 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001036
1037 rw_sections = [
1038 "datas",
1039 "initlevel",
1040 "exceptions",
1041 "initshell",
1042 "_static_thread_area",
1043 "_k_timer_area",
1044 "_k_mem_slab_area",
1045 "_k_mem_pool_area",
1046 "sw_isr_table",
1047 "_k_sem_area",
1048 "_k_mutex_area",
1049 "app_shmem_regions",
1050 "_k_fifo_area",
1051 "_k_lifo_area",
1052 "_k_stack_area",
1053 "_k_msgq_area",
1054 "_k_mbox_area",
1055 "_k_pipe_area",
1056 "net_if",
1057 "net_if_dev",
1058 "net_stack",
1059 "net_l2_data",
1060 "_k_queue_area",
1061 "_net_buf_pool_area",
1062 "app_datas",
1063 "kobject_data",
1064 "mmu_tables",
1065 "app_pad",
1066 "priv_stacks",
1067 "ccm_data",
1068 "usb_descriptor",
1069 "usb_data", "usb_bos_desc",
1070 'log_backends_sections',
1071 'log_dynamic_sections',
1072 'log_const_sections',
1073 "app_smem",
1074 'shell_root_cmds_sections',
1075 'log_const_sections',
1076 "font_entry_sections",
1077 "priv_stacks_noinit",
1078 "_TEXT_SECTION_NAME_2",
1079 "_GCOV_BSS_SECTION_NAME",
1080 "gcov",
1081 "nocache"
Anas Nashifd9882382019-12-12 09:58:28 -05001082 ]
Anas Nashifdb9592a2018-10-08 10:19:41 -04001083
Andrew Boie73b4ee62015-10-07 11:33:22 -07001084 # These get copied into RAM only on non-XIP
Anas Nashif83fc06a2019-06-22 11:04:10 -04001085 ro_sections = [
1086 "text",
1087 "ctors",
1088 "init_array",
1089 "reset",
1090 "object_access",
1091 "rodata",
1092 "devconfig",
1093 "net_l2",
1094 "vector",
1095 "sw_isr_table",
1096 "_settings_handlers_area",
1097 "_bt_channels_area",
1098 "_bt_br_channels_area",
1099 "_bt_services_area",
1100 "vectors",
1101 "net_socket_register",
1102 "net_ppp_proto"
Anas Nashifd9882382019-12-12 09:58:28 -05001103 ]
Andrew Boie73b4ee62015-10-07 11:33:22 -07001104
Andrew Boie52fef672016-11-29 12:21:59 -08001105 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -07001106 """Constructor
1107
Andrew Boiebbd670c2015-08-17 13:16:11 -07001108 @param filename Path to the output binary
1109 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -07001110 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001111 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -07001112 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -07001113 magic = f.read(4)
1114
Anas Nashifb4bdd662018-08-15 17:12:28 -05001115 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001116 if magic != b'\x7fELF':
Anas Nashifb4bdd662018-08-15 17:12:28 -05001117 raise SanityRuntimeError("%s is not an ELF binary" % filename)
1118 except Exception as e:
1119 print(str(e))
1120 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07001121
1122 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -05001123 # GREP can not be used as it returns an error if the symbol is not
1124 # found.
1125 is_xip_command = "nm " + filename + \
Anas Nashifd9882382019-12-12 09:58:28 -05001126 " | awk '/CONFIG_XIP/ { print $3 }'"
Anas Nashif3ba1d432017-12-05 15:28:44 -05001127 is_xip_output = subprocess.check_output(
1128 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
1129 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -05001130 try:
1131 if is_xip_output.endswith("no symbols"):
1132 raise SanityRuntimeError("%s has no symbol information" % filename)
1133 except Exception as e:
1134 print(str(e))
1135 sys.exit(2)
1136
Andrew Boie6acbe632015-07-17 12:03:52 -07001137 self.is_xip = (len(is_xip_output) != 0)
1138
Andrew Boiebbd670c2015-08-17 13:16:11 -07001139 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -07001140 self.sections = []
1141 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001142 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -08001143 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -07001144
1145 self._calculate_sizes()
1146
1147 def get_ram_size(self):
1148 """Get the amount of RAM the application will use up on the device
1149
1150 @return amount of RAM, in bytes
1151 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001152 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001153
1154 def get_rom_size(self):
1155 """Get the size of the data that this application uses on device's flash
1156
1157 @return amount of ROM, in bytes
1158 """
Andrew Boie73b4ee62015-10-07 11:33:22 -07001159 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07001160
1161 def unrecognized_sections(self):
1162 """Get a list of sections inside the binary that weren't recognized
1163
David B. Kinder29963c32017-06-16 12:32:42 -07001164 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -07001165 """
1166 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -07001167 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001169 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -07001170 return slist
1171
1172 def _calculate_sizes(self):
1173 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -07001174 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -05001175 objdump_output = subprocess.check_output(
1176 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -07001177
1178 for line in objdump_output:
1179 words = line.split()
1180
Anas Nashifd9882382019-12-12 09:58:28 -05001181 if not words: # Skip lines that are too short
Andrew Boie6acbe632015-07-17 12:03:52 -07001182 continue
1183
1184 index = words[0]
Anas Nashifd9882382019-12-12 09:58:28 -05001185 if not index[0].isdigit(): # Skip lines that do not start
1186 continue # with a digit
Andrew Boie6acbe632015-07-17 12:03:52 -07001187
Anas Nashifd9882382019-12-12 09:58:28 -05001188 name = words[1] # Skip lines with section names
1189 if name[0] == '.': # starting with '.'
Andrew Boie6acbe632015-07-17 12:03:52 -07001190 continue
1191
Andrew Boie73b4ee62015-10-07 11:33:22 -07001192 # TODO this doesn't actually reflect the size in flash or RAM as
1193 # it doesn't include linker-imposed padding between sections.
1194 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001195 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001196 if size == 0:
1197 continue
1198
Andrew Boie73b4ee62015-10-07 11:33:22 -07001199 load_addr = int(words[4], 16)
1200 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001201
1202 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001203 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001204 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001205 if name in SizeCalculator.alloc_sections:
1206 self.ram_size += size
1207 stype = "alloc"
1208 elif name in SizeCalculator.rw_sections:
1209 self.ram_size += size
1210 self.rom_size += size
1211 stype = "rw"
1212 elif name in SizeCalculator.ro_sections:
1213 self.rom_size += size
1214 if not self.is_xip:
1215 self.ram_size += size
1216 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001217 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001218 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001219 if name not in self.extra_sections:
1220 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001221
Anas Nashif3ba1d432017-12-05 15:28:44 -05001222 self.sections.append({"name": name, "load_addr": load_addr,
1223 "size": size, "virt_addr": virt_addr,
1224 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001225
1226
Andrew Boie6acbe632015-07-17 12:03:52 -07001227# "list" - List of strings
1228# "list:<type>" - List of <type>
1229# "set" - Set of unordered, unique strings
1230# "set:<type>" - Set of <type>
1231# "float" - Floating point
1232# "int" - Integer
1233# "bool" - Boolean
1234# "str" - String
1235
1236# XXX Be sure to update __doc__ if you change any of this!!
1237
Anas Nashif83fc06a2019-06-22 11:04:10 -04001238platform_valid_keys = {
Anas Nashifd9882382019-12-12 09:58:28 -05001239 "supported_toolchains": {"type": "list", "default": []},
1240 "env": {"type": "list", "default": []}
1241}
Andrew Boie6acbe632015-07-17 12:03:52 -07001242
Anas Nashif3ba1d432017-12-05 15:28:44 -05001243testcase_valid_keys = {"tags": {"type": "set", "required": False},
1244 "type": {"type": "str", "default": "integration"},
1245 "extra_args": {"type": "list"},
1246 "extra_configs": {"type": "list"},
1247 "build_only": {"type": "bool", "default": False},
1248 "build_on_all": {"type": "bool", "default": False},
1249 "skip": {"type": "bool", "default": False},
1250 "slow": {"type": "bool", "default": False},
1251 "timeout": {"type": "int", "default": 60},
1252 "min_ram": {"type": "int", "default": 8},
1253 "depends_on": {"type": "set"},
1254 "min_flash": {"type": "int", "default": 32},
1255 "arch_whitelist": {"type": "set"},
1256 "arch_exclude": {"type": "set"},
1257 "extra_sections": {"type": "list", "default": []},
1258 "platform_exclude": {"type": "set"},
1259 "platform_whitelist": {"type": "set"},
1260 "toolchain_exclude": {"type": "set"},
1261 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001262 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001263 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301264 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001265 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001266
Anas Nashif11ee5252019-12-04 12:59:10 -05001267
Andrew Boie6acbe632015-07-17 12:03:52 -07001268class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001269 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001270 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001271
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001272 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 """Instantiate a new SanityConfigParser object
1274
Anas Nashifa792a3d2017-04-04 18:47:49 -04001275 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001276 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001277 self.data = {}
1278 self.schema = schema
Andrew Boie6acbe632015-07-17 12:03:52 -07001279 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001280 self.tests = {}
1281 self.common = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001282
1283 def load(self):
1284 self.data = scl.yaml_load_verify(self.filename, self.schema)
1285
Anas Nashif255625b2017-12-05 15:08:26 -05001286 if 'tests' in self.data:
1287 self.tests = self.data['tests']
1288 if 'common' in self.data:
1289 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001290
1291 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001292 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001293 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001294 if typestr == "str":
1295 return v
1296
1297 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001298 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001299
1300 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001301 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001302
1303 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001304 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001305
Anas Nashif3ba1d432017-12-05 15:28:44 -05001306 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001307 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001308 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001309 vs = v.split()
1310 if len(typestr) > 4 and typestr[4] == ":":
1311 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1312 else:
1313 return vs
1314
1315 elif typestr.startswith("set"):
1316 vs = v.split()
1317 if len(typestr) > 3 and typestr[3] == ":":
Anas Nashif83fc06a2019-06-22 11:04:10 -04001318 return {self._cast_value(vsi, typestr[4:]) for vsi in vs}
Andrew Boie6acbe632015-07-17 12:03:52 -07001319 else:
1320 return set(vs)
1321
Anas Nashif576be982017-12-23 20:20:27 -05001322 elif typestr.startswith("map"):
1323 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001324 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001325 raise ConfigurationError(
1326 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001327
Anas Nashifb4754ed2017-12-05 17:27:58 -05001328 def get_test(self, name, valid_keys):
1329 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001330
Anas Nashifb4754ed2017-12-05 17:27:58 -05001331 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001332 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001333 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001334 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001335 here, it will generate an error. Each value in this dictionary
1336 is another dictionary containing metadata:
1337
1338 "default" - Default value if not given
1339 "type" - Data type to convert the text value to. Simple types
1340 supported are "str", "float", "int", "bool" which will get
1341 converted to respective Python data types. "set" and "list"
1342 may also be specified which will split the value by
1343 whitespace (but keep the elements as strings). finally,
1344 "list:<type>" and "set:<type>" may be given which will
1345 perform a type conversion after splitting the value up.
1346 "required" - If true, raise an error if not defined. If false
1347 and "default" isn't specified, a type conversion will be
1348 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001349 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001350 type conversion and default values filled in per valid_keys
1351 """
1352
1353 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001354 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001355 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001356
Anas Nashifb4754ed2017-12-05 17:27:58 -05001357 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001358 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001359 raise ConfigurationError(
1360 self.filename,
1361 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001362 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001363
Anas Nashiffa695d22017-10-04 16:14:27 -04001364 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001365 if isinstance(d[k], str):
Paul Sokolovsky8c6d2e22019-08-29 13:20:57 +03001366 # By default, we just concatenate string values of keys
1367 # which appear both in "common" and per-test sections,
1368 # but some keys are handled in adhoc way based on their
1369 # semantics.
1370 if k == "filter":
1371 d[k] = "(%s) and (%s)" % (d[k], v)
1372 else:
1373 d[k] += " " + v
Anas Nashiffa695d22017-10-04 16:14:27 -04001374 else:
1375 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001376
Andrew Boie08ce5a52016-02-22 13:28:10 -08001377 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001378 if k not in d:
1379 if "required" in kinfo:
1380 required = kinfo["required"]
1381 else:
1382 required = False
1383
1384 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001385 raise ConfigurationError(
1386 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001387 "missing required value for '%s' in test '%s'" %
1388 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001389 else:
1390 if "default" in kinfo:
1391 default = kinfo["default"]
1392 else:
1393 default = self._cast_value("", kinfo["type"])
1394 d[k] = default
1395 else:
1396 try:
1397 d[k] = self._cast_value(d[k], kinfo["type"])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001398 except ValueError:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001399 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001400 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
Anas Nashifd9882382019-12-12 09:58:28 -05001401 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001402
1403 return d
1404
1405
1406class Platform:
1407 """Class representing metadata for a particular platform
1408
Anas Nashifc7406082015-12-13 15:00:31 -05001409 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001410
Anas Nashif83fc06a2019-06-22 11:04:10 -04001411 platform_schema = scl.yaml_load(os.path.join(ZEPHYR_BASE,
Anas Nashifd9882382019-12-12 09:58:28 -05001412 "scripts", "sanity_chk", "platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001413
Anas Nashif83fc06a2019-06-22 11:04:10 -04001414 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001415 """Constructor.
1416
Andrew Boie6acbe632015-07-17 12:03:52 -07001417 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001418
1419 self.name = ""
1420 self.sanitycheck = True
1421 # if no RAM size is specified by the board, take a default of 128K
1422 self.ram = 128
1423
1424 self.ignore_tags = []
1425 self.default = False
1426 # if no flash size is specified by the board, take a default of 512K
1427 self.flash = 512
1428 self.supported = set()
1429
1430 self.arch = ""
1431 self.type = "na"
1432 self.simulation = "na"
1433 self.supported_toolchains = []
1434 self.env = []
1435 self.env_satisfied = True
1436 self.filter_data = dict()
1437
1438 def load(self, platform_file):
1439 scp = SanityConfigParser(platform_file, self.platform_schema)
1440 scp.load()
Anas Nashif255625b2017-12-05 15:08:26 -05001441 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001442
Anas Nashif255625b2017-12-05 15:08:26 -05001443 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001444 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001445 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001446 self.ram = data.get("ram", 128)
1447 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001448 self.ignore_tags = testing.get("ignore_tags", [])
1449 self.default = testing.get("default", False)
1450 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001451 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001452 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001453 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001454 for item in supp_feature.split(":"):
1455 self.supported.add(item)
1456
Anas Nashif255625b2017-12-05 15:08:26 -05001457 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001458 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001459 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001460 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001461 self.env = data.get("env", [])
1462 self.env_satisfied = True
1463 for env in self.env:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001464 if not os.environ.get(env, None):
Anas Nashif924a4e72018-10-18 12:25:55 -04001465 self.env_satisfied = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001466
Andrew Boie6acbe632015-07-17 12:03:52 -07001467 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001468 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001469
Anas Nashif11ee5252019-12-04 12:59:10 -05001470
Anas Nashif83fc06a2019-06-22 11:04:10 -04001471class TestCase(object):
Andrew Boie6acbe632015-07-17 12:03:52 -07001472 """Class representing a test application
1473 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001474
Anas Nashif83fc06a2019-06-22 11:04:10 -04001475 def __init__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001476 """TestCase constructor.
1477
Anas Nashif877d3ca2017-12-05 17:39:29 -05001478 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001479 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001480 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001481
Andrew Boie6acbe632015-07-17 12:03:52 -07001482 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001483 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001484 the test case is <workdir>/<name>.
1485
Marc Herbert1c8632c2019-04-15 17:58:45 -07001486 @param testcase_root os.path.abspath() of one of the --testcase-root
1487 @param workdir Sub-directory of testcase_root where the
1488 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001489 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001490 in the test case configuration file. For many test cases that just
1491 define one test, can be anything and is usually "test". This is
1492 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001493 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001494 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001495 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001496 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001497
Anas Nashif83fc06a2019-06-22 11:04:10 -04001498 self.id = ""
1499 self.source_dir = ""
1500 self.yamlfile = ""
Anas Nashifaae71d72018-04-21 22:26:48 -05001501 self.cases = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04001502 self.name = ""
Anas Nashifbd166f42017-09-02 12:32:08 -04001503
Anas Nashif83fc06a2019-06-22 11:04:10 -04001504 self.type = None
1505 self.tags = None
1506 self.extra_args = None
1507 self.extra_configs = None
1508 self.arch_whitelist = None
1509 self.arch_exclude = None
1510 self.skip = None
1511 self.platform_exclude = None
1512 self.platform_whitelist = None
1513 self.toolchain_exclude = None
1514 self.toolchain_whitelist = None
1515 self.tc_filter = None
1516 self.timeout = 60
1517 self.harness = ""
1518 self.harness_config = {}
1519 self.build_only = True
1520 self.build_on_all = False
1521 self.slow = False
1522 self.min_ram = None
1523 self.depends_on = None
1524 self.min_flash = None
1525 self.extra_sections = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001526
Anas Nashif83fc06a2019-06-22 11:04:10 -04001527 @staticmethod
1528 def get_unique(testcase_root, workdir, name):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001529
Marc Herbert1c8632c2019-04-15 17:58:45 -07001530 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001531 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001532 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001533 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001534 relative_tc_root = os.path.relpath(canonical_testcase_root,
Anas Nashif11ee5252019-12-04 12:59:10 -05001535 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001536 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001537 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001538
Marc Herbert1c8632c2019-04-15 17:58:45 -07001539 # workdir can be "."
1540 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001541 return unique
1542
Anas Nashif83fc06a2019-06-22 11:04:10 -04001543 @staticmethod
1544 def scan_file(inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001545 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001546 # do not match until end-of-line, otherwise we won't allow
1547 # stc_regex below to catch the ones that are declared in the same
1548 # line--as we only search starting the end of this match
1549 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001550 re.MULTILINE)
1551 stc_regex = re.compile(
Anas Nashifd9882382019-12-12 09:58:28 -05001552 br"^\s*" # empy space at the beginning is ok
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001553 # catch the case where it is declared in the same sentence, e.g:
1554 #
1555 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1556 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1557 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
Anas Nashif9091a012019-11-24 09:22:22 -05001558 br"ztest_(?:1cpu_)?(?:user_)?unit_test(?:_setup_teardown)?"
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001559 # Consume the argument that becomes the extra testcse
1560 br"\(\s*"
1561 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1562 # _setup_teardown() variant has two extra arguments that we ignore
1563 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1564 br"\s*\)",
1565 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001566 re.MULTILINE)
1567 suite_run_regex = re.compile(
1568 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1569 re.MULTILINE)
1570 achtung_regex = re.compile(
1571 br"(#ifdef|#endif)",
1572 re.MULTILINE)
1573 warnings = None
1574
1575 with open(inf_name) as inf:
Anas Nashif19d67e42019-11-21 11:33:12 -05001576 if os.name == 'nt':
Anas Nashifd9882382019-12-12 09:58:28 -05001577 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'access': mmap.ACCESS_READ}
Anas Nashif19d67e42019-11-21 11:33:12 -05001578 else:
Anas Nashifd9882382019-12-12 09:58:28 -05001579 mmap_args = {'fileno': inf.fileno(), 'length': 0, 'flags': mmap.MAP_PRIVATE, 'prot': mmap.PROT_READ,
1580 'offset': 0}
Anas Nashif19d67e42019-11-21 11:33:12 -05001581
1582 with contextlib.closing(mmap.mmap(**mmap_args)) as main_c:
Ulf Magnusson5c2e8142019-10-29 11:50:26 +01001583 # contextlib makes pylint think main_c isn't subscriptable
1584 # pylint: disable=unsubscriptable-object
1585
Anas Nashifaae71d72018-04-21 22:26:48 -05001586 suite_regex_match = suite_regex.search(main_c)
1587 if not suite_regex_match:
1588 # can't find ztest_test_suite, maybe a client, because
1589 # it includes ztest.h
1590 return None, None
1591
1592 suite_run_match = suite_run_regex.search(main_c)
1593 if not suite_run_match:
1594 raise ValueError("can't find ztest_run_test_suite")
1595
1596 achtung_matches = re.findall(
1597 achtung_regex,
1598 main_c[suite_regex_match.end():suite_run_match.start()])
1599 if achtung_matches:
1600 warnings = "found invalid %s in ztest_test_suite()" \
Anas Nashif83fc06a2019-06-22 11:04:10 -04001601 % ", ".join({match.decode() for match in achtung_matches})
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001602 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001603 stc_regex,
1604 main_c[suite_regex_match.end():suite_run_match.start()])
Anas Nashifd9882382019-12-12 09:58:28 -05001605 matches = [match.decode().replace("test_", "") for match in _matches]
Anas Nashifaae71d72018-04-21 22:26:48 -05001606 return matches, warnings
1607
1608 def scan_path(self, path):
1609 subcases = []
1610 for filename in glob.glob(os.path.join(path, "src", "*.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 Nashifaae71d72018-04-21 22:26:48 -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 Nashif434995c2019-12-01 13:55:11 -05001619 for filename in glob.glob(os.path.join(path, "*.c")):
1620 try:
1621 _subcases, warnings = self.scan_file(filename)
1622 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05001623 logger.error("%s: %s" % (filename, warnings))
Anas Nashif434995c2019-12-01 13:55:11 -05001624 if _subcases:
1625 subcases += _subcases
1626 except ValueError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05001627 logger.error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001628 return subcases
1629
Anas Nashif83fc06a2019-06-22 11:04:10 -04001630 def parse_subcases(self, test_path):
Anas Nashif9091a012019-11-24 09:22:22 -05001631 results = self.scan_path(test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001632 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001633 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001634 self.cases.append(name)
1635
Anas Nashiff16e92c2019-03-31 16:58:12 -04001636 if not results:
1637 self.cases.append(self.id)
1638
Anas Nashif75547e22018-02-24 08:32:14 -06001639 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001640 return self.name
1641
1642
Andrew Boie6acbe632015-07-17 12:03:52 -07001643class TestInstance:
1644 """Class representing the execution of a particular TestCase on a platform
1645
1646 @param test The TestCase object we want to build/execute
1647 @param platform Platform object that we want to build and run against
1648 @param base_outdir Base directory for all test results. The actual
1649 out directory used is <outdir>/<platform>/<test case name>
1650 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001651
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001652 def __init__(self, testcase, platform, outdir):
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001653
Anas Nashif83fc06a2019-06-22 11:04:10 -04001654 self.testcase = testcase
1655 self.platform = platform
1656
1657 self.status = None
Anas Nashif813deb72019-12-20 10:42:59 -05001658 self.reason = "Unknown"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001659 self.metrics = dict()
1660 self.handler = None
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001661 self.outdir = outdir
Anas Nashif83fc06a2019-06-22 11:04:10 -04001662
1663 self.name = os.path.join(platform.name, testcase.name)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05001664 self.build_dir = os.path.join(outdir, platform.name, testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001665
Anas Nashif56656842019-12-10 12:26:00 -05001666 self.build_only = True
1667 self.run = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001668
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001669 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001670
Marc Herbert0f7255c2019-04-05 14:14:21 -07001671 def __lt__(self, other):
1672 return self.name < other.name
1673
Anas Nashif56656842019-12-10 12:26:00 -05001674 def check_build_or_run(self, build_only=False, enable_slow=False, device_testing=False, fixture=[]):
1675
Anas Nashif19d67e42019-11-21 11:33:12 -05001676 # right now we only support building on windows. running is still work
1677 # in progress.
Anas Nashif19d67e42019-11-21 11:33:12 -05001678 if os.name == 'nt':
Anas Nashif56656842019-12-10 12:26:00 -05001679 self.build_only = True
1680 self.run = False
1681 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001682
Anas Nashif56656842019-12-10 12:26:00 -05001683 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001684
1685 # we asked for build-only on the command line
Anas Nashif56656842019-12-10 12:26:00 -05001686 if build_only or self.testcase.build_only:
1687 self.build_only = True
1688 self.run = False
1689 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001690
1691 # Do not run slow tests:
Anas Nashif56656842019-12-10 12:26:00 -05001692 skip_slow = self.testcase.slow and not enable_slow
Anas Nashif83fc06a2019-06-22 11:04:10 -04001693 if skip_slow:
Anas Nashif56656842019-12-10 12:26:00 -05001694 self.build_only = True
1695 self.run = False
1696 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04001697
Anas Nashifd9882382019-12-12 09:58:28 -05001698 runnable = bool(self.testcase.type == "unit" or \
1699 self.platform.type == "native" or \
1700 self.platform.simulation in ["nsim", "renode", "qemu"] or \
1701 device_testing)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001702
1703 if self.platform.simulation == "nsim":
1704 if not find_executable("nsimdrv"):
1705 runnable = False
1706
1707 if self.platform.simulation == "renode":
1708 if not find_executable("renode"):
1709 runnable = False
1710
1711 # console harness allows us to run the test and capture data.
1712 if self.testcase.harness == 'console':
1713
1714 # if we have a fixture that is also being supplied on the
1715 # command-line, then we need to run the test, not just build it.
1716 if "fixture" in self.testcase.harness_config:
Anas Nashifd9882382019-12-12 09:58:28 -05001717 fixture_cfg = self.testcase.harness_config['fixture']
1718 if fixture_cfg in fixture:
Anas Nashif56656842019-12-10 12:26:00 -05001719 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001720 else:
Anas Nashif56656842019-12-10 12:26:00 -05001721 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001722 else:
Anas Nashif56656842019-12-10 12:26:00 -05001723 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001724 elif self.testcase.harness:
Anas Nashif56656842019-12-10 12:26:00 -05001725 _build_only = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04001726 else:
Anas Nashif56656842019-12-10 12:26:00 -05001727 _build_only = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04001728
Anas Nashif56656842019-12-10 12:26:00 -05001729 self.build_only = not (not _build_only and runnable)
1730 self.run = not self.build_only
1731 return
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001732
Anas Nashif56656842019-12-10 12:26:00 -05001733 def create_overlay(self, platform, enable_asan=False, enable_coverage=False, coverage_platform=[]):
Marc Herbertc7633de2019-07-06 15:52:31 -07001734 # Create this in a "sanitycheck/" subdirectory otherwise this
1735 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1736 # will silently give that second time precedence over any
1737 # --extra-args=CONFIG_*
Anas Nashif83fc06a2019-06-22 11:04:10 -04001738 subdir = os.path.join(self.build_dir, "sanitycheck")
Marc Herbertc7633de2019-07-06 15:52:31 -07001739 os.makedirs(subdir, exist_ok=True)
1740 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashif56656842019-12-10 12:26:00 -05001741
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001742 with open(file, "w") as f:
1743 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001744
Anas Nashif83fc06a2019-06-22 11:04:10 -04001745 if self.testcase.extra_configs:
1746 content = "\n".join(self.testcase.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001747
Anas Nashif56656842019-12-10 12:26:00 -05001748 if enable_coverage:
1749 if platform.name in coverage_platform:
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001750 content = content + "\nCONFIG_COVERAGE=y"
Anas Nashif471ffbe2020-01-30 08:44:10 -05001751 content = content + "\nCONFIG_COVERAGE_DUMP=y"
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001752
Anas Nashif56656842019-12-10 12:26:00 -05001753 if enable_asan:
Jan Van Winkel21212f32019-09-12 00:03:35 +02001754 if platform.type == "native":
1755 content = content + "\nCONFIG_ASAN=y"
1756
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001757 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001758
Andrew Boie6acbe632015-07-17 12:03:52 -07001759 def calculate_sizes(self):
1760 """Get the RAM/ROM sizes of a test case.
1761
1762 This can only be run after the instance has been executed by
1763 MakeGenerator, otherwise there won't be any binaries to measure.
1764
1765 @return A SizeCalculator object
1766 """
Anas Nashif83fc06a2019-06-22 11:04:10 -04001767 fns = glob.glob(os.path.join(self.build_dir, "zephyr", "*.elf"))
1768 fns.extend(glob.glob(os.path.join(self.build_dir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001769 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001770 if len(fns) != 1:
Andrew Boie5d4eb782015-10-02 10:04:56 -07001771 raise BuildError("Missing/multiple output ELF binary")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001772
1773 return SizeCalculator(fns[0], self.testcase.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001774
1775 def __repr__(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001776 return "<TestCase %s on %s>" % (self.testcase.name, self.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001777
1778
Anas Nashif83fc06a2019-06-22 11:04:10 -04001779class CMake():
Anas Nashif83fc06a2019-06-22 11:04:10 -04001780 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1781 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
1782
1783 def __init__(self, testcase, platform, source_dir, build_dir):
1784
1785 self.cwd = None
1786 self.capture_output = True
1787
1788 self.defconfig = {}
1789 self.cmake_cache = {}
Anas Nashif83fc06a2019-06-22 11:04:10 -04001790
1791 self.instance = None
1792 self.testcase = testcase
1793 self.platform = platform
1794 self.source_dir = source_dir
1795 self.build_dir = build_dir
1796 self.log = "build.log"
1797
1798 def parse_generated(self):
1799 self.defconfig = {}
1800 return {}
1801
1802 def run_build(self, args=[]):
1803
Anas Nashif7a361b82019-12-06 11:37:40 -05001804 logger.debug("Building %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001805
1806 cmake_args = []
1807 cmake_args.extend(args)
1808 cmake = shutil.which('cmake')
1809 cmd = [cmake] + cmake_args
1810 kwargs = dict()
1811
1812 if self.capture_output:
1813 kwargs['stdout'] = subprocess.PIPE
1814 # CMake sends the output of message() to stderr unless it's STATUS
1815 kwargs['stderr'] = subprocess.STDOUT
1816
1817 if self.cwd:
1818 kwargs['cwd'] = self.cwd
1819
1820 p = subprocess.Popen(cmd, **kwargs)
1821 out, _ = p.communicate()
1822
1823 results = {}
1824 if p.returncode == 0:
Anas Nashifd9882382019-12-12 09:58:28 -05001825 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001826
1827 self.instance.status = "passed"
Anas Nashif83fc06a2019-06-22 11:04:10 -04001828 results = {'msg': msg, "returncode": p.returncode, "instance": self.instance}
1829
1830 if out:
1831 log_msg = out.decode(sys.getdefaultencoding())
1832 with open(os.path.join(self.build_dir, self.log), "a") as log:
1833 log.write(log_msg)
1834
1835 else:
1836 return None
1837 else:
1838 # A real error occurred, raise an exception
1839 if out:
1840 log_msg = out.decode(sys.getdefaultencoding())
1841 with open(os.path.join(self.build_dir, self.log), "a") as log:
1842 log.write(log_msg)
1843
Anas Nashif83fc06a2019-06-22 11:04:10 -04001844 if log_msg:
Anas Nashif4522e102020-01-04 08:45:56 -05001845 res = re.findall("region `(FLASH|RAM|SRAM)' overflowed by", log_msg)
1846 if res:
1847 logger.debug("Test skipped due to {} Overflow".format(res[0]))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001848 self.instance.status = "skipped"
Anas Nashif4522e102020-01-04 08:45:56 -05001849 self.instance.reason = "{} overflow".format(res[0])
Anas Nashif83fc06a2019-06-22 11:04:10 -04001850 else:
1851 self.instance.status = "failed"
1852 self.instance.reason = "Build failure"
1853
1854 results = {
Anas Nashifd9882382019-12-12 09:58:28 -05001855 "returncode": p.returncode,
1856 "instance": self.instance,
1857 }
Anas Nashif83fc06a2019-06-22 11:04:10 -04001858
1859 return results
1860
1861 def run_cmake(self, args=[]):
1862
Anas Nashif11ee5252019-12-04 12:59:10 -05001863 ldflags = "-Wl,--fatal-warnings"
Anas Nashifd9882382019-12-12 09:58:28 -05001864 logger.debug("Running cmake on %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001865
Anas Nashif11ee5252019-12-04 12:59:10 -05001866 # fixme: add additional cflags based on options
Anas Nashifa5984ab2019-10-22 07:36:24 -07001867 cmake_args = [
Anas Nashifd9882382019-12-12 09:58:28 -05001868 '-B{}'.format(self.build_dir),
1869 '-S{}'.format(self.source_dir),
1870 '-DEXTRA_CFLAGS="-Werror ',
1871 '-DEXTRA_AFLAGS=-Wa,--fatal-warnings',
1872 '-DEXTRA_LDFLAGS="{}'.format(ldflags),
1873 '-G{}'.format(get_generator()[1])
1874 ]
Anas Nashif83fc06a2019-06-22 11:04:10 -04001875
Anas Nashifd91f9932019-11-30 10:15:23 -05001876 if options.cmake_only:
1877 cmake_args.append("-DCMAKE_EXPORT_COMPILE_COMMANDS=1")
1878
Anas Nashif83fc06a2019-06-22 11:04:10 -04001879 args = ["-D{}".format(a.replace('"', '')) for a in args]
1880 cmake_args.extend(args)
1881
1882 cmake_opts = ['-DBOARD={}'.format(self.platform.name)]
1883 cmake_args.extend(cmake_opts)
1884
Anas Nashifcd1f0172020-01-14 09:47:27 -05001885
1886 logger.debug("Calling cmake with arguments: {}".format(cmake_args))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001887 cmake = shutil.which('cmake')
1888 cmd = [cmake] + cmake_args
1889 kwargs = dict()
1890
1891 if self.capture_output:
1892 kwargs['stdout'] = subprocess.PIPE
1893 # CMake sends the output of message() to stderr unless it's STATUS
1894 kwargs['stderr'] = subprocess.STDOUT
1895
1896 if self.cwd:
1897 kwargs['cwd'] = self.cwd
1898
1899 p = subprocess.Popen(cmd, **kwargs)
1900 out, _ = p.communicate()
1901
1902 if p.returncode == 0:
1903 filter_results = self.parse_generated()
Anas Nashifd9882382019-12-12 09:58:28 -05001904 msg = "Finished building %s for %s" % (self.source_dir, self.platform.name)
Anas Nashif64c84b12020-01-03 10:12:54 -05001905 logger.debug(msg)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001906 results = {'msg': msg, 'filter': filter_results}
1907
1908 else:
1909 self.instance.status = "failed"
1910 self.instance.reason = "Cmake build failure"
Anas Nashif64c84b12020-01-03 10:12:54 -05001911 logger.error("Cmake build failure: %s for %s" % (self.source_dir, self.platform.name))
Anas Nashif83fc06a2019-06-22 11:04:10 -04001912 results = {"returncode": p.returncode}
1913
Anas Nashif83fc06a2019-06-22 11:04:10 -04001914 if out:
1915 with open(os.path.join(self.build_dir, self.log), "a") as log:
1916 log_msg = out.decode(sys.getdefaultencoding())
1917 log.write(log_msg)
1918
1919 return results
1920
1921
1922class FilterBuilder(CMake):
1923
1924 def __init__(self, testcase, platform, source_dir, build_dir):
1925 super().__init__(testcase, platform, source_dir, build_dir)
1926
1927 self.log = "config-sanitycheck.log"
1928
1929 def parse_generated(self):
1930
1931 if self.platform.name == "unit_testing":
1932 return {}
1933
Anas Nashif83fc06a2019-06-22 11:04:10 -04001934 cmake_cache_path = os.path.join(self.build_dir, "CMakeCache.txt")
Anas Nashif83fc06a2019-06-22 11:04:10 -04001935 defconfig_path = os.path.join(self.build_dir, "zephyr", ".config")
1936
1937 with open(defconfig_path, "r") as fp:
1938 defconfig = {}
1939 for line in fp.readlines():
1940 m = self.config_re.match(line)
1941 if not m:
1942 if line.strip() and not line.startswith("#"):
1943 sys.stderr.write("Unrecognized line %s\n" % line)
1944 continue
1945 defconfig[m.group(1)] = m.group(2).strip()
1946
1947 self.defconfig = defconfig
1948
1949 cmake_conf = {}
1950 try:
1951 cache = CMakeCache.from_file(cmake_cache_path)
1952 except FileNotFoundError:
1953 cache = {}
1954
1955 for k in iter(cache):
1956 cmake_conf[k.name] = k.value
1957
1958 self.cmake_cache = cmake_conf
1959
Anas Nashif83fc06a2019-06-22 11:04:10 -04001960 filter_data = {
1961 "ARCH": self.platform.arch,
1962 "PLATFORM": self.platform.name
1963 }
1964 filter_data.update(os.environ)
1965 filter_data.update(self.defconfig)
1966 filter_data.update(self.cmake_cache)
Anas Nashif83fc06a2019-06-22 11:04:10 -04001967
Anas Nashif556f3cb2019-11-05 15:36:15 -08001968 dts_path = os.path.join(self.build_dir, "zephyr", self.platform.name + ".dts.pre.tmp")
Kumar Galaae88e442019-11-06 04:55:24 -06001969 if self.testcase and self.testcase.tc_filter:
Anas Nashif83fc06a2019-06-22 11:04:10 -04001970 try:
Kumar Galaae88e442019-11-06 04:55:24 -06001971 if os.path.exists(dts_path):
1972 edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")])
1973 else:
1974 edt = None
Kumar Gala7733b942019-09-12 17:08:43 -05001975 res = expr_parser.parse(self.testcase.tc_filter, filter_data, edt)
1976
Anas Nashif83fc06a2019-06-22 11:04:10 -04001977 except (ValueError, SyntaxError) as se:
1978 sys.stderr.write(
1979 "Failed processing %s\n" % self.testcase.yamlfile)
1980 raise se
1981
1982 if not res:
1983 return {os.path.join(self.platform.name, self.testcase.name): True}
1984 else:
1985 return {os.path.join(self.platform.name, self.testcase.name): False}
1986 else:
1987 self.platform.filter_data = filter_data
1988 return filter_data
1989
1990
1991class ProjectBuilder(FilterBuilder):
1992
Anas Nashif56656842019-12-10 12:26:00 -05001993 def __init__(self, suite, instance, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04001994 super().__init__(instance.testcase, instance.platform, instance.testcase.source_dir, instance.build_dir)
1995
1996 self.log = "build.log"
1997 self.instance = instance
1998 self.suite = suite
1999
Anas Nashif56656842019-12-10 12:26:00 -05002000 self.lsan = kwargs.get('lsan', False)
2001 self.asan = kwargs.get('asan', False)
2002 self.valgrind = kwargs.get('valgrind', False)
2003 self.extra_args = kwargs.get('extra_args', [])
2004 self.device_testing = kwargs.get('device_testing', False)
2005 self.cmake_only = kwargs.get('cmake_only', False)
Anas Nashif1342d212020-01-16 12:23:00 -05002006 self.cleanup = kwargs.get('cleanup', False)
Anas Nashif56656842019-12-10 12:26:00 -05002007 self.coverage = kwargs.get('coverage', False)
Anas Nashife9eb0092019-12-10 16:31:22 -05002008 self.inline_logs = kwargs.get('inline_logs', False)
2009
Anas Nashifd9882382019-12-12 09:58:28 -05002010 @staticmethod
2011 def log_info(filename, inline_logs):
Andrew Boiebd137102020-01-02 18:56:17 -08002012 filename = os.path.abspath(os.path.realpath(filename))
Anas Nashife9eb0092019-12-10 16:31:22 -05002013 if inline_logs:
2014 logger.info("{:-^100}".format(filename))
2015
2016 try:
2017 with open(filename) as fp:
2018 data = fp.read()
2019 except Exception as e:
2020 data = "Unable to read log data (%s)\n" % (str(e))
2021
2022 logger.error(data)
2023
2024 logger.info("{:-^100}".format(filename))
2025 else:
Anas Nashif97445682019-12-16 09:36:40 -05002026 logger.error("see: " + Fore.YELLOW + filename + Fore.RESET)
Anas Nashife9eb0092019-12-10 16:31:22 -05002027
2028 def log_info_file(self, inline_logs):
2029 build_dir = self.instance.build_dir
2030 h_log = "{}/handler.log".format(build_dir)
2031 b_log = "{}/build.log".format(build_dir)
2032 v_log = "{}/valgrind.log".format(build_dir)
2033 d_log = "{}/device.log".format(build_dir)
2034
2035 if os.path.exists(v_log) and "Valgrind" in self.instance.reason:
2036 self.log_info("{}".format(v_log), inline_logs)
Anas Nashif17d066b2019-12-17 14:37:16 -05002037 elif os.path.exists(h_log) and os.path.getsize(h_log) > 0:
Anas Nashife9eb0092019-12-10 16:31:22 -05002038 self.log_info("{}".format(h_log), inline_logs)
Anas Nashif52b66c62019-12-20 13:11:44 -05002039 elif os.path.exists(d_log) and os.path.getsize(d_log) > 0:
2040 self.log_info("{}".format(d_log), inline_logs)
Anas Nashife9eb0092019-12-10 16:31:22 -05002041 else:
2042 self.log_info("{}".format(b_log), inline_logs)
Anas Nashif56656842019-12-10 12:26:00 -05002043
Anas Nashif83fc06a2019-06-22 11:04:10 -04002044 def setup_handler(self):
2045
2046 instance = self.instance
2047 args = []
2048
2049 # FIXME: Needs simplification
2050 if instance.platform.simulation == "qemu":
2051 instance.handler = QEMUHandler(instance, "qemu")
2052 args.append("QEMU_PIPE=%s" % instance.handler.get_fifo())
2053 instance.handler.call_make_run = True
2054 elif instance.testcase.type == "unit":
2055 instance.handler = BinaryHandler(instance, "unit")
2056 instance.handler.binary = os.path.join(instance.build_dir, "testbinary")
2057 elif instance.platform.type == "native":
Anas Nashif6c0e1702019-12-05 15:24:52 -05002058 handler = BinaryHandler(instance, "native")
2059
Anas Nashif56656842019-12-10 12:26:00 -05002060 handler.asan = self.asan
2061 handler.valgrind = self.valgrind
2062 handler.lsan = self.lsan
2063 handler.coverage = self.coverage
Anas Nashif6c0e1702019-12-05 15:24:52 -05002064
2065 handler.binary = os.path.join(instance.build_dir, "zephyr", "zephyr.exe")
2066 instance.handler = handler
Anas Nashif83fc06a2019-06-22 11:04:10 -04002067 elif instance.platform.simulation == "nsim":
2068 if find_executable("nsimdrv"):
2069 instance.handler = BinaryHandler(instance, "nsim")
2070 instance.handler.call_make_run = True
2071 elif instance.platform.simulation == "renode":
2072 if find_executable("renode"):
2073 instance.handler = BinaryHandler(instance, "renode")
2074 instance.handler.pid_fn = os.path.join(instance.build_dir, "renode.pid")
2075 instance.handler.call_make_run = True
Anas Nashif56656842019-12-10 12:26:00 -05002076 elif self.device_testing:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002077 instance.handler = DeviceHandler(instance, "device")
2078
2079 if instance.handler:
2080 instance.handler.args = args
2081
2082 def process(self, message):
2083 op = message.get('op')
2084
2085 if not self.instance.handler:
2086 self.setup_handler()
2087
2088 # The build process, call cmake and build with configured generator
2089 if op == "cmake":
2090 results = self.cmake()
2091 if self.instance.status == "failed":
2092 pipeline.put({"op": "report", "test": self.instance})
Anas Nashif56656842019-12-10 12:26:00 -05002093 elif self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002094 pipeline.put({"op": "report", "test": self.instance})
2095 else:
2096 if self.instance.name in results['filter'] and results['filter'][self.instance.name]:
Anas Nashif7a361b82019-12-06 11:37:40 -05002097 logger.debug("filtering %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002098 self.instance.status = "skipped"
2099 self.instance.reason = "filter"
2100 pipeline.put({"op": "report", "test": self.instance})
2101 else:
2102 pipeline.put({"op": "build", "test": self.instance})
2103
Anas Nashif83fc06a2019-06-22 11:04:10 -04002104 elif op == "build":
Anas Nashifd9882382019-12-12 09:58:28 -05002105 logger.debug("build test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002106 results = self.build()
2107
2108 if results.get('returncode', 1) > 0:
2109 pipeline.put({"op": "report", "test": self.instance})
2110 else:
2111 if self.instance.run:
2112 pipeline.put({"op": "run", "test": self.instance})
2113 else:
2114 pipeline.put({"op": "report", "test": self.instance})
2115 # Run the generated binary using one of the supported handlers
2116 elif op == "run":
Anas Nashifd9882382019-12-12 09:58:28 -05002117 logger.debug("run test: %s" % self.instance.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002118 self.run()
2119 self.instance.status, _ = self.instance.handler.get_state()
Anas Nashif83fc06a2019-06-22 11:04:10 -04002120 pipeline.put({
2121 "op": "report",
2122 "test": self.instance,
2123 "state": "executed",
2124 "status": self.instance.status,
Stephanos Ioannidis93889002019-11-11 20:31:03 +09002125 "reason": self.instance.reason}
Anas Nashifd9882382019-12-12 09:58:28 -05002126 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002127
2128 # Report results and output progress to screen
2129 elif op == "report":
Anas Nashifc1ea4522019-10-11 07:32:45 -07002130 with report_lock:
2131 self.report_out()
2132
Anas Nashif323161d2020-02-07 11:01:17 -05002133 if self.cleanup and not self.coverage and self.instance.status == "passed":
Anas Nashif1342d212020-01-16 12:23:00 -05002134 pipeline.put({
2135 "op": "cleanup",
2136 "test": self.instance
2137 })
2138
2139 elif op == "cleanup":
2140 self.cleanup_artifacts()
2141
2142 def cleanup_artifacts(self):
2143 logger.debug("Cleaning up {}".format(self.instance.build_dir))
2144 whitelist = [
2145 'zephyr/.config',
2146 'handler.log',
2147 'build.log',
2148 'device.log',
2149 ]
2150 whitelist = [os.path.join(self.instance.build_dir, file) for file in whitelist]
2151
2152 for dirpath, dirnames, filenames in os.walk(self.instance.build_dir, topdown=False):
2153 for name in filenames:
2154 path = os.path.join(dirpath, name)
2155 if path not in whitelist:
2156 os.remove(path)
2157 # Remove empty directories and symbolic links to directories
2158 for dir in dirnames:
2159 path = os.path.join(dirpath, dir)
2160 if os.path.islink(path):
2161 os.remove(path)
2162 elif not os.listdir(path):
2163 os.rmdir(path)
2164
Anas Nashif83fc06a2019-06-22 11:04:10 -04002165 def report_out(self):
2166 total_tests_width = len(str(self.suite.total_tests))
2167 self.suite.total_done += 1
2168 instance = self.instance
2169
2170 if instance.status in ["failed", "timeout"]:
2171 self.suite.total_failed += 1
Anas Nashif97445682019-12-16 09:36:40 -05002172 if VERBOSE:
2173 status = Fore.RED + "FAILED " + Fore.RESET + instance.reason
Anas Nashif83fc06a2019-06-22 11:04:10 -04002174 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002175 print("")
2176 logger.error(
2177 "{:<25} {:<50} {}FAILED{}: {}".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002178 instance.platform.name,
2179 instance.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05002180 Fore.RED,
2181 Fore.RESET,
Anas Nashif7a361b82019-12-06 11:37:40 -05002182 instance.reason))
Anas Nashifc1ea4522019-10-11 07:32:45 -07002183 if not VERBOSE:
Anas Nashife9eb0092019-12-10 16:31:22 -05002184 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002185 elif instance.status == "skipped":
2186 self.suite.total_skipped += 1
Anas Nashif97445682019-12-16 09:36:40 -05002187 status = Fore.YELLOW + "SKIPPED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002188 else:
Anas Nashif97445682019-12-16 09:36:40 -05002189 status = Fore.GREEN + "PASSED" + Fore.RESET
Anas Nashif83fc06a2019-06-22 11:04:10 -04002190
Anas Nashif97445682019-12-16 09:36:40 -05002191 if VERBOSE:
Anas Nashif56656842019-12-10 12:26:00 -05002192 if self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002193 more_info = "cmake"
2194 elif instance.status == "skipped":
2195 more_info = instance.reason
2196 else:
2197 if instance.handler and instance.run:
2198 more_info = instance.handler.type_str
2199 htime = instance.handler.duration
2200 if htime:
2201 more_info += " {:.3f}s".format(htime)
2202 else:
2203 more_info = "build"
2204
Anas Nashif7a361b82019-12-06 11:37:40 -05002205 logger.info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002206 self.suite.total_done, total_tests_width, self.suite.total_tests, instance.platform.name,
2207 instance.testcase.name, status, more_info))
2208
2209 if instance.status in ["failed", "timeout"]:
Anas Nashifd9882382019-12-12 09:58:28 -05002210 self.log_info_file(self.inline_logs)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002211 else:
Anas Nashif67f9ecb2019-12-09 10:46:19 -05002212 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 -05002213 Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002214 self.suite.total_done,
2215 self.suite.total_tests,
Anas Nashif97445682019-12-16 09:36:40 -05002216 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002217 int((float(self.suite.total_done) / self.suite.total_tests) * 100),
Anas Nashif97445682019-12-16 09:36:40 -05002218 Fore.YELLOW if self.suite.total_skipped > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002219 self.suite.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002220 Fore.RESET,
2221 Fore.RED if self.suite.total_failed > 0 else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002222 self.suite.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002223 Fore.RESET
Anas Nashifd9882382019-12-12 09:58:28 -05002224 )
2225 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002226 sys.stdout.flush()
2227
2228 def cmake(self):
2229
2230 instance = self.instance
2231 args = self.testcase.extra_args[:]
Anas Nashif56656842019-12-10 12:26:00 -05002232 args += self.extra_args
Anas Nashif83fc06a2019-06-22 11:04:10 -04002233
2234 if instance.handler:
2235 args += instance.handler.args
2236
2237 # merge overlay files into one variable
Marcin Niestrojbe0f5fe2019-12-05 16:20:43 +01002238 def extract_overlays(args):
2239 re_overlay = re.compile('OVERLAY_CONFIG=(.*)')
2240 other_args = []
2241 overlays = []
2242 for arg in args:
2243 match = re_overlay.search(arg)
2244 if match:
2245 overlays.append(match.group(1).strip('\'"'))
2246 else:
2247 other_args.append(arg)
2248
2249 args[:] = other_args
2250 return overlays
2251
2252 overlays = extract_overlays(args)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002253
Anas Nashif56656842019-12-10 12:26:00 -05002254 if (self.testcase.extra_configs or self.coverage or
2255 self.asan):
Marcin Niestrojbe0f5fe2019-12-05 16:20:43 +01002256 overlays.append(os.path.join(instance.build_dir,
2257 "sanitycheck", "testcase_extra.conf"))
2258
2259 if overlays:
2260 args.append("OVERLAY_CONFIG=\"%s\"" % (" ".join(overlays)))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002261
2262 results = self.run_cmake(args)
2263 return results
2264
2265 def build(self):
2266 results = self.run_build(['--build', self.build_dir])
2267 return results
2268
2269 def run(self):
2270
2271 instance = self.instance
2272
2273 if instance.handler.type_str == "device":
2274 instance.handler.suite = self.suite
2275
2276 instance.handler.handle()
2277
Anas Nashif83fc06a2019-06-22 11:04:10 -04002278 sys.stdout.flush()
2279
2280
2281pipeline = queue.LifoQueue()
2282
Anas Nashif11ee5252019-12-04 12:59:10 -05002283
Anas Nashif83fc06a2019-06-22 11:04:10 -04002284class BoundedExecutor(concurrent.futures.ThreadPoolExecutor):
2285 """BoundedExecutor behaves as a ThreadPoolExecutor which will block on
2286 calls to submit() once the limit given as "bound" work items are queued for
2287 execution.
2288 :param bound: Integer - the maximum number of items in the work queue
2289 :param max_workers: Integer - the size of the thread pool
2290 """
Anas Nashifd9882382019-12-12 09:58:28 -05002291
Anas Nashif83fc06a2019-06-22 11:04:10 -04002292 def __init__(self, bound, max_workers, **kwargs):
2293 super().__init__(max_workers)
Anas Nashif11ee5252019-12-04 12:59:10 -05002294 # self.executor = ThreadPoolExecutor(max_workers=max_workers)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002295 self.semaphore = BoundedSemaphore(bound + max_workers)
2296
2297 def submit(self, fn, *args, **kwargs):
2298 self.semaphore.acquire()
2299 try:
2300 future = super().submit(fn, *args, **kwargs)
Anas Nashif11ee5252019-12-04 12:59:10 -05002301 except Exception:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002302 self.semaphore.release()
2303 raise
2304 else:
2305 future.add_done_callback(lambda x: self.semaphore.release())
2306 return future
2307
Andrew Boie6acbe632015-07-17 12:03:52 -07002308
2309class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08002310 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002311 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07002312
Anas Nashif83fc06a2019-06-22 11:04:10 -04002313 tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03002314 os.path.join(ZEPHYR_BASE,
Anas Nashif83fc06a2019-06-22 11:04:10 -04002315 "scripts", "sanity_chk", "testcase-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07002316
Anas Nashif37f9dc52018-02-23 08:53:46 -06002317 def __init__(self, board_root_list, testcase_roots, outdir):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002318
2319 self.roots = testcase_roots
2320 if not isinstance(board_root_list, list):
Anas Nashif11ee5252019-12-04 12:59:10 -05002321 self.board_roots = [board_root_list]
Anas Nashif83fc06a2019-06-22 11:04:10 -04002322 else:
2323 self.board_roots = board_root_list
2324
Anas Nashif56656842019-12-10 12:26:00 -05002325 # Testsuite Options
2326 self.coverage_platform = []
2327 self.build_only = False
2328 self.cmake_only = False
Anas Nashif1342d212020-01-16 12:23:00 -05002329 self.cleanup = False
Anas Nashif56656842019-12-10 12:26:00 -05002330 self.enable_slow = False
2331 self.device_testing = False
2332 self.fixture = []
2333 self.enable_coverage = False
2334 self.enable_lsan = False
2335 self.enable_asan = False
2336 self.enable_valgrind = False
2337 self.extra_args = []
Anas Nashife9eb0092019-12-10 16:31:22 -05002338 self.inline_logs = False
Anas Nashifc5ee3952019-12-10 16:38:45 -05002339 self.enable_sizes_report = False
Anas Nashif56656842019-12-10 12:26:00 -05002340
Andrew Boie6acbe632015-07-17 12:03:52 -07002341 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07002342 self.testcases = {}
2343 self.platforms = []
Anas Nashif5f908822019-11-25 08:19:25 -05002344 self.selected_platforms = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04002345 self.default_platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07002346 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002347 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05002348 self.load_errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04002349 self.instances = dict()
Andrew Boie6acbe632015-07-17 12:03:52 -07002350
Anas Nashif11ee5252019-12-04 12:59:10 -05002351 self.total_tests = 0 # number of test instances
2352 self.total_cases = 0 # number of test cases
2353 self.total_done = 0 # tests completed
Anas Nashif83fc06a2019-06-22 11:04:10 -04002354 self.total_failed = 0
2355 self.total_skipped = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07002356
Anas Nashif83fc06a2019-06-22 11:04:10 -04002357 self.total_platforms = 0
2358 self.start_time = 0
2359 self.duration = 0
2360 self.warnings = 0
2361 self.cv = threading.Condition()
Anas Nashif61e21632018-04-08 13:30:16 -05002362
Anas Nashif83fc06a2019-06-22 11:04:10 -04002363 # hardcoded for now
2364 self.connected_hardware = []
Andrew Boie3d348712016-04-08 11:52:13 -07002365
Anas Nashif56656842019-12-10 12:26:00 -05002366 def config(self):
2367 logger.info("coverage platform: {}".format(self.coverage_platform))
2368
Anas Nashiff16ed8e2019-12-09 16:22:27 -05002369 # Debug Functions
2370 @staticmethod
2371 def info(what):
2372 sys.stdout.write(what + "\n")
2373 sys.stdout.flush()
2374
Anas Nashif83fc06a2019-06-22 11:04:10 -04002375 def update(self):
2376 self.total_tests = len(self.instances)
2377 self.total_cases = len(self.testcases)
Anas Nashifbd166f42017-09-02 12:32:08 -04002378
Andrew Boie6acbe632015-07-17 12:03:52 -07002379 def compare_metrics(self, filename):
2380 # name, datatype, lower results better
2381 interesting_metrics = [("ram_size", int, True),
2382 ("rom_size", int, True)]
2383
Andrew Boie6acbe632015-07-17 12:03:52 -07002384 if not os.path.exists(filename):
Anas Nashif7a361b82019-12-06 11:37:40 -05002385 logger.info("Cannot compare metrics, %s not found" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -07002386 return []
2387
2388 results = []
2389 saved_metrics = {}
2390 with open(filename) as fp:
2391 cr = csv.DictReader(fp)
2392 for row in cr:
2393 d = {}
2394 for m, _, _ in interesting_metrics:
2395 d[m] = row[m]
2396 saved_metrics[(row["test"], row["platform"])] = d
2397
Anas Nashif83fc06a2019-06-22 11:04:10 -04002398 for instance in self.instances.values():
2399 mkey = (instance.testcase.name, instance.platform.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07002400 if mkey not in saved_metrics:
2401 continue
2402 sm = saved_metrics[mkey]
2403 for metric, mtype, lower_better in interesting_metrics:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002404 if metric not in instance.metrics:
Andrew Boie6acbe632015-07-17 12:03:52 -07002405 continue
2406 if sm[metric] == "":
2407 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002408 delta = instance.metrics.get(metric, 0) - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002409 if delta == 0:
2410 continue
Anas Nashifd9882382019-12-12 09:58:28 -05002411 results.append((instance, metric, instance.metrics.get(metric, 0), delta,
Andrew Boieea7928f2015-08-14 14:27:38 -07002412 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002413 return results
2414
Anas Nashif83fc06a2019-06-22 11:04:10 -04002415 def misc_reports(self, report, show_footprint, all_deltas,
2416 footprint_threshold, last_metrics):
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002417
Anas Nashif83fc06a2019-06-22 11:04:10 -04002418 if not report:
2419 return
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002420
Anas Nashif83fc06a2019-06-22 11:04:10 -04002421 deltas = self.compare_metrics(report)
2422 warnings = 0
2423 if deltas and show_footprint:
2424 for i, metric, value, delta, lower_better in deltas:
2425 if not all_deltas and ((delta < 0 and lower_better) or
Anas Nashifd9882382019-12-12 09:58:28 -05002426 (delta > 0 and not lower_better)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002427 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002428
Anas Nashif83fc06a2019-06-22 11:04:10 -04002429 percentage = (float(delta) / float(value - delta))
2430 if not all_deltas and (percentage <
Anas Nashifd9882382019-12-12 09:58:28 -05002431 (footprint_threshold / 100.0)):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002432 continue
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002433
Anas Nashifd9882382019-12-12 09:58:28 -05002434 logger.info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Anas Nashif97445682019-12-16 09:36:40 -05002435 i.platform.name, i.testcase.name, Fore.YELLOW,
2436 "INFO" if all_deltas else "WARNING", Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002437 metric, delta, value, percentage))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002438 warnings += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002439
Anas Nashif83fc06a2019-06-22 11:04:10 -04002440 if warnings:
Anas Nashif7a361b82019-12-06 11:37:40 -05002441 logger.warning("Deltas based on metrics from last %s" %
Anas Nashifd9882382019-12-12 09:58:28 -05002442 ("release" if not last_metrics else "run"))
Anas Nashif61e21632018-04-08 13:30:16 -05002443
Anas Nashif83fc06a2019-06-22 11:04:10 -04002444 def summary(self, unrecognized_sections):
2445 failed = 0
2446 for instance in self.instances.values():
2447 if instance.status == "failed":
2448 failed += 1
2449 elif instance.metrics.get("unrecognized") and not unrecognized_sections:
Anas Nashif7a361b82019-12-06 11:37:40 -05002450 logger.error("%sFAILED%s: %s has unrecognized binary sections: %s" %
Anas Nashif97445682019-12-16 09:36:40 -05002451 (Fore.RED, Fore.RESET, instance.name,
Anas Nashifd9882382019-12-12 09:58:28 -05002452 str(instance.metrics.get("unrecognized", []))))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002453 failed += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002454
Anas Nashif83fc06a2019-06-22 11:04:10 -04002455 if self.total_tests and self.total_tests != self.total_skipped:
Anas Nashifd9882382019-12-12 09:58:28 -05002456 pass_rate = (float(self.total_tests - self.total_failed - self.total_skipped) / float(
2457 self.total_tests - self.total_skipped))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002458 else:
2459 pass_rate = 0
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002460
Anas Nashifd9882382019-12-12 09:58:28 -05002461 logger.info(
2462 "{}{} of {}{} tests passed ({:.2%}), {}{}{} failed, {} skipped with {}{}{} warnings in {:.2f} seconds".format(
Anas Nashif97445682019-12-16 09:36:40 -05002463 Fore.RED if failed else Fore.GREEN,
Anas Nashifd9882382019-12-12 09:58:28 -05002464 self.total_tests - self.total_failed - self.total_skipped,
Andrew Boie75bd9b62020-01-14 15:04:06 -08002465 self.total_tests - self.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002466 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002467 pass_rate,
Anas Nashif97445682019-12-16 09:36:40 -05002468 Fore.RED if self.total_failed else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002469 self.total_failed,
Anas Nashif97445682019-12-16 09:36:40 -05002470 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002471 self.total_skipped,
Anas Nashif97445682019-12-16 09:36:40 -05002472 Fore.YELLOW if self.warnings else Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002473 self.warnings,
Anas Nashif97445682019-12-16 09:36:40 -05002474 Fore.RESET,
Anas Nashifd9882382019-12-12 09:58:28 -05002475 self.duration))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002476
Anas Nashif83fc06a2019-06-22 11:04:10 -04002477 self.total_platforms = len(self.platforms)
2478 if self.platforms:
Anas Nashif7a361b82019-12-06 11:37:40 -05002479 logger.info("In total {} test cases were executed on {} out of total {} platforms ({:02.2f}%)".format(
Anas Nashif83fc06a2019-06-22 11:04:10 -04002480 self.total_cases,
Anas Nashif5f908822019-11-25 08:19:25 -05002481 len(self.selected_platforms),
Anas Nashif83fc06a2019-06-22 11:04:10 -04002482 self.total_platforms,
Anas Nashif5f908822019-11-25 08:19:25 -05002483 (100 * len(self.selected_platforms) / len(self.platforms))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002484 ))
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002485
Anas Nashif56656842019-12-10 12:26:00 -05002486 def save_reports(self, name, report_dir, no_update, release, only_failed):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002487 if not self.instances:
2488 return
Anas Nashif61e21632018-04-08 13:30:16 -05002489
Anas Nashif56656842019-12-10 12:26:00 -05002490 if name:
2491 report_name = name
2492 else:
2493 report_name = "sanitycheck"
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002494
Anas Nashif56656842019-12-10 12:26:00 -05002495 if report_dir:
2496 os.makedirs(report_dir, exist_ok=True)
2497 filename = os.path.join(report_dir, report_name)
2498 outdir = report_dir
Anas Nashif83fc06a2019-06-22 11:04:10 -04002499 else:
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002500 filename = os.path.join(self.outdir, report_name)
2501 outdir = self.outdir
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002502
Anas Nashif56656842019-12-10 12:26:00 -05002503 if not no_update:
2504 self.xunit_report(filename + ".xml", only_failed)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002505 self.csv_report(filename + ".csv")
2506 self.target_report(outdir)
2507 if self.discards:
2508 self.discard_report(filename + "_discard.csv")
2509
Anas Nashif56656842019-12-10 12:26:00 -05002510 if release:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002511 self.csv_report(RELEASE_DATA)
2512
Anas Nashif83fc06a2019-06-22 11:04:10 -04002513 def add_configurations(self):
2514
2515 for board_root in self.board_roots:
2516 board_root = os.path.abspath(board_root)
2517
Anas Nashif7a361b82019-12-06 11:37:40 -05002518 logger.debug("Reading platform configuration files under %s..." %
Anas Nashifd9882382019-12-12 09:58:28 -05002519 board_root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002520
2521 for file in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
Anas Nashif64c84b12020-01-03 10:12:54 -05002522 logger.debug("Found platform configuration " + file)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002523 try:
2524 platform = Platform()
2525 platform.load(file)
2526 if platform.sanitycheck:
2527 self.platforms.append(platform)
2528 if platform.default:
2529 self.default_platforms.append(platform.name)
2530
2531 except RuntimeError as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002532 logger.error("E: %s: can't load: %s" % (file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002533 self.load_errors += 1
2534
Anas Nashifeabaa7f2019-11-18 07:49:17 -08002535 def get_all_tests(self):
2536 tests = []
2537 for _, tc in self.testcases.items():
2538 for case in tc.cases:
2539 tests.append(case)
2540
2541 return tests
2542
Anas Nashif83fc06a2019-06-22 11:04:10 -04002543 @staticmethod
2544 def get_toolchain():
2545 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2546 os.environ.get("ZEPHYR_GCC_VARIANT", None)
2547
2548 if toolchain == "gccarmemb":
2549 # Remove this translation when gccarmemb is no longer supported.
2550 toolchain = "gnuarmemb"
2551
Anas Nashifb4bdd662018-08-15 17:12:28 -05002552 try:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002553 if not toolchain:
2554 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
Anas Nashifb4bdd662018-08-15 17:12:28 -05002555 except Exception as e:
2556 print(str(e))
2557 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002558
Anas Nashif83fc06a2019-06-22 11:04:10 -04002559 return toolchain
2560
Anas Nashif83fc06a2019-06-22 11:04:10 -04002561 def add_testcases(self):
2562 for root in self.roots:
2563 root = os.path.abspath(root)
2564
Anas Nashifd9882382019-12-12 09:58:28 -05002565 logger.debug("Reading test case configuration files under %s..." % root)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002566
2567 for dirpath, dirnames, filenames in os.walk(root, topdown=True):
Anas Nashif7a361b82019-12-06 11:37:40 -05002568 logger.debug("scanning %s" % dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002569 if 'sample.yaml' in filenames:
2570 filename = 'sample.yaml'
2571 elif 'testcase.yaml' in filenames:
2572 filename = 'testcase.yaml'
2573 else:
2574 continue
2575
Anas Nashif7a361b82019-12-06 11:37:40 -05002576 logger.debug("Found possible test case in " + dirpath)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002577
2578 dirnames[:] = []
2579 tc_path = os.path.join(dirpath, filename)
2580 self.add_testcase(tc_path, root)
2581
2582 def add_testcase(self, tc_data_file, root):
2583 try:
2584 parsed_data = SanityConfigParser(tc_data_file, self.tc_schema)
2585 parsed_data.load()
2586
2587 tc_path = os.path.dirname(tc_data_file)
2588 workdir = os.path.relpath(tc_path, root)
2589
2590 for name in parsed_data.tests.keys():
2591 tc = TestCase()
2592 tc.name = tc.get_unique(root, workdir, name)
2593
2594 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
2595
2596 tc.source_dir = tc_path
2597 tc.yamlfile = tc_data_file
2598
2599 tc.id = name
2600 tc.type = tc_dict["type"]
2601 tc.tags = tc_dict["tags"]
2602 tc.extra_args = tc_dict["extra_args"]
2603 tc.extra_configs = tc_dict["extra_configs"]
2604 tc.arch_whitelist = tc_dict["arch_whitelist"]
2605 tc.arch_exclude = tc_dict["arch_exclude"]
2606 tc.skip = tc_dict["skip"]
2607 tc.platform_exclude = tc_dict["platform_exclude"]
2608 tc.platform_whitelist = tc_dict["platform_whitelist"]
2609 tc.toolchain_exclude = tc_dict["toolchain_exclude"]
2610 tc.toolchain_whitelist = tc_dict["toolchain_whitelist"]
2611 tc.tc_filter = tc_dict["filter"]
2612 tc.timeout = tc_dict["timeout"]
2613 tc.harness = tc_dict["harness"]
2614 tc.harness_config = tc_dict["harness_config"]
2615 tc.build_only = tc_dict["build_only"]
2616 tc.build_on_all = tc_dict["build_on_all"]
2617 tc.slow = tc_dict["slow"]
2618 tc.min_ram = tc_dict["min_ram"]
2619 tc.depends_on = tc_dict["depends_on"]
2620 tc.min_flash = tc_dict["min_flash"]
2621 tc.extra_sections = tc_dict["extra_sections"]
2622
2623 tc.parse_subcases(tc_path)
2624
2625 if tc.name:
2626 self.testcases[tc.name] = tc
2627
2628 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002629 logger.error("%s: can't load (skipping): %s" % (tc_data_file, e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002630 self.load_errors += 1
2631 return False
2632
2633 return True
2634
Anas Nashif83fc06a2019-06-22 11:04:10 -04002635 def get_platform(self, name):
2636 selected_platform = None
2637 for platform in self.platforms:
2638 if platform.name == name:
2639 selected_platform = platform
2640 break
2641 return selected_platform
2642
2643 def get_last_failed(self):
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05002644 last_run = os.path.join(self.outdir, "sanitycheck.csv")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002645 try:
2646 if not os.path.exists(last_run):
Anas Nashifd9882382019-12-12 09:58:28 -05002647 raise SanityRuntimeError("Couldn't find last sanitycheck run.: %s" % last_run)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002648 except Exception as e:
2649 print(str(e))
2650 sys.exit(2)
2651
2652 total_tests = 0
2653 with open(last_run, "r") as fp:
2654 cr = csv.DictReader(fp)
2655 instance_list = []
2656 for row in cr:
2657 total_tests += 1
2658 if row["passed"] == "True":
2659 continue
2660 test = row["test"]
2661 platform = self.get_platform(row["platform"])
2662 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002663 instance.check_build_or_run(
2664 self.build_only,
2665 self.enable_slow,
2666 self.device_testing,
2667 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002668 )
Anas Nashif56656842019-12-10 12:26:00 -05002669 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002670 instance_list.append(instance)
2671 self.add_instances(instance_list)
2672
2673 tests_to_run = len(self.instances)
Anas Nashifd9882382019-12-12 09:58:28 -05002674 logger.info("%d tests passed already, retrying %d tests" % (total_tests - tests_to_run, tests_to_run))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002675
2676 def load_from_file(self, file):
2677 try:
2678 if not os.path.exists(file):
2679 raise SanityRuntimeError(
2680 "Couldn't find input file with list of tests.")
2681 except Exception as e:
2682 print(str(e))
2683 sys.exit(2)
2684
2685 with open(file, "r") as fp:
2686 cr = csv.DictReader(fp)
2687 instance_list = []
2688 for row in cr:
2689 if row["arch"] == "arch":
2690 continue
2691 test = row["test"]
2692 platform = self.get_platform(row["platform"])
2693 instance = TestInstance(self.testcases[test], platform, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002694 instance.check_build_or_run(
2695 self.build_only,
2696 self.enable_slow,
2697 self.device_testing,
2698 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002699 )
Anas Nashif56656842019-12-10 12:26:00 -05002700 instance.create_overlay(platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002701 instance_list.append(instance)
2702 self.add_instances(instance_list)
2703
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002704 def apply_filters(self, **kwargs):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002705
2706 toolchain = self.get_toolchain()
2707
2708 discards = {}
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002709 platform_filter = kwargs.get('platform')
Anas Nashif9fe1e2a2020-01-21 17:19:36 -05002710 exclude_platform = kwargs.get('exclude_platform')
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002711 testcase_filter = kwargs.get('run_individual_tests')
2712 arch_filter = kwargs.get('arch')
2713 tag_filter = kwargs.get('tag')
2714 exclude_tag = kwargs.get('exclude_tag')
2715 all_filter = kwargs.get('all')
2716 device_testing_filter = kwargs.get('device_testing')
2717 force_toolchain = kwargs.get('force_toolchain')
Anas Nashif83fc06a2019-06-22 11:04:10 -04002718
Anas Nashif7a361b82019-12-06 11:37:40 -05002719 logger.debug("platform filter: " + str(platform_filter))
2720 logger.debug(" arch_filter: " + str(arch_filter))
2721 logger.debug(" tag_filter: " + str(tag_filter))
2722 logger.debug(" exclude_tag: " + str(exclude_tag))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002723
2724 default_platforms = False
2725
2726 if platform_filter:
2727 platforms = list(filter(lambda p: p.name in platform_filter, self.platforms))
2728 else:
2729 platforms = self.platforms
2730
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002731 if all_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002732 logger.info("Selecting all possible platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002733 # When --all used, any --platform arguments ignored
2734 platform_filter = []
2735 elif not platform_filter:
Anas Nashif7a361b82019-12-06 11:37:40 -05002736 logger.info("Selecting default platforms per test case")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002737 default_platforms = True
2738
Anas Nashif7a361b82019-12-06 11:37:40 -05002739 logger.info("Building initial testcase list...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002740
2741 for tc_name, tc in self.testcases.items():
2742 # list of instances per testcase, aka configurations.
2743 instance_list = []
2744 for plat in platforms:
2745 instance = TestInstance(tc, plat, self.outdir)
Anas Nashif56656842019-12-10 12:26:00 -05002746 instance.check_build_or_run(
2747 self.build_only,
2748 self.enable_slow,
2749 self.device_testing,
2750 self.fixture
Anas Nashifd9882382019-12-12 09:58:28 -05002751 )
Anas Nashif9fe1e2a2020-01-21 17:19:36 -05002752 if plat.name in exclude_platform:
2753 discards[instance] = "Platform is excluded on command line."
2754 continue
Anas Nashif83fc06a2019-06-22 11:04:10 -04002755
2756 if (plat.arch == "unit") != (tc.type == "unit"):
2757 # Discard silently
2758 continue
2759
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002760 if device_testing_filter and instance.build_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002761 discards[instance] = "Not runnable on device"
2762 continue
2763
2764 if tc.skip:
2765 discards[instance] = "Skip filter"
2766 continue
2767
2768 if tc.build_on_all and not platform_filter:
2769 platform_filter = []
2770
2771 if tag_filter and not tc.tags.intersection(tag_filter):
2772 discards[instance] = "Command line testcase tag filter"
2773 continue
2774
2775 if exclude_tag and tc.tags.intersection(exclude_tag):
2776 discards[instance] = "Command line testcase exclude filter"
2777 continue
2778
2779 if testcase_filter and tc_name not in testcase_filter:
2780 discards[instance] = "Testcase name filter"
2781 continue
2782
2783 if arch_filter and plat.arch not in arch_filter:
2784 discards[instance] = "Command line testcase arch filter"
2785 continue
2786
2787 if tc.arch_whitelist and plat.arch not in tc.arch_whitelist:
2788 discards[instance] = "Not in test case arch whitelist"
2789 continue
2790
2791 if tc.arch_exclude and plat.arch in tc.arch_exclude:
2792 discards[instance] = "In test case arch exclude"
2793 continue
2794
2795 if tc.platform_exclude and plat.name in tc.platform_exclude:
2796 discards[instance] = "In test case platform exclude"
2797 continue
2798
2799 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2800 discards[instance] = "In test case toolchain exclude"
2801 continue
2802
2803 if platform_filter and plat.name not in platform_filter:
2804 discards[instance] = "Command line platform filter"
2805 continue
2806
2807 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2808 discards[instance] = "Not in testcase platform whitelist"
2809 continue
2810
2811 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2812 discards[instance] = "Not in testcase toolchain whitelist"
2813 continue
2814
2815 if not plat.env_satisfied:
2816 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2817 continue
2818
Anas Nashiff5e5ef02019-12-06 19:20:48 -05002819 if not force_toolchain \
Anas Nashifd9882382019-12-12 09:58:28 -05002820 and toolchain and (toolchain not in plat.supported_toolchains) \
2821 and tc.type != 'unit':
Anas Nashif83fc06a2019-06-22 11:04:10 -04002822 discards[instance] = "Not supported by the toolchain"
2823 continue
2824
2825 if plat.ram < tc.min_ram:
2826 discards[instance] = "Not enough RAM"
2827 continue
2828
2829 if tc.depends_on:
2830 dep_intersection = tc.depends_on.intersection(set(plat.supported))
2831 if dep_intersection != set(tc.depends_on):
2832 discards[instance] = "No hardware support"
2833 continue
2834
2835 if plat.flash < tc.min_flash:
2836 discards[instance] = "Not enough FLASH"
2837 continue
2838
2839 if set(plat.ignore_tags) & tc.tags:
2840 discards[instance] = "Excluded tags per platform"
2841 continue
2842
2843 # if nothing stopped us until now, it means this configuration
2844 # needs to be added.
2845 instance_list.append(instance)
2846
2847 # no configurations, so jump to next testcase
2848 if not instance_list:
2849 continue
2850
2851 # if sanitycheck was launched with no platform options at all, we
2852 # take all default platforms
2853 if default_platforms and not tc.build_on_all:
2854 if tc.platform_whitelist:
2855 a = set(self.default_platforms)
2856 b = set(tc.platform_whitelist)
2857 c = a.intersection(b)
2858 if c:
Anas Nashifd9882382019-12-12 09:58:28 -05002859 aa = list(filter(lambda tc: tc.platform.name in c, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002860 self.add_instances(aa)
2861 else:
2862 self.add_instances(instance_list[:1])
2863 else:
Anas Nashifd9882382019-12-12 09:58:28 -05002864 instances = list(filter(lambda tc: tc.platform.default, instance_list))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002865 self.add_instances(instances)
2866
2867 for instance in list(filter(lambda tc: not tc.platform.default, instance_list)):
2868 discards[instance] = "Not a default test platform"
2869
2870 else:
2871 self.add_instances(instance_list)
2872
2873 for _, case in self.instances.items():
Anas Nashif56656842019-12-10 12:26:00 -05002874 case.create_overlay(case.platform, self.enable_asan, self.enable_coverage, self.coverage_platform)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002875
2876 self.discards = discards
Anas Nashif5f908822019-11-25 08:19:25 -05002877 self.selected_platforms = set(p.platform.name for p in self.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04002878
2879 return discards
2880
2881 def add_instances(self, instance_list):
2882 for instance in instance_list:
2883 self.instances[instance.name] = instance
2884
Anas Nashif56656842019-12-10 12:26:00 -05002885 def add_tasks_to_queue(self, test_only=False):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002886 for instance in self.instances.values():
Anas Nashif56656842019-12-10 12:26:00 -05002887 if test_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002888 if instance.run:
2889 pipeline.put({"op": "run", "test": instance, "status": "built"})
2890 else:
2891 if instance.status not in ['passed', 'skipped']:
2892 instance.status = None
2893 pipeline.put({"op": "cmake", "test": instance})
2894
2895 return "DONE FEEDING"
2896
Anas Nashifc5ee3952019-12-10 16:38:45 -05002897 def execute(self):
Anas Nashif83fc06a2019-06-22 11:04:10 -04002898 def calc_one_elf_size(instance):
2899 if instance.status not in ["failed", "skipped"]:
2900 if instance.platform.type != "native":
2901 size_calc = instance.calculate_sizes()
2902 instance.metrics["ram_size"] = size_calc.get_ram_size()
2903 instance.metrics["rom_size"] = size_calc.get_rom_size()
2904 instance.metrics["unrecognized"] = size_calc.unrecognized_sections()
2905 else:
2906 instance.metrics["ram_size"] = 0
2907 instance.metrics["rom_size"] = 0
2908 instance.metrics["unrecognized"] = []
2909
2910 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2911
Anas Nashif7a361b82019-12-06 11:37:40 -05002912 logger.info("Adding tasks to the queue...")
Anas Nashif83fc06a2019-06-22 11:04:10 -04002913 # We can use a with statement to ensure threads are cleaned up promptly
2914 with BoundedExecutor(bound=self.jobs, max_workers=self.jobs) as executor:
2915
2916 # start a future for a thread which sends work in through the queue
2917 future_to_test = {
Anas Nashifd9882382019-12-12 09:58:28 -05002918 executor.submit(self.add_tasks_to_queue, self.test_only): 'FEEDER DONE'}
Anas Nashif83fc06a2019-06-22 11:04:10 -04002919
2920 while future_to_test:
2921 # check for status of the futures which are currently working
Anas Nashif923ca142020-01-29 22:17:47 -05002922 done, pending = concurrent.futures.wait(future_to_test, timeout=1,
Anas Nashifd9882382019-12-12 09:58:28 -05002923 return_when=concurrent.futures.FIRST_COMPLETED)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002924
2925 # if there is incoming work, start a new future
2926 while not pipeline.empty():
2927 # fetch a url from the queue
2928 message = pipeline.get()
2929 test = message['test']
2930
Anas Nashif56656842019-12-10 12:26:00 -05002931 pb = ProjectBuilder(self,
Anas Nashifd9882382019-12-12 09:58:28 -05002932 test,
2933 lsan=self.enable_lsan,
2934 asan=self.enable_asan,
2935 coverage=self.enable_coverage,
2936 extra_args=self.extra_args,
2937 device_testing=self.device_testing,
2938 cmake_only=self.cmake_only,
Anas Nashif1342d212020-01-16 12:23:00 -05002939 cleanup=self.cleanup,
Anas Nashifd9882382019-12-12 09:58:28 -05002940 valgrind=self.enable_valgrind,
2941 inline_logs=self.inline_logs
2942 )
Anas Nashif83fc06a2019-06-22 11:04:10 -04002943 future_to_test[executor.submit(pb.process, message)] = test.name
2944
2945 # process any completed futures
2946 for future in done:
2947 test = future_to_test[future]
2948 try:
2949 data = future.result()
2950 except Exception as exc:
Anas Nashif64c84b12020-01-03 10:12:54 -05002951 logger.error('%r generated an exception: %s' % (test, exc))
Anas Nashif4f043862019-11-05 06:01:49 -08002952 sys.exit('%r generated an exception: %s' % (test, exc))
2953
Anas Nashif83fc06a2019-06-22 11:04:10 -04002954 else:
2955 if data:
Anas Nashif7a361b82019-12-06 11:37:40 -05002956 logger.debug(data)
Anas Nashif83fc06a2019-06-22 11:04:10 -04002957
2958 # remove the now completed future
2959 del future_to_test[future]
2960
Anas Nashif923ca142020-01-29 22:17:47 -05002961 for future in pending:
2962 test = future_to_test[future]
2963
2964 try:
2965 future.result(timeout=180)
2966 except concurrent.futures.TimeoutError:
2967 logger.warning("{} stuck?".format(test))
2968
2969
2970
2971
2972
Anas Nashifc5ee3952019-12-10 16:38:45 -05002973 if self.enable_size_report and not self.cmake_only:
Anas Nashif83fc06a2019-06-22 11:04:10 -04002974 # Parallelize size calculation
2975 executor = concurrent.futures.ThreadPoolExecutor(self.jobs)
2976 futures = [executor.submit(calc_one_elf_size, instance)
2977 for instance in self.instances.values()]
2978 concurrent.futures.wait(futures)
2979 else:
2980 for instance in self.instances.values():
2981 instance.metrics["ram_size"] = 0
2982 instance.metrics["rom_size"] = 0
2983 instance.metrics["handler_time"] = instance.handler.duration if instance.handler else 0
2984 instance.metrics["unrecognized"] = []
2985
Anas Nashif83fc06a2019-06-22 11:04:10 -04002986 def discard_report(self, filename):
2987
2988 try:
2989 if self.discards is None:
2990 raise SanityRuntimeError("apply_filters() hasn't been run!")
2991 except Exception as e:
Anas Nashif7a361b82019-12-06 11:37:40 -05002992 logger.error(str(e))
Anas Nashif83fc06a2019-06-22 11:04:10 -04002993 sys.exit(2)
2994
2995 with open(filename, "wt") as csvfile:
2996 fieldnames = ["test", "arch", "platform", "reason"]
2997 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2998 cw.writeheader()
2999 for instance, reason in sorted(self.discards.items()):
3000 rowdict = {"test": instance.testcase.name,
3001 "arch": instance.platform.arch,
3002 "platform": instance.platform.name,
3003 "reason": reason}
3004 cw.writerow(rowdict)
3005
Anas Nashif83fc06a2019-06-22 11:04:10 -04003006 def target_report(self, outdir):
3007 run = "Sanitycheck"
3008 eleTestsuite = None
3009
Anas Nashifd9882382019-12-12 09:58:28 -05003010 platforms = {inst.platform.name for _, inst in self.instances.items()}
Anas Nashif83fc06a2019-06-22 11:04:10 -04003011 for platform in platforms:
3012 errors = 0
3013 passes = 0
3014 fails = 0
3015 duration = 0
3016 skips = 0
3017 for _, instance in self.instances.items():
3018 if instance.platform.name != platform:
3019 continue
3020
3021 handler_time = instance.metrics.get('handler_time', 0)
3022 duration += handler_time
3023 for k in instance.results.keys():
3024 if instance.results[k] == 'PASS':
3025 passes += 1
3026 elif instance.results[k] == 'BLOCK':
3027 errors += 1
3028 elif instance.results[k] == 'SKIP':
3029 skips += 1
3030 else:
3031 fails += 1
3032
3033 eleTestsuites = ET.Element('testsuites')
3034 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashifd9882382019-12-12 09:58:28 -05003035 name=run, time="%f" % duration,
3036 tests="%d" % (errors + passes + fails),
3037 failures="%d" % fails,
3038 errors="%d" % errors, skipped="%d" % skips)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003039
3040 handler_time = 0
3041
3042 # print out test results
3043 for _, instance in self.instances.items():
3044 if instance.platform.name != platform:
3045 continue
3046 handler_time = instance.metrics.get('handler_time', 0)
3047 for k in instance.results.keys():
3048 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05003049 eleTestsuite, 'testcase',
3050 classname="%s:%s" % (instance.platform.name, os.path.basename(instance.testcase.name)),
3051 name="%s" % (k), time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003052 if instance.results[k] in ['FAIL', 'BLOCK']:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003053 if instance.results[k] == 'FAIL':
3054 el = ET.SubElement(
3055 eleTestcase,
3056 'failure',
3057 type="failure",
3058 message="failed")
Flavio Ceolin9dbb50b2020-01-22 10:05:59 -08003059 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003060 el = ET.SubElement(
3061 eleTestcase,
3062 'error',
3063 type="failure",
3064 message="failed")
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003065 p = os.path.join(self.outdir, instance.platform.name, instance.testcase.name)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003066 log_file = os.path.join(p, "handler.log")
3067
3068 if os.path.exists(log_file):
3069 with open(log_file, "rb") as f:
3070 log = f.read().decode("utf-8")
3071 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3072 el.text = filtered_string
3073
3074 elif instance.results[k] == 'SKIP':
3075 el = ET.SubElement(
3076 eleTestcase,
3077 'skipped',
3078 type="skipped",
3079 message="Skipped")
3080
Anas Nashif83fc06a2019-06-22 11:04:10 -04003081 result = ET.tostring(eleTestsuites)
3082 with open(os.path.join(outdir, platform + ".xml"), 'wb') as f:
3083 f.write(result)
3084
Anas Nashif56656842019-12-10 12:26:00 -05003085 def xunit_report(self, filename, append=False):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003086 fails = 0
3087 passes = 0
3088 errors = 0
Anas Nashif83fc06a2019-06-22 11:04:10 -04003089 skips = 0
3090 duration = 0
Anas Nashifb3311ed2017-04-13 14:44:48 -04003091
Anas Nashif83fc06a2019-06-22 11:04:10 -04003092 for instance in self.instances.values():
3093 handler_time = instance.metrics.get('handler_time', 0)
3094 duration += handler_time
Anas Nashife3378822020-01-22 00:31:25 -05003095 if instance.status in ["failed", "timeout"]:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003096 if instance.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003097 errors += 1
3098 else:
3099 fails += 1
Anas Nashif83fc06a2019-06-22 11:04:10 -04003100 elif instance.status == 'skipped':
3101 skips += 1
Anas Nashifb3311ed2017-04-13 14:44:48 -04003102 else:
3103 passes += 1
3104
3105 run = "Sanitycheck"
3106 eleTestsuite = None
Anas Nashifb3311ed2017-04-13 14:44:48 -04003107
Anas Nashif83fc06a2019-06-22 11:04:10 -04003108 # When we re-run the tests, we re-use the results and update only with
3109 # the newly run tests.
Anas Nashif0605fa32017-05-07 08:51:02 -04003110 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04003111 tree = ET.parse(filename)
3112 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05003113 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04003114 else:
3115 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05003116 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
Anas Nashif83fc06a2019-06-22 11:04:10 -04003117 name=run, time="%f" % duration,
3118 tests="%d" % (errors + passes + fails + skips),
Anas Nashif3ba1d432017-12-05 15:28:44 -05003119 failures="%d" % fails,
Anas Nashifd9882382019-12-12 09:58:28 -05003120 errors="%d" % (errors), skip="%s" % (skips))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003121
Anas Nashif83fc06a2019-06-22 11:04:10 -04003122 for instance in self.instances.values():
Anas Nashifb3311ed2017-04-13 14:44:48 -04003123
Anas Nashif83fc06a2019-06-22 11:04:10 -04003124 # remove testcases that are a re-run
Anas Nashifb3311ed2017-04-13 14:44:48 -04003125 if append:
3126 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003127 if tc.get('classname') == "%s:%s" % (
Anas Nashif83fc06a2019-06-22 11:04:10 -04003128 instance.platform.name, instance.testcase.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04003129 eleTestsuite.remove(tc)
3130
Anas Nashif83fc06a2019-06-22 11:04:10 -04003131 handler_time = 0
3132 if instance.status != "failed" and instance.handler:
3133 handler_time = instance.metrics.get("handler_time", 0)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003134
Anas Nashifd9882382019-12-12 09:58:28 -05003135
Anas Nashif3ba1d432017-12-05 15:28:44 -05003136 eleTestcase = ET.SubElement(
Anas Nashifd9882382019-12-12 09:58:28 -05003137 eleTestsuite,
3138 'testcase',
3139 classname="%s:%s" % (instance.platform.name, instance.testcase.name),
3140 name="%s" % (instance.testcase.name),
3141 time="%f" % handler_time)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003142
Anas Nashife3378822020-01-22 00:31:25 -05003143 if instance.status in ["failed", "timeout"]:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003144 failure = ET.SubElement(
3145 eleTestcase,
3146 'failure',
3147 type="failure",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003148 message=instance.reason)
Anas Nashif9e7a2cd2019-12-05 13:07:27 -05003149 p = ("%s/%s/%s" % (self.outdir, instance.platform.name, instance.testcase.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04003150 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05003151 hl = os.path.join(p, "handler.log")
3152 log_file = bl
Anas Nashif83fc06a2019-06-22 11:04:10 -04003153 if instance.reason != 'Build error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05003154 if os.path.exists(hl):
3155 log_file = hl
3156 else:
3157 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04003158
Anas Nashifc96c90a2019-02-05 07:38:32 -05003159 if os.path.exists(log_file):
3160 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05003161 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04003162 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
3163 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05003164 f.close()
Anas Nashif83fc06a2019-06-22 11:04:10 -04003165 elif instance.status == "skipped":
Anas Nashifd9882382019-12-12 09:58:28 -05003166 ET.SubElement(eleTestcase, 'skipped', type="skipped", message="Skipped")
Anas Nashifb3311ed2017-04-13 14:44:48 -04003167
3168 result = ET.tostring(eleTestsuites)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003169 with open(filename, 'wb') as report:
3170 report.write(result)
Anas Nashifb3311ed2017-04-13 14:44:48 -04003171
Anas Nashif83fc06a2019-06-22 11:04:10 -04003172 def csv_report(self, filename):
Andrew Boie08ce5a52016-02-22 13:28:10 -08003173 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07003174 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003175 "extra_args", "handler", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07003176 "rom_size"]
3177 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3178 cw.writeheader()
Anas Nashifdd65f7c2020-01-22 18:10:17 -05003179 for instance in self.instances.values():
Anas Nashif83fc06a2019-06-22 11:04:10 -04003180 rowdict = {"test": instance.testcase.name,
3181 "arch": instance.platform.arch,
3182 "platform": instance.platform.name,
3183 "extra_args": " ".join(instance.testcase.extra_args),
3184 "handler": instance.platform.simulation}
3185
3186 if instance.status in ["failed", "timeout"]:
Andrew Boie6acbe632015-07-17 12:03:52 -07003187 rowdict["passed"] = False
Anas Nashif83fc06a2019-06-22 11:04:10 -04003188 rowdict["status"] = instance.reason
Andrew Boie6acbe632015-07-17 12:03:52 -07003189 else:
3190 rowdict["passed"] = True
Anas Nashif83fc06a2019-06-22 11:04:10 -04003191 if instance.handler:
3192 rowdict["handler_time"] = instance.metrics.get("handler_time", 0)
3193 ram_size = instance.metrics.get("ram_size", 0)
3194 rom_size = instance.metrics.get("rom_size", 0)
3195 rowdict["ram_size"] = ram_size
3196 rowdict["rom_size"] = rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -07003197 cw.writerow(rowdict)
3198
Anas Nashif19ca7832019-11-18 08:16:21 -08003199 def get_testcase(self, identifier):
3200 results = []
3201 for _, tc in self.testcases.items():
3202 for case in tc.cases:
3203 if case == identifier:
3204 results.append(tc)
3205 return results
3206
3207
Andrew Boie6acbe632015-07-17 12:03:52 -07003208def parse_arguments():
Anas Nashif3ba1d432017-12-05 15:28:44 -05003209 parser = argparse.ArgumentParser(
3210 description=__doc__,
3211 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05003212 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07003213
Marc Herbert932a33a2019-03-12 11:37:53 -07003214 case_select = parser.add_argument_group("Test case selection",
3215 """
3216Artificially long but functional example:
3217 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07003218 --testcase-root tests/ztest/base \\
3219 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07003220 --test tests/ztest/base/testing.ztest.verbose_0 \\
3221 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
3222
3223 "kernel.fifo.poll" is one of the test section names in
3224 __/fifo_api/testcase.yaml
3225 """)
Marc Herbertedf17592019-03-08 12:39:11 -08003226
Anas Nashif07d54c02018-07-21 19:29:08 -05003227 parser.add_argument("--force-toolchain", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003228 help="Do not filter based on toolchain, use the set "
3229 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003230 parser.add_argument(
3231 "-p", "--platform", action="append",
3232 help="Platform filter for testing. This option may be used multiple "
Anas Nashifd9882382019-12-12 09:58:28 -05003233 "times. Testcases will only be built/run on the platforms "
3234 "specified. If this option is not used, then platforms marked "
3235 "as default in the platform metadata file will be chosen "
3236 "to build and test. ")
Anas Nashif9fe1e2a2020-01-21 17:19:36 -05003237
3238 parser.add_argument("-P", "--exclude-platform", action="append", default=[],
3239 help="""Exclude platforms and do not build or run any tests
3240 on those platforms. This option can be called multiple times.
3241 """
3242 )
3243
Anas Nashif3ba1d432017-12-05 15:28:44 -05003244 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003245 "-a", "--arch", action="append",
3246 help="Arch filter for testing. Takes precedence over --platform. "
Anas Nashifd9882382019-12-12 09:58:28 -05003247 "If unspecified, test all arches. Multiple invocations "
3248 "are treated as a logical 'or' relationship")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003249 parser.add_argument(
3250 "-t", "--tag", action="append",
3251 help="Specify tags to restrict which tests to run by tag value. "
Anas Nashifd9882382019-12-12 09:58:28 -05003252 "Default is to not do any tag filtering. Multiple invocations "
3253 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04003254 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003255 help="Specify tags of tests that should not run. "
Anas Nashifd9882382019-12-12 09:58:28 -05003256 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08003257 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003258 "-f",
3259 "--only-failed",
3260 action="store_true",
3261 help="Run only those tests that failed the previous sanity check "
Anas Nashifd9882382019-12-12 09:58:28 -05003262 "invocation.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003263
Anas Nashif3ba1d432017-12-05 15:28:44 -05003264 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003265 "--retry-failed", type=int, default=0,
3266 help="Retry failing tests again, up to the number of times specified.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003267
Marc Herbert0c465bb2019-03-11 17:28:36 -07003268 test_xor_subtest = case_select.add_mutually_exclusive_group()
3269
3270 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003271 "-s", "--test", action="append",
3272 help="Run only the specified test cases. These are named by "
Anas Nashifd9882382019-12-12 09:58:28 -05003273 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003274
Marc Herbert0c465bb2019-03-11 17:28:36 -07003275 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003276 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07003277 help="""Recursively find sub-test functions and run the entire
3278 test section where they were found, including all sibling test
3279 functions. Sub-tests are named by:
3280 section.name.in.testcase.yaml.function_name_without_test_prefix
3281 Example: kernel.fifo.poll.fifo_loop
3282 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003283
Anas Nashif3ba1d432017-12-05 15:28:44 -05003284 parser.add_argument(
3285 "-l", "--all", action="store_true",
3286 help="Build/test on all platforms. Any --platform arguments "
Anas Nashifd9882382019-12-12 09:58:28 -05003287 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003288
Anas Nashif3ba1d432017-12-05 15:28:44 -05003289 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003290 "-o", "--report-dir",
3291 help="""Output reports containing results of the test run into the
3292 specified directory.
3293 The output will be both in CSV and JUNIT format
3294 (sanitycheck.csv and sanitycheck.xml).
3295 """)
3296
Anas Nashif3ba1d432017-12-05 15:28:44 -05003297 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003298 "--report-name",
3299 help="""Create a report with a custom name.
3300 """)
3301
Anas Nashif83fc06a2019-06-22 11:04:10 -04003302 parser.add_argument("--report-excluded",
Anas Nashifd9882382019-12-12 09:58:28 -05003303 action="store_true",
3304 help="""List all tests that are never run based on current scope and
Anas Nashif83fc06a2019-06-22 11:04:10 -04003305 coverage. If you are looking for accurate results, run this with
3306 --all, but this will take a while...""")
3307
Daniel Leung7f850102016-04-08 11:07:32 -07003308 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003309 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07003310
Anas Nashif3ba1d432017-12-05 15:28:44 -05003311 parser.add_argument(
3312 "-B", "--subset",
3313 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
Anas Nashifd9882382019-12-12 09:58:28 -05003314 "3/5 means run the 3rd fifth of the total. "
3315 "This option is useful when running a large number of tests on "
3316 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05003317
3318 parser.add_argument(
3319 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06003320 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05003321
Anas Nashif3ba1d432017-12-05 15:28:44 -05003322 parser.add_argument(
3323 "-y", "--dry-run", action="store_true",
Anas Nashif12d8cce2019-11-20 03:47:27 -08003324 help="""Create the filtered list of test cases, but don't actually
3325 run them. Useful if you're just interested in the discard report
3326 generated for every run and saved in the specified output
3327 directory (sanitycheck_discard.csv).
3328 """)
Andrew Boie6acbe632015-07-17 12:03:52 -07003329
Anas Nashif75547e22018-02-24 08:32:14 -06003330 parser.add_argument("--list-tags", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003331 help="list all tags in selected tests")
Anas Nashif75547e22018-02-24 08:32:14 -06003332
Marc Herbertedf17592019-03-08 12:39:11 -08003333 case_select.add_argument("--list-tests", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003334 help="""List of all sub-test functions recursively found in
Marc Herbert932a33a2019-03-12 11:37:53 -07003335 all --testcase-root arguments. Note different sub-tests can share
3336 the same section name and come from different directories.
3337 The output is flattened and reports --sub-test names only,
3338 not their directories. For instance net.socket.getaddrinfo_ok
3339 and net.socket.fd_set belong to different directories.
3340 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003341
Anas Nashif434995c2019-12-01 13:55:11 -05003342 case_select.add_argument("--test-tree", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003343 help="""Output the testsuite in a tree form""")
Anas Nashif434995c2019-12-01 13:55:11 -05003344
Anas Nashif19ca7832019-11-18 08:16:21 -08003345 case_select.add_argument("--list-test-duplicates", action="store_true",
Anas Nashifd9882382019-12-12 09:58:28 -05003346 help="""List tests with duplicate identifiers.
Anas Nashif19ca7832019-11-18 08:16:21 -08003347 """)
3348
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003349 parser.add_argument("--export-tests", action="store",
Anas Nashifd9882382019-12-12 09:58:28 -05003350 metavar="FILENAME",
3351 help="Export tests case meta-data to a file in CSV format.")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003352
Anas Nashif654ec5982019-04-11 08:38:21 -04003353 parser.add_argument("--timestamps",
Anas Nashifd9882382019-12-12 09:58:28 -05003354 action="store_true",
3355 help="Print all messages with time stamps")
Anas Nashif654ec5982019-04-11 08:38:21 -04003356
Anas Nashif3ba1d432017-12-05 15:28:44 -05003357 parser.add_argument(
3358 "-r", "--release", action="store_true",
3359 help="Update the benchmark database with the results of this test "
Anas Nashifd9882382019-12-12 09:58:28 -05003360 "run. Intended to be run by CI when tagging an official "
3361 "release. This database is used as a basis for comparison "
3362 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07003363 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003364 help="Treat warning conditions as errors")
3365 parser.add_argument(
3366 "-v",
3367 "--verbose",
3368 action="count",
3369 default=0,
3370 help="Emit debugging information, call multiple times to increase "
Anas Nashifd9882382019-12-12 09:58:28 -05003371 "verbosity")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003372 parser.add_argument(
3373 "-i", "--inline-logs", action="store_true",
3374 help="Upon test failure, print relevant log data to stdout "
Anas Nashifd9882382019-12-12 09:58:28 -05003375 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003376 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003377 help="log also to file")
3378 parser.add_argument(
3379 "-m", "--last-metrics", action="store_true",
3380 help="Instead of comparing metrics from the last --release, "
Anas Nashifd9882382019-12-12 09:58:28 -05003381 "compare with the results of the previous sanity check "
3382 "invocation")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003383 parser.add_argument(
3384 "-u",
3385 "--no-update",
3386 action="store_true",
3387 help="do not update the results of the last run of the sanity "
Anas Nashifd9882382019-12-12 09:58:28 -05003388 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08003389 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003390 "-F",
3391 "--load-tests",
3392 metavar="FILENAME",
3393 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07003394 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003395
Marc Herbertedf17592019-03-08 12:39:11 -08003396 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003397 "-E",
3398 "--save-tests",
3399 metavar="FILENAME",
3400 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07003401 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04003402
Andy Doancbecadd2019-02-08 10:19:10 -06003403 test_or_build = parser.add_mutually_exclusive_group()
3404 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003405 "-b", "--build-only", action="store_true",
3406 help="Only build the code, do not execute any of it in QEMU")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003407
Andy Doancbecadd2019-02-08 10:19:10 -06003408 test_or_build.add_argument(
3409 "--test-only", action="store_true",
3410 help="""Only run device tests with current artifacts, do not build
3411 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003412 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003413 "--cmake-only", action="store_true",
Anas Nashif83fc06a2019-06-22 11:04:10 -04003414 help="Only run cmake, do not build or run.")
Kumar Gala84cf9dc2019-06-15 09:36:06 -05003415
3416 parser.add_argument(
Anas Nashif1342d212020-01-16 12:23:00 -05003417 "-M", "--runtime-artifact-cleanup", action="store_true",
3418 help="Delete artifacts of passing tests.")
3419
3420 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003421 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07003422 help="Number of jobs for building, defaults to number of CPU threads, "
Anas Nashifd9882382019-12-12 09:58:28 -05003423 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06003424
3425 parser.add_argument(
Anas Nashifd9882382019-12-12 09:58:28 -05003426 "--show-footprint", action="store_true",
3427 help="Show footprint statistics and deltas since last release."
3428 )
Anas Nashif424a3db2018-02-20 08:37:24 -06003429 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003430 "-H", "--footprint-threshold", type=float, default=5,
3431 help="When checking test case footprint sizes, warn the user if "
Anas Nashifd9882382019-12-12 09:58:28 -05003432 "the new app size is greater then the specified percentage "
3433 "from the last release. Default is 5. 0 to warn on any "
3434 "increase on app size")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003435 parser.add_argument(
3436 "-D", "--all-deltas", action="store_true",
3437 help="Show all footprint deltas, positive or negative. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003438 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003439 parser.add_argument(
3440 "-O", "--outdir",
Anas Nashifd9882382019-12-12 09:58:28 -05003441 default=os.path.join(os.getcwd(), "sanity-out"),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00003442 help="Output directory for logs and binaries. "
Anas Nashifd9882382019-12-12 09:58:28 -05003443 "Default is 'sanity-out' in the current directory. "
Andrew Boie114c01b2020-01-02 18:44:22 -08003444 "This directory will be cleaned unless '--no-clean' is set. "
3445 "The '--clobber-output' option controls what cleaning does.")
3446 parser.add_argument(
3447 "-c", "--clobber-output", action="store_true",
3448 help="Cleaning the output directory will simply delete it instead "
3449 "of the default policy of renaming.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003450 parser.add_argument(
3451 "-n", "--no-clean", action="store_true",
Andrew Boie114c01b2020-01-02 18:44:22 -08003452 help="Re-use the outdir before building. Will result in "
3453 "faster compilation since builds will be incremental.")
Marc Herbertedf17592019-03-08 12:39:11 -08003454 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05003455 "-T", "--testcase-root", action="append", default=[],
3456 help="Base directory to recursively search for test cases. All "
Anas Nashifd9882382019-12-12 09:58:28 -05003457 "testcase.yaml files under here will be processed. May be "
3458 "called multiple times. Defaults to the 'samples/' and "
3459 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003460
Anas Nashif3ba1d432017-12-05 15:28:44 -05003461 board_root_list = ["%s/boards" % ZEPHYR_BASE,
3462 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05003463
Anas Nashif3ba1d432017-12-05 15:28:44 -05003464 parser.add_argument(
Thomas Stilwell53105102019-10-23 14:25:09 +02003465 "-A", "--board-root", action="append", default=board_root_list,
Anas Nashif83fc06a2019-06-22 11:04:10 -04003466 help="""Directory to search for board configuration files. All .yaml
3467files in the directory will be processed. The directory should have the same
3468structure in the main Zephyr tree: boards/<arch>/<board_name>/""")
3469
Anas Nashif3ba1d432017-12-05 15:28:44 -05003470 parser.add_argument(
3471 "-z", "--size", action="append",
3472 help="Don't run sanity checks. Instead, produce a report to "
Anas Nashifd9882382019-12-12 09:58:28 -05003473 "stdout detailing RAM/ROM sizes on the specified filenames. "
3474 "All other command line arguments ignored.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05003475 parser.add_argument(
3476 "-S", "--enable-slow", action="store_true",
3477 help="Execute time-consuming test cases that have been marked "
Anas Nashifd9882382019-12-12 09:58:28 -05003478 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003479 parser.add_argument(
3480 "--disable-unrecognized-section-test", action="store_true",
3481 default=False,
3482 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07003483 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003484 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07003485 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003486 parser.add_argument("--disable-asserts", action="store_false",
3487 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003488 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003489 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003490 help="Error on deprecation warnings.")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003491 parser.add_argument("--enable-size-report", action="store_true",
3492 help="Enable expensive computation of RAM/ROM segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003493
3494 parser.add_argument(
3495 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003496 help="""Extra CMake cache entries to define when building test cases.
3497 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003498 prefixed with -D before being passed to CMake.
3499
3500 E.g
3501 "sanitycheck -x=USE_CCACHE=0"
3502 will translate to
3503 "cmake -DUSE_CCACHE=0"
3504
3505 which will ultimately disable ccache.
3506 """
3507 )
Michael Scott421ce462019-06-18 09:37:46 -07003508
Andy Doan79c48842019-02-08 10:09:04 -06003509 parser.add_argument(
Anas Nashif83fc06a2019-06-22 11:04:10 -04003510 "--device-testing", action="store_true",
3511 help="Test on device directly. Specify the serial device to "
3512 "use with the --device-serial option.")
3513
3514 parser.add_argument(
3515 "-X", "--fixture", action="append", default=[],
3516 help="Specify a fixture that a board might support")
3517 parser.add_argument(
3518 "--device-serial",
3519 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
3520
3521 parser.add_argument("--generate-hardware-map",
3522 help="""Probe serial devices connected to this platform
3523 and create a hardware map file to be used with
3524 --device-testing
3525 """)
3526
3527 parser.add_argument("--hardware-map",
3528 help="""Load hardware map from a file. This will be used
3529 for testing on hardware that is listed in the file.
3530 """)
3531
3532 parser.add_argument(
Andy Doan79c48842019-02-08 10:09:04 -06003533 "--west-flash", nargs='?', const=[],
3534 help="""Uses west instead of ninja or make to flash when running with
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003535 --device-testing. Supports comma-separated argument list.
Andy Doan79c48842019-02-08 10:09:04 -06003536
Michael Scott4ca54392019-07-09 14:21:30 -07003537 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
Jorgen Kvalvaagd50fb312019-09-02 15:25:20 +02003538 --west-flash="--board-id=foobar,--erase"
3539 will translate to "west flash -- --board-id=foobar --erase"
Michael Scott4ca54392019-07-09 14:21:30 -07003540
3541 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003542 """
3543 )
Michael Scott421ce462019-06-18 09:37:46 -07003544 parser.add_argument(
3545 "--west-runner",
3546 help="""Uses the specified west runner instead of default when running
3547 with --west-flash.
3548
3549 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3550 --west-flash --west-runner=pyocd"
3551 will translate to "west flash --runner pyocd"
3552
3553 NOTE: west-flash must be enabled to use this option.
3554 """
3555 )
Jan Van Winkel21212f32019-09-12 00:03:35 +02003556
3557 valgrind_asan_group = parser.add_mutually_exclusive_group()
3558
3559 valgrind_asan_group.add_argument(
Anas Nashifc1ea4522019-10-11 07:32:45 -07003560 "--enable-valgrind", action="store_true",
3561 help="""Run binary through valgrind and check for several memory access
Jan Van Winkel21212f32019-09-12 00:03:35 +02003562 errors. Valgrind needs to be installed on the host. This option only
Anas Nashifc1ea4522019-10-11 07:32:45 -07003563 works with host binaries such as those generated for the native_posix
Jan Van Winkel21212f32019-09-12 00:03:35 +02003564 configuration and is mutual exclusive with --enable-asan.
3565 """)
3566
3567 valgrind_asan_group.add_argument(
3568 "--enable-asan", action="store_true",
3569 help="""Enable address sanitizer to check for several memory access
3570 errors. Libasan needs to be installed on the host. This option only
3571 works with host binaries such as those generated for the native_posix
3572 configuration and is mutual exclusive with --enable-valgrind.
3573 """)
3574
3575 parser.add_argument(
3576 "--enable-lsan", action="store_true",
3577 help="""Enable leak sanitizer to check for heap memory leaks.
3578 Libasan needs to be installed on the host. This option only
3579 works with host binaries such as those generated for the native_posix
3580 configuration and when --enable-asan is given.
Anas Nashifc1ea4522019-10-11 07:32:45 -07003581 """)
Andrew Boie8047a6f2019-07-02 15:43:29 -07003582
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003583 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003584 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003585
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003586 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003587 help="Generate coverage reports. Implies "
Anas Nashifd9882382019-12-12 09:58:28 -05003588 "--enable_coverage.")
Andrew Boie6acbe632015-07-17 12:03:52 -07003589
Andrew Boie8047a6f2019-07-02 15:43:29 -07003590 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003591 help="Plarforms to run coverage reports on. "
Anas Nashifd9882382019-12-12 09:58:28 -05003592 "This option may be used multiple times. "
3593 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003594
Anas Nashif83fc06a2019-06-22 11:04:10 -04003595 parser.add_argument("--gcov-tool", default=None,
3596 help="Path to the gcov tool to use for code coverage "
Anas Nashifd9882382019-12-12 09:58:28 -05003597 "reports")
Anas Nashif83fc06a2019-06-22 11:04:10 -04003598
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003599 parser.add_argument("--coverage-tool", choices=['lcov', 'gcovr'], default='lcov',
3600 help="Tool to use to generate coverage report.")
3601
Andrew Boie6acbe632015-07-17 12:03:52 -07003602 return parser.parse_args()
3603
Anas Nashifd9882382019-12-12 09:58:28 -05003604
Andrew Boiebbd670c2015-08-17 13:16:11 -07003605def size_report(sc):
Anas Nashif7a361b82019-12-06 11:37:40 -05003606 logger.info(sc.filename)
3607 logger.info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003608 for i in range(len(sc.sections)):
3609 v = sc.sections[i]
3610
Anas Nashif7a361b82019-12-06 11:37:40 -05003611 logger.info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
Anas Nashifd9882382019-12-12 09:58:28 -05003612 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3613 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003614
Anas Nashif7a361b82019-12-06 11:37:40 -05003615 logger.info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashifd9882382019-12-12 09:58:28 -05003616 (sc.rom_size, sc.ram_size))
Anas Nashif7a361b82019-12-06 11:37:40 -05003617 logger.info("")
Andrew Boiebbd670c2015-08-17 13:16:11 -07003618
Anas Nashifd9882382019-12-12 09:58:28 -05003619
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003620class CoverageTool:
3621 """ Base class for every supported coverage tool
3622 """
3623
3624 def __init__(self):
3625 self.gcov_tool = options.gcov_tool
3626
3627 @staticmethod
3628 def factory(tool):
3629 if tool == 'lcov':
3630 return Lcov()
3631 if tool == 'gcovr':
3632 return Gcovr()
Anas Nashif7a361b82019-12-06 11:37:40 -05003633 logger.error("Unsupported coverage tool specified: {}".format(tool))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003634
3635 @staticmethod
3636 def retrieve_gcov_data(intput_file):
3637 if VERBOSE:
Anas Nashifd9882382019-12-12 09:58:28 -05003638 logger.debug("Working on %s" % intput_file)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003639 extracted_coverage_info = {}
3640 capture_data = False
3641 capture_complete = False
3642 with open(intput_file, 'r') as fp:
3643 for line in fp.readlines():
3644 if re.search("GCOV_COVERAGE_DUMP_START", line):
3645 capture_data = True
3646 continue
3647 if re.search("GCOV_COVERAGE_DUMP_END", line):
3648 capture_complete = True
3649 break
3650 # Loop until the coverage data is found.
3651 if not capture_data:
3652 continue
3653 if line.startswith("*"):
3654 sp = line.split("<")
3655 if len(sp) > 1:
3656 # Remove the leading delimiter "*"
3657 file_name = sp[0][1:]
3658 # Remove the trailing new line char
3659 hex_dump = sp[1][:-1]
3660 else:
3661 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003662 else:
3663 continue
Anas Nashifd9882382019-12-12 09:58:28 -05003664 extracted_coverage_info.update({file_name: hex_dump})
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003665 if not capture_data:
3666 capture_complete = True
3667 return {'complete': capture_complete, 'data': extracted_coverage_info}
3668
3669 @staticmethod
3670 def create_gcda_files(extracted_coverage_info):
3671 if VERBOSE:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003672 logger.debug("Generating gcda files")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003673 for filename, hexdump_val in extracted_coverage_info.items():
3674 # if kobject_hash is given for coverage gcovr fails
3675 # hence skipping it problem only in gcovr v4.1
3676 if "kobject_hash" in filename:
Anas Nashifd9882382019-12-12 09:58:28 -05003677 filename = (filename[:-4]) + "gcno"
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003678 try:
3679 os.remove(filename)
3680 except Exception:
3681 pass
Anas Nashifdb9592a2018-10-08 10:19:41 -04003682 continue
Anas Nashifdb9592a2018-10-08 10:19:41 -04003683
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003684 with open(filename, 'wb') as fp:
3685 fp.write(bytes.fromhex(hexdump_val))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003686
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003687 def generate(self, outdir):
3688 for filename in glob.glob("%s/**/handler.log" % outdir, recursive=True):
3689 gcov_data = self.__class__.retrieve_gcov_data(filename)
3690 capture_complete = gcov_data['complete']
3691 extracted_coverage_info = gcov_data['data']
3692 if capture_complete:
3693 self.__class__.create_gcda_files(extracted_coverage_info)
Anas Nashif7a361b82019-12-06 11:37:40 -05003694 logger.debug("Gcov data captured: {}".format(filename))
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003695 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003696 logger.error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003697
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003698 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3699 ret = self._generate(outdir, coveragelog)
3700 if ret == 0:
Anas Nashif7a361b82019-12-06 11:37:40 -05003701 logger.info("HTML report generated: {}".format(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003702 os.path.join(outdir, "coverage", "index.html")))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003703
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003704
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003705class Lcov(CoverageTool):
3706
3707 def __init__(self):
3708 super().__init__()
3709 self.ignores = []
3710
3711 def add_ignore_file(self, pattern):
3712 self.ignores.append('*' + pattern + '*')
3713
3714 def add_ignore_directory(self, pattern):
3715 self.ignores.append(pattern + '/*')
3716
3717 def _generate(self, outdir, coveragelog):
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003718 coveragefile = os.path.join(outdir, "coverage.info")
3719 ztestfile = os.path.join(outdir, "ztest.info")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003720 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool,
3721 "--capture", "--directory", outdir,
3722 "--rc", "lcov_branch_coverage=1",
3723 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003724 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003725 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--extract",
3726 coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003727 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003728 "--output-file", ztestfile,
3729 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3730
Anas Nashif3cbffef2018-11-07 23:50:54 -05003731 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003732 subprocess.call(["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3733 ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003734 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3735 "--output-file", ztestfile,
3736 "--rc", "lcov_branch_coverage=1"],
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003737 stdout=coveragelog)
Anas Nashif83fc06a2019-06-22 11:04:10 -04003738 files = [coveragefile, ztestfile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003739 else:
Anas Nashif83fc06a2019-06-22 11:04:10 -04003740 files = [coveragefile]
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003741
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003742 for i in self.ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003743 subprocess.call(
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003744 ["lcov", "--gcov-tool", self.gcov_tool, "--remove",
3745 coveragefile, i, "--output-file",
3746 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003747 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003748
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003749 # The --ignore-errors source option is added to avoid it exiting due to
3750 # samples/application_development/external_lib/
3751 return subprocess.call(["genhtml", "--legend", "--branch-coverage",
3752 "--ignore-errors", "source",
3753 "-output-directory",
3754 os.path.join(outdir, "coverage")] + files,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003755 stdout=coveragelog)
Christian Taedckeed22c5e2019-11-23 16:47:33 +01003756
3757
3758class Gcovr(CoverageTool):
3759
3760 def __init__(self):
3761 super().__init__()
3762 self.ignores = []
3763
3764 def add_ignore_file(self, pattern):
3765 self.ignores.append('.*' + pattern + '.*')
3766
3767 def add_ignore_directory(self, pattern):
3768 self.ignores.append(pattern + '/.*')
3769
3770 @staticmethod
3771 def _interleave_list(prefix, list):
3772 tuple_list = [(prefix, item) for item in list]
3773 return [item for sublist in tuple_list for item in sublist]
3774
3775 def _generate(self, outdir, coveragelog):
3776 coveragefile = os.path.join(outdir, "coverage.json")
3777 ztestfile = os.path.join(outdir, "ztest.json")
3778
3779 excludes = Gcovr._interleave_list("-e", self.ignores)
3780
3781 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
3782 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3783 self.gcov_tool, "-e", "tests/*"] + excludes +
3784 ["--json", "-o", coveragefile, outdir],
3785 stdout=coveragelog)
3786
3787 subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--gcov-executable",
3788 self.gcov_tool, "-f", "tests/ztest", "-e",
3789 "tests/ztest/test/*", "--json", "-o", ztestfile,
3790 outdir], stdout=coveragelog)
3791
3792 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
3793 files = [coveragefile, ztestfile]
3794 else:
3795 files = [coveragefile]
3796
3797 subdir = os.path.join(outdir, "coverage")
3798 os.makedirs(subdir, exist_ok=True)
3799
3800 tracefiles = self._interleave_list("--add-tracefile", files)
3801
3802 return subprocess.call(["gcovr", "-r", ZEPHYR_BASE, "--html",
3803 "--html-details"] + tracefiles +
3804 ["-o", os.path.join(subdir, "index.html")],
3805 stdout=coveragelog)
3806
Anas Nashif3ba1d432017-12-05 15:28:44 -05003807
Anas Nashif83fc06a2019-06-22 11:04:10 -04003808def get_generator():
3809 if options.ninja:
3810 generator_cmd = "ninja"
3811 generator = "Ninja"
3812 else:
3813 generator_cmd = "make"
3814 generator = "Unix Makefiles"
3815 return generator_cmd, generator
3816
3817
3818def export_tests(filename, tests):
3819 with open(filename, "wt") as csvfile:
3820 fieldnames = ['section', 'subsection', 'title', 'reference']
3821 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3822 for test in tests:
3823 data = test.split(".")
Anas Nashif19ca7832019-11-18 08:16:21 -08003824 if len(data) > 1:
3825 subsec = " ".join(data[1].split("_")).title()
3826 rowdict = {
Anas Nashifd9882382019-12-12 09:58:28 -05003827 "section": data[0].capitalize(),
3828 "subsection": subsec,
3829 "title": test,
3830 "reference": test
3831 }
Anas Nashif19ca7832019-11-18 08:16:21 -08003832 cw.writerow(rowdict)
3833 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05003834 logger.info("{} can't be exported".format(test))
Anas Nashif83fc06a2019-06-22 11:04:10 -04003835
Anas Nashif83fc06a2019-06-22 11:04:10 -04003836
Anas Nashif5f908822019-11-25 08:19:25 -05003837class HardwareMap:
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003838
3839 schema_path = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk", "hwmap-schema.yaml")
3840
Anas Nashif5f908822019-11-25 08:19:25 -05003841 manufacturer = [
3842 'ARM',
3843 'SEGGER',
3844 'MBED',
3845 'STMicroelectronics',
3846 'Atmel Corp.',
3847 'Texas Instruments',
3848 'Silicon Labs',
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003849 'NXP Semiconductors',
3850 'Microchip Technology Inc.',
Anas Nashif5c331f92019-12-20 10:42:27 -05003851 'FTDI',
3852 'Digilent'
Anas Nashifd9882382019-12-12 09:58:28 -05003853 ]
Anas Nashif5f908822019-11-25 08:19:25 -05003854
3855 runner_mapping = {
3856 'pyocd': [
3857 'DAPLink CMSIS-DAP',
3858 'MBED CMSIS-DAP'
3859 ],
3860 'jlink': [
3861 'J-Link',
3862 'J-Link OB'
3863 ],
3864 'openocd': [
3865 'STM32 STLink', '^XDS110.*'
Maksim Masalski1b4b9ef2019-12-18 14:48:24 +08003866 ],
3867 'dediprog': [
3868 'TTL232R-3V3',
3869 'MCP2200 USB Serial Port Emulator'
Anas Nashif5f908822019-11-25 08:19:25 -05003870 ]
3871 }
3872
3873 def __init__(self):
3874 self.detected = []
3875 self.connected_hardware = []
3876
3877 def load_device_from_cmdline(self, serial, platform):
3878 device = {
3879 "serial": serial,
3880 "platform": platform,
3881 "counter": 0,
3882 "available": True,
3883 "connected": True
3884 }
3885 self.connected_hardware.append(device)
3886
3887 def load_hardware_map(self, map_file):
Anas Nashif2a5d61d2019-12-19 12:33:51 -05003888 hwm_schema = scl.yaml_load(self.schema_path)
3889 self.connected_hardware = scl.yaml_load_verify(map_file, hwm_schema)
Anas Nashif5f908822019-11-25 08:19:25 -05003890 for i in self.connected_hardware:
3891 i['counter'] = 0
3892
3893 def scan_hw(self):
3894 from serial.tools import list_ports
3895
3896 serial_devices = list_ports.comports()
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003897 logger.info("Scanning connected hardware...")
Anas Nashif5f908822019-11-25 08:19:25 -05003898 for d in serial_devices:
3899 if d.manufacturer in self.manufacturer:
3900
3901 # TI XDS110 can have multiple serial devices for a single board
3902 # assume endpoint 0 is the serial, skip all others
3903 if d.manufacturer == 'Texas Instruments' and not d.location.endswith('0'):
3904 continue
3905 s_dev = {}
3906 s_dev['platform'] = "unknown"
3907 s_dev['id'] = d.serial_number
3908 s_dev['serial'] = d.device
3909 s_dev['product'] = d.product
3910 s_dev['runner'] = 'unknown'
Anas Nashifd9882382019-12-12 09:58:28 -05003911 for runner, _ in self.runner_mapping.items():
Anas Nashif5f908822019-11-25 08:19:25 -05003912 products = self.runner_mapping.get(runner)
3913 if d.product in products:
3914 s_dev['runner'] = runner
3915 continue
3916 # Try regex matching
3917 for p in products:
3918 if re.match(p, d.product):
3919 s_dev['runner'] = runner
3920
3921 s_dev['available'] = True
3922 s_dev['connected'] = True
3923 self.detected.append(s_dev)
3924 else:
Anas Nashifd9882382019-12-12 09:58:28 -05003925 logger.warning("Unsupported device (%s): %s" % (d.manufacturer, d))
Anas Nashif5f908822019-11-25 08:19:25 -05003926
3927 def write_map(self, hwm_file):
3928 # use existing map
3929 if os.path.exists(hwm_file):
3930 with open(hwm_file, 'r') as yaml_file:
3931 hwm = yaml.load(yaml_file, Loader=yaml.FullLoader)
3932 # disconnect everything
3933 for h in hwm:
3934 h['connected'] = False
3935 h['serial'] = None
3936
3937 for d in self.detected:
3938 for h in hwm:
3939 if d['id'] == h['id'] and d['product'] == h['product']:
Anas Nashif5f908822019-11-25 08:19:25 -05003940 h['connected'] = True
3941 h['serial'] = d['serial']
3942 d['match'] = True
3943
Anas Nashifd9882382019-12-12 09:58:28 -05003944 new = list(filter(lambda n: not n.get('match', False), self.detected))
Anas Nashif5f908822019-11-25 08:19:25 -05003945 hwm = hwm + new
3946
Anas Nashif6d66a7a2019-12-08 12:11:43 -05003947 logger.info("Registered devices:")
Anas Nashif0c69c282019-12-18 10:41:27 -05003948 self.dump(hwm)
Anas Nashife5006d12019-12-01 11:41:22 -05003949
Anas Nashif5f908822019-11-25 08:19:25 -05003950 with open(hwm_file, 'w') as yaml_file:
3951 yaml.dump(hwm, yaml_file, default_flow_style=False)
3952
3953 else:
3954 # create new file
3955 with open(hwm_file, 'w') as yaml_file:
3956 yaml.dump(self.detected, yaml_file, default_flow_style=False)
Anas Nashif0c69c282019-12-18 10:41:27 -05003957 logger.info("Detected devices:")
3958 self.dump(self.detected)
Anas Nashif5f908822019-11-25 08:19:25 -05003959
Anas Nashif0c69c282019-12-18 10:41:27 -05003960 @staticmethod
3961 def dump(hwmap=[], filtered=[], header=[], connected_only=False):
3962 print("")
3963 table = []
3964 if not header:
3965 header = ["Platform", "ID", "Serial device"]
3966 for p in sorted(hwmap, key=lambda i: i['platform']):
3967 platform = p.get('platform')
3968 connected = p.get('connected', False)
3969 if filtered and platform not in filtered:
3970 continue
3971
3972 if not connected_only or connected:
3973 table.append([platform, p.get('id', None), p.get('serial')])
3974
3975 print(tabulate(table, headers=header, tablefmt="github"))
Anas Nashifd9882382019-12-12 09:58:28 -05003976
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003977options = None
Anas Nashif5f908822019-11-25 08:19:25 -05003978
Anas Nashifd9882382019-12-12 09:58:28 -05003979
Andrew Boie6acbe632015-07-17 12:03:52 -07003980def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003981 start_time = time.time()
Anas Nashif7a361b82019-12-06 11:37:40 -05003982 global VERBOSE
Anas Nashife10b6512017-12-30 13:01:45 -05003983 global options
Andrew Boie1578ef72019-07-03 10:19:29 -07003984
Anas Nashiff5e5ef02019-12-06 19:20:48 -05003985 options = parse_arguments()
Anas Nashif7a361b82019-12-06 11:37:40 -05003986
3987 # Cleanup
3988 if options.no_clean or options.only_failed or options.test_only:
3989 if os.path.exists(options.outdir):
Andrew Boie2d8d4c52020-01-02 19:31:20 -08003990 print("Keeping artifacts untouched")
Anas Nashif7a361b82019-12-06 11:37:40 -05003991 elif os.path.exists(options.outdir):
Andrew Boie114c01b2020-01-02 18:44:22 -08003992 if options.clobber_output:
3993 print("Deleting output directory {}".format(options.outdir))
3994 shutil.rmtree(options.outdir)
3995 else:
3996 for i in range(1, 100):
3997 new_out = options.outdir + ".{}".format(i)
3998 if not os.path.exists(new_out):
3999 print("Renaming output directory to {}".format(new_out))
4000 shutil.move(options.outdir, new_out)
4001 break
Anas Nashif7a361b82019-12-06 11:37:40 -05004002
4003 os.makedirs(options.outdir, exist_ok=True)
4004
4005 # create file handler which logs even debug messages
4006 if options.log_file:
4007 fh = logging.FileHandler(options.log_file)
4008 else:
4009 fh = logging.FileHandler(os.path.join(options.outdir, "sanitycheck.log"))
4010
4011 fh.setLevel(logging.DEBUG)
4012
4013 # create console handler with a higher log level
4014 ch = logging.StreamHandler()
4015
Anas Nashif7a361b82019-12-06 11:37:40 -05004016 VERBOSE += options.verbose
4017 if VERBOSE > 1:
4018 ch.setLevel(logging.DEBUG)
4019 else:
4020 ch.setLevel(logging.INFO)
4021
Anas Nashif7a361b82019-12-06 11:37:40 -05004022 # create formatter and add it to the handlers
Anas Nashifd4cef072019-12-08 11:58:00 -05004023 if options.timestamps:
4024 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
4025 else:
Anas Nashif6d66a7a2019-12-08 12:11:43 -05004026 formatter = logging.Formatter('%(levelname)-7s - %(message)s')
Anas Nashifd4cef072019-12-08 11:58:00 -05004027
Anas Nashif7a361b82019-12-06 11:37:40 -05004028 formatter_file = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
4029 ch.setFormatter(formatter)
4030 fh.setFormatter(formatter_file)
4031
4032 # add the handlers to logger
4033 logger.addHandler(ch)
4034 logger.addHandler(fh)
Andrew Boiebbd670c2015-08-17 13:16:11 -07004035
Anas Nashif5f908822019-11-25 08:19:25 -05004036 hwm = HardwareMap()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004037 if options.generate_hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05004038 hwm.scan_hw()
4039 hwm.write_map(options.generate_hardware_map)
Anas Nashif83fc06a2019-06-22 11:04:10 -04004040 return
4041
Anas Nashif5f908822019-11-25 08:19:25 -05004042 if not options.device_testing and options.hardware_map:
4043 hwm.load_hardware_map(options.hardware_map)
4044
Anas Nashif6d66a7a2019-12-08 12:11:43 -05004045 logger.info("Available devices:")
Anas Nashif5f908822019-11-25 08:19:25 -05004046 table = []
Anas Nashif0c69c282019-12-18 10:41:27 -05004047 hwm.dump(hwmap=hwm.connected_hardware, connected_only=True)
Anas Nashif5f908822019-11-25 08:19:25 -05004048 return
Anas Nashif83fc06a2019-06-22 11:04:10 -04004049
Michael Scott421ce462019-06-18 09:37:46 -07004050 if options.west_runner and not options.west_flash:
Anas Nashif7a361b82019-12-06 11:37:40 -05004051 logger.error("west-runner requires west-flash to be enabled")
Michael Scott421ce462019-06-18 09:37:46 -07004052 sys.exit(1)
4053
Michael Scott4ca54392019-07-09 14:21:30 -07004054 if options.west_flash and not options.device_testing:
Anas Nashif7a361b82019-12-06 11:37:40 -05004055 logger.error("west-flash requires device-testing to be enabled")
Michael Scott4ca54392019-07-09 14:21:30 -07004056 sys.exit(1)
4057
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02004058 if options.coverage:
4059 options.enable_coverage = True
Christian Taedckec415a4e2019-11-23 23:25:36 +01004060
4061 if not options.coverage_platform:
4062 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02004063
Anas Nashife10b6512017-12-30 13:01:45 -05004064 if options.size:
4065 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08004066 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07004067 sys.exit(0)
4068
Anas Nashife10b6512017-12-30 13:01:45 -05004069 if options.subset:
4070 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04004071 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif7a361b82019-12-06 11:37:40 -05004072 logger.info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04004073 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004074 logger.error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04004075 return
4076
Anas Nashife10b6512017-12-30 13:01:45 -05004077 if not options.testcase_root:
4078 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Anas Nashifd9882382019-12-12 09:58:28 -05004079 os.path.join(ZEPHYR_BASE, "samples")]
Andrew Boie3d348712016-04-08 11:52:13 -07004080
Anas Nashife0d931f2019-12-09 15:23:43 -05004081 if options.show_footprint or options.compare_report or options.release:
4082 options.enable_size_report = True
4083
Anas Nashif56656842019-12-10 12:26:00 -05004084 suite = TestSuite(options.board_root, options.testcase_root, options.outdir)
4085
4086 # Set testsuite options from command line.
4087 suite.build_only = options.build_only
Anas Nashifc5ee3952019-12-10 16:38:45 -05004088 suite.cmake_only = options.cmake_only
Anas Nashif1342d212020-01-16 12:23:00 -05004089 suite.cleanup = options.runtime_artifact_cleanup
Anas Nashifc5ee3952019-12-10 16:38:45 -05004090 suite.test_only = options.test_only
Anas Nashif56656842019-12-10 12:26:00 -05004091 suite.enable_slow = options.enable_slow
4092 suite.device_testing = options.device_testing
4093 suite.fixture = options.fixture
4094 suite.enable_asan = options.enable_asan
4095 suite.enable_lsan = options.enable_lsan
4096 suite.enable_coverage = options.enable_coverage
Anas Nashif56656842019-12-10 12:26:00 -05004097 suite.enable_valgrind = options.enable_valgrind
4098 suite.coverage_platform = options.coverage_platform
Anas Nashife9eb0092019-12-10 16:31:22 -05004099 suite.inline_logs = options.inline_logs
Anas Nashifc5ee3952019-12-10 16:38:45 -05004100 suite.enable_size_report = options.enable_size_report
Anas Nashifcd1f0172020-01-14 09:47:27 -05004101 suite.extra_args = options.extra_args
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004102
4103 # Set number of jobs
4104 if options.jobs:
4105 suite.jobs = options.jobs
4106 elif options.build_only:
4107 suite.jobs = multiprocessing.cpu_count() * 2
4108 else:
4109 suite.jobs = multiprocessing.cpu_count()
Anas Nashif7a361b82019-12-06 11:37:40 -05004110 logger.info("JOBS: %d" % suite.jobs)
Anas Nashif51ae4ed2019-12-05 11:02:02 -05004111
Anas Nashif83fc06a2019-06-22 11:04:10 -04004112 suite.add_testcases()
4113 suite.add_configurations()
Anas Nashifbd166f42017-09-02 12:32:08 -04004114
Anas Nashif83fc06a2019-06-22 11:04:10 -04004115 if options.device_testing:
4116 if options.hardware_map:
Anas Nashif5f908822019-11-25 08:19:25 -05004117 hwm.load_hardware_map(options.hardware_map)
4118 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004119 if not options.platform:
4120 options.platform = []
Anas Nashif5f908822019-11-25 08:19:25 -05004121 for platform in hwm.connected_hardware:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004122 if platform['connected']:
4123 options.platform.append(platform['platform'])
4124
Anas Nashifd9882382019-12-12 09:58:28 -05004125 elif options.device_serial: # back-ward compatibility
Anas Nashif83fc06a2019-06-22 11:04:10 -04004126 if options.platform and len(options.platform) == 1:
Anas Nashif5f908822019-11-25 08:19:25 -05004127 hwm.load_device_from_cmdline(options.device_serial, options.platform[0])
Anas Nashifebf8dae2019-12-16 09:22:21 -05004128 suite.connected_hardware = hwm.connected_hardware
Anas Nashif83fc06a2019-06-22 11:04:10 -04004129 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004130 logger.error("""When --device-testing is used with --device-serial, only one
Anas Nashif83fc06a2019-06-22 11:04:10 -04004131 platform is allowed""")
4132
Anas Nashif83fc06a2019-06-22 11:04:10 -04004133 if suite.load_errors:
Kumar Galac84235e2018-04-10 13:32:51 -05004134 sys.exit(1)
4135
Anas Nashif75547e22018-02-24 08:32:14 -06004136 if options.list_tags:
4137 tags = set()
Anas Nashif83fc06a2019-06-22 11:04:10 -04004138 for _, tc in suite.testcases.items():
Anas Nashif75547e22018-02-24 08:32:14 -06004139 tags = tags.union(tc.tags)
4140
4141 for t in tags:
4142 print("- {}".format(t))
4143
4144 return
4145
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004146 if options.export_tests:
4147 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004148 tests = suite.get_all_tests()
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004149 export_tests(options.export_tests, tests)
4150 return
4151
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004152 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05004153
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004154 if options.test:
4155 run_individual_tests = options.test
4156
Anas Nashif434995c2019-12-01 13:55:11 -05004157 if options.list_tests or options.test_tree or options.list_test_duplicates or options.sub_test:
Anas Nashif49b22d42019-06-14 13:45:34 -04004158 cnt = 0
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004159 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004160
Anas Nashif19ca7832019-11-18 08:16:21 -08004161 if options.list_test_duplicates:
4162 import collections
4163 dupes = [item for item, count in collections.Counter(all_tests).items() if count > 1]
4164 if dupes:
4165 print("Tests with duplicate identifiers:")
4166 for dupe in dupes:
4167 print("- {}".format(dupe))
4168 for dc in suite.get_testcase(dupe):
4169 print(" - {}".format(dc))
4170 else:
4171 print("No duplicates found.")
4172 return
4173
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004174 if options.sub_test:
Anas Nashifd11fd782019-11-18 10:22:56 -08004175 for st in options.sub_test:
4176 subtests = suite.get_testcase(st)
4177 for sti in subtests:
4178 run_individual_tests.append(sti.name)
4179
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004180 if run_individual_tests:
Anas Nashif7a361b82019-12-06 11:37:40 -05004181 logger.info("Running the following tests:")
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004182 for test in run_individual_tests:
4183 print(" - {}".format(test))
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004184 else:
Anas Nashif7a361b82019-12-06 11:37:40 -05004185 logger.info("Tests not found")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004186 return
4187
Anas Nashif434995c2019-12-01 13:55:11 -05004188 elif options.list_tests or options.test_tree:
4189 if options.test_tree:
4190 testsuite = Node("Testsuite")
4191 samples = Node("Samples", parent=testsuite)
4192 tests = Node("Tests", parent=testsuite)
4193
Anas Nashifc1c3cc62019-12-01 13:24:33 -05004194 for test in sorted(all_tests):
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004195 cnt = cnt + 1
Anas Nashif434995c2019-12-01 13:55:11 -05004196 if options.list_tests:
4197 print(" - {}".format(test))
4198
4199 if options.test_tree:
4200 if test.startswith("sample."):
4201 sec = test.split(".")
4202 area = find(samples, lambda node: node.name == sec[1] and node.parent == samples)
4203 if not area:
4204 area = Node(sec[1], parent=samples)
4205
4206 t = Node(test, parent=area)
4207 else:
4208 sec = test.split(".")
4209 area = find(tests, lambda node: node.name == sec[0] and node.parent == tests)
4210 if not area:
4211 area = Node(sec[0], parent=tests)
4212
4213 if area and len(sec) > 2:
4214 subarea = find(area, lambda node: node.name == sec[1] and node.parent == area)
4215 if not subarea:
4216 subarea = Node(sec[1], parent=area)
4217
4218 t = Node(test, parent=subarea)
4219
4220 if options.list_tests:
4221 print("{} total.".format(cnt))
4222
4223 if options.test_tree:
4224 for pre, _, node in RenderTree(testsuite):
4225 print("%s%s" % (pre, node.name))
4226
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05004227 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05004228
Anas Nashifbd166f42017-09-02 12:32:08 -04004229 discards = []
Anas Nashif83fc06a2019-06-22 11:04:10 -04004230
4231 if options.only_failed:
4232 suite.get_last_failed()
Anas Nashif5f908822019-11-25 08:19:25 -05004233 suite.selected_platforms = set(p.platform.name for p in suite.instances.values())
Anas Nashif83fc06a2019-06-22 11:04:10 -04004234 elif options.load_tests:
4235 suite.load_from_file(options.load_tests)
4236 elif options.test_only:
4237 last_run = os.path.join(options.outdir, "sanitycheck.csv")
4238 suite.load_from_file(last_run)
Anas Nashifbd166f42017-09-02 12:32:08 -04004239 else:
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004240 discards = suite.apply_filters(
4241 build_only=options.build_only,
4242 enable_slow=options.enable_slow,
4243 platform=options.platform,
Anas Nashif9fe1e2a2020-01-21 17:19:36 -05004244 exclude_platform=options.exclude_platform,
Anas Nashiff5e5ef02019-12-06 19:20:48 -05004245 arch=options.arch,
4246 tag=options.tag,
4247 exclude_tag=options.exclude_tag,
4248 force_toolchain=options.force_toolchain,
4249 all=options.all,
4250 run_individual_tests=run_individual_tests,
4251 device_testing=options.device_testing
4252
4253 )
Andrew Boie6acbe632015-07-17 12:03:52 -07004254
Anas Nashif30551f42018-01-12 21:56:59 -05004255 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004256 # if we are using command line platform filter, no need to list every
4257 # other platform as excluded, we know that already.
4258 # Show only the discards that apply to the selected platforms on the
4259 # command line
4260
Andrew Boie08ce5a52016-02-22 13:28:10 -08004261 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05004262 if options.platform and i.platform.name not in options.platform:
4263 continue
Anas Nashif7a361b82019-12-06 11:37:40 -05004264 logger.debug(
Anas Nashif3ba1d432017-12-05 15:28:44 -05004265 "{:<25} {:<50} {}SKIPPED{}: {}".format(
4266 i.platform.name,
Anas Nashif83fc06a2019-06-22 11:04:10 -04004267 i.testcase.name,
Anas Nashif97445682019-12-16 09:36:40 -05004268 Fore.YELLOW,
4269 Fore.RESET,
Anas Nashif3ba1d432017-12-05 15:28:44 -05004270 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07004271
Anas Nashif49b22d42019-06-14 13:45:34 -04004272 if options.report_excluded:
Anas Nashifeabaa7f2019-11-18 07:49:17 -08004273 all_tests = suite.get_all_tests()
Anas Nashif49b22d42019-06-14 13:45:34 -04004274 to_be_run = set()
Anas Nashifd9882382019-12-12 09:58:28 -05004275 for i, p in suite.instances.items():
Anas Nashif83fc06a2019-06-22 11:04:10 -04004276 to_be_run.update(p.testcase.cases)
Anas Nashif49b22d42019-06-14 13:45:34 -04004277
Anas Nashif83fc06a2019-06-22 11:04:10 -04004278 if all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004279 print("Tests that never build or run:")
Anas Nashif83fc06a2019-06-22 11:04:10 -04004280 for not_run in all_tests - to_be_run:
Anas Nashif49b22d42019-06-14 13:45:34 -04004281 print("- {}".format(not_run))
4282
4283 return
4284
Anas Nashife10b6512017-12-30 13:01:45 -05004285 if options.subset:
Anas Nashifdd65f7c2020-01-22 18:10:17 -05004286 suite.instances = OrderedDict(sorted(suite.instances.items(),
4287 key=lambda x: x[0][x[0].find("/") + 1:]))
4288
Anas Nashife10b6512017-12-30 13:01:45 -05004289 subset, sets = options.subset.split("/")
Kumar Galade70e9c2020-02-04 17:22:16 -06004290 subset = int(subset)
4291 sets = int(sets)
Anas Nashif83fc06a2019-06-22 11:04:10 -04004292 total = len(suite.instances)
Kumar Galade70e9c2020-02-04 17:22:16 -06004293 per_set = int(total / sets)
4294 num_extra_sets = total - (per_set * sets)
4295
4296 # Try and be more fair for rounding error with integer division
4297 # so the last subset doesn't get overloaded, we add 1 extra to
4298 # subsets 1..num_extra_sets.
4299 if subset <= num_extra_sets:
4300 start = (subset - 1) * (per_set + 1)
4301 end = start + per_set + 1
Anas Nashif035799f2017-05-13 21:31:53 -04004302 else:
Kumar Galade70e9c2020-02-04 17:22:16 -06004303 base = num_extra_sets * (per_set + 1)
4304 start = ((subset - num_extra_sets - 1) * per_set) + base
Anas Nashif035799f2017-05-13 21:31:53 -04004305 end = start + per_set
4306
Anas Nashif83fc06a2019-06-22 11:04:10 -04004307 sliced_instances = islice(suite.instances.items(), start, end)
4308 suite.instances = OrderedDict(sliced_instances)
Anas Nashif035799f2017-05-13 21:31:53 -04004309
Anas Nashif83fc06a2019-06-22 11:04:10 -04004310 if options.save_tests:
4311 suite.csv_report(options.save_tests)
Andrew Boie6acbe632015-07-17 12:03:52 -07004312 return
4313
Anas Nashif7a361b82019-12-06 11:37:40 -05004314 logger.info("%d test configurations selected, %d configurations discarded due to filters." %
Anas Nashifd9882382019-12-12 09:58:28 -05004315 (len(suite.instances), len(discards)))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004316
Peter Bigot3d46ea52019-11-21 12:00:18 -06004317 if options.device_testing:
4318 print("\nDevice testing on:")
Anas Nashif0c69c282019-12-18 10:41:27 -05004319 hwm.dump(suite.connected_hardware, suite.selected_platforms)
Anas Nashif5f908822019-11-25 08:19:25 -05004320 print("")
Peter Bigot3d46ea52019-11-21 12:00:18 -06004321
Anas Nashif83fc06a2019-06-22 11:04:10 -04004322 if options.dry_run:
4323 duration = time.time() - start_time
Anas Nashif7a361b82019-12-06 11:37:40 -05004324 logger.info("Completed in %d seconds" % (duration))
Anas Nashif83fc06a2019-06-22 11:04:10 -04004325 return
4326
4327 retries = options.retry_failed + 1
4328 completed = 0
4329
4330 suite.update()
4331 suite.start_time = start_time
4332
4333 while True:
4334 completed += 1
4335
4336 if completed > 1:
Anas Nashifd9882382019-12-12 09:58:28 -05004337 logger.info("%d Iteration:" % (completed))
4338 time.sleep(60) # waiting for the system to settle down
Anas Nashif83fc06a2019-06-22 11:04:10 -04004339 suite.total_done = suite.total_tests - suite.total_failed
4340 suite.total_failed = 0
4341
Anas Nashifc5ee3952019-12-10 16:38:45 -05004342 suite.execute()
Anas Nashif7a361b82019-12-06 11:37:40 -05004343 print("")
Andrew Boie6acbe632015-07-17 12:03:52 -07004344
Anas Nashif83fc06a2019-06-22 11:04:10 -04004345 retries = retries - 1
4346 if retries == 0 or suite.total_failed == 0:
4347 break
Anas Nashife0a6a0b2018-02-15 20:07:24 -06004348
Anas Nashif83fc06a2019-06-22 11:04:10 -04004349 suite.misc_reports(options.compare_report, options.show_footprint,
Anas Nashifd9882382019-12-12 09:58:28 -05004350 options.all_deltas, options.footprint_threshold, options.last_metrics)
Andrew Boie6acbe632015-07-17 12:03:52 -07004351
Anas Nashif83a98e52019-11-24 07:42:06 -05004352 suite.duration = time.time() - start_time
4353 suite.summary(options.disable_unrecognized_section_test)
4354
Anas Nashife10b6512017-12-30 13:01:45 -05004355 if options.coverage:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004356 if options.gcov_tool is None:
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004357 use_system_gcov = False
Andrew Boie49cf4862019-07-08 12:02:13 -07004358
4359 for plat in options.coverage_platform:
Anas Nashif83fc06a2019-06-22 11:04:10 -04004360 ts_plat = suite.get_platform(plat)
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004361 if ts_plat and (ts_plat.type in {"native", "unit"}):
4362 use_system_gcov = True
Andrew Boie49cf4862019-07-08 12:02:13 -07004363
Alberto Escolar Piedrasaf30f2b2019-09-12 14:44:08 +02004364 if use_system_gcov or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
Andrew Boie49cf4862019-07-08 12:02:13 -07004365 options.gcov_tool = "gcov"
4366 else:
4367 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
Anas Nashif323161d2020-02-07 11:01:17 -05004368 "x86_64-zephyr-elf/bin/x86_64-zephyr-elf-gcov")
Andrew Boie49cf4862019-07-08 12:02:13 -07004369
Anas Nashif7a361b82019-12-06 11:37:40 -05004370 logger.info("Generating coverage files...")
Christian Taedckeed22c5e2019-11-23 16:47:33 +01004371 coverage_tool = CoverageTool.factory(options.coverage_tool)
4372 coverage_tool.add_ignore_file('generated')
4373 coverage_tool.add_ignore_directory('tests')
4374 coverage_tool.add_ignore_directory('samples')
4375 coverage_tool.generate(options.outdir)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03004376
Anas Nashif83fc06a2019-06-22 11:04:10 -04004377 if options.device_testing:
4378 print("\nHardware distribution summary:\n")
Anas Nashif5f908822019-11-25 08:19:25 -05004379 table = []
4380 header = ['Board', 'ID', 'Counter']
4381 for p in hwm.connected_hardware:
4382 if p['connected'] and p['platform'] in suite.selected_platforms:
4383 row = [p['platform'], p.get('id', None), p['counter']]
4384 table.append(row)
4385 print(tabulate(table, headers=header, tablefmt="github"))
4386
Anas Nashif56656842019-12-10 12:26:00 -05004387 suite.save_reports(options.report_name,
4388 options.report_dir,
4389 options.no_update,
4390 options.release,
4391 options.only_failed)
4392
Anas Nashif83fc06a2019-06-22 11:04:10 -04004393 if suite.total_failed or (suite.warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07004394 sys.exit(1)
4395
Anas Nashifd9882382019-12-12 09:58:28 -05004396
Andrew Boie6acbe632015-07-17 12:03:52 -07004397if __name__ == "__main__":
Andrew Boie1fe1f3a2020-01-02 19:27:40 -08004398 try:
4399 main()
4400 finally:
Peter Bigotab7773d2020-01-26 05:35:40 -06004401 if os.isatty(1): # stdout is interactive
4402 os.system("stty sane")