blob: 5fce4b42b2628d7c2154b8e17f3f1a6debe468f0 [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
Andrew Boie6acbe632015-07-17 12:03:52 -070012run the tests in the QEMU emulator.
13
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>
Andrew Boie6acbe632015-07-17 12:03:52 -070066 Length of time to run test in QEMU before automatically killing it.
67 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
144 matches. For example, if CONFIG_SOC="quark_se" then
145
146 filter = CONFIG_SOC : "quark.*"
147
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,
152running with -v or --discard-report can help show why particular test cases
153were 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
Sebastian Bøe56d74712019-01-21 15:48:46 +0100168if os.name == 'nt':
169 print("Running sanitycheck on Windows is not supported yet.")
170 print("https://github.com/zephyrproject-rtos/zephyr/issues/2664")
171 exit(1)
172
Anas Nashifaae71d72018-04-21 22:26:48 -0500173import contextlib
Anas Nashifa4c368e2018-10-15 09:45:59 -0400174import string
Anas Nashifaae71d72018-04-21 22:26:48 -0500175import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700176import argparse
Andrew Boie6acbe632015-07-17 12:03:52 -0700177import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700178import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700179import subprocess
180import multiprocessing
181import select
182import shutil
Marc Herbertaf1090c2019-04-30 14:11:29 -0700183import shlex
Andrew Boie6acbe632015-07-17 12:03:52 -0700184import signal
185import threading
186import time
Anas Nashif654ec5982019-04-11 08:38:21 -0400187import datetime
Andrew Boie6acbe632015-07-17 12:03:52 -0700188import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700189import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600190import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700191import concurrent
192import concurrent.futures
Anas Nashifb3311ed2017-04-13 14:44:48 -0400193import xml.etree.ElementTree as ET
Andrew Boie1578ef72019-07-03 10:19:29 -0700194import resource
Anas Nashife6fcc012017-05-17 09:29:09 -0400195from xml.sax.saxutils import escape
Anas Nashif035799f2017-05-13 21:31:53 -0400196from collections import OrderedDict
197from itertools import islice
Anas Nashif1a5bba72018-01-05 08:07:45 -0500198from functools import cmp_to_key
Anas Nashife24350c2018-07-11 15:09:22 -0500199from pathlib import Path
200from distutils.spawn import find_executable
Andrew Boie6acbe632015-07-17 12:03:52 -0700201
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700202import logging
Anas Nashif3ba1d432017-12-05 15:28:44 -0500203from sanity_chk import scl
204from sanity_chk import expr_parser
205
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700206log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500207logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700208
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +0300209ZEPHYR_BASE = os.environ.get("ZEPHYR_BASE")
210if not ZEPHYR_BASE:
Anas Nashif427cdd32015-08-06 07:25:42 -0400211 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700212 exit(1)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700213
Marc Herbert1c8632c2019-04-15 17:58:45 -0700214# Use this for internal comparisons; that's what canonicalization is
215# for. Don't use it when invoking other components of the build system
216# to avoid confusing and hard to trace inconsistencies in error messages
217# and logs, generated Makefiles, etc. compared to when users invoke these
218# components directly.
219# Note "normalization" is different from canonicalization, see os.path.
220canonical_zephyr_base = os.path.realpath(ZEPHYR_BASE)
221
Andrew Boie3ea78922016-03-24 14:46:00 -0700222sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
223
Andrew Boie3ea78922016-03-24 14:46:00 -0700224
Andrew Boie6acbe632015-07-17 12:03:52 -0700225VERBOSE = 0
226LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
227 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400228LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
Anas Nashif3ba1d432017-12-05 15:28:44 -0500229 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700230RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
231 "sanity_last_release.csv")
Andrew Boie6acbe632015-07-17 12:03:52 -0700232
233if os.isatty(sys.stdout.fileno()):
234 TERMINAL = True
235 COLOR_NORMAL = '\033[0m'
236 COLOR_RED = '\033[91m'
237 COLOR_GREEN = '\033[92m'
238 COLOR_YELLOW = '\033[93m'
239else:
240 TERMINAL = False
241 COLOR_NORMAL = ""
242 COLOR_RED = ""
243 COLOR_GREEN = ""
244 COLOR_YELLOW = ""
245
Anas Nashif45a97862019-01-09 08:46:42 -0500246class CMakeCacheEntry:
247 '''Represents a CMake cache entry.
248
249 This class understands the type system in a CMakeCache.txt, and
250 converts the following cache types to Python types:
251
252 Cache Type Python type
253 ---------- -------------------------------------------
254 FILEPATH str
255 PATH str
256 STRING str OR list of str (if ';' is in the value)
257 BOOL bool
258 INTERNAL str OR list of str (if ';' is in the value)
259 ---------- -------------------------------------------
260 '''
261
262 # Regular expression for a cache entry.
263 #
264 # CMake variable names can include escape characters, allowing a
265 # wider set of names than is easy to match with a regular
266 # expression. To be permissive here, use a non-greedy match up to
267 # the first colon (':'). This breaks if the variable name has a
268 # colon inside, but it's good enough.
269 CACHE_ENTRY = re.compile(
270 r'''(?P<name>.*?) # name
271 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
272 =(?P<value>.*) # value
273 ''', re.X)
274
275 @classmethod
276 def _to_bool(cls, val):
277 # Convert a CMake BOOL string into a Python bool.
278 #
279 # "True if the constant is 1, ON, YES, TRUE, Y, or a
280 # non-zero number. False if the constant is 0, OFF, NO,
281 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
282 # the suffix -NOTFOUND. Named boolean constants are
283 # case-insensitive. If the argument is not one of these
284 # constants, it is treated as a variable."
285 #
286 # https://cmake.org/cmake/help/v3.0/command/if.html
287 val = val.upper()
288 if val in ('ON', 'YES', 'TRUE', 'Y'):
289 return 1
290 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
291 return 0
292 elif val.endswith('-NOTFOUND'):
293 return 0
294 else:
295 try:
296 v = int(val)
297 return v != 0
298 except ValueError as exc:
299 raise ValueError('invalid bool {}'.format(val)) from exc
300
301 @classmethod
302 def from_line(cls, line, line_no):
303 # Comments can only occur at the beginning of a line.
304 # (The value of an entry could contain a comment character).
305 if line.startswith('//') or line.startswith('#'):
306 return None
307
308 # Whitespace-only lines do not contain cache entries.
309 if not line.strip():
310 return None
311
312 m = cls.CACHE_ENTRY.match(line)
313 if not m:
314 return None
315
316 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
317 if type_ == 'BOOL':
318 try:
319 value = cls._to_bool(value)
320 except ValueError as exc:
321 args = exc.args + ('on line {}: {}'.format(line_no, line),)
322 raise ValueError(args) from exc
323 elif type_ == 'STRING' or type_ == 'INTERNAL':
324 # If the value is a CMake list (i.e. is a string which
325 # contains a ';'), convert to a Python list.
326 if ';' in value:
327 value = value.split(';')
328
329 return CMakeCacheEntry(name, value)
330
331 def __init__(self, name, value):
332 self.name = name
333 self.value = value
334
335 def __str__(self):
336 fmt = 'CMakeCacheEntry(name={}, value={})'
337 return fmt.format(self.name, self.value)
338
339
340class CMakeCache:
341 '''Parses and represents a CMake cache file.'''
342
343 @staticmethod
344 def from_file(cache_file):
345 return CMakeCache(cache_file)
346
347 def __init__(self, cache_file):
348 self.cache_file = cache_file
349 self.load(cache_file)
350
351 def load(self, cache_file):
352 entries = []
353 with open(cache_file, 'r') as cache:
354 for line_no, line in enumerate(cache):
355 entry = CMakeCacheEntry.from_line(line, line_no)
356 if entry:
357 entries.append(entry)
358 self._entries = OrderedDict((e.name, e) for e in entries)
359
360 def get(self, name, default=None):
361 entry = self._entries.get(name)
362 if entry is not None:
363 return entry.value
364 else:
365 return default
366
367 def get_list(self, name, default=None):
368 if default is None:
369 default = []
370 entry = self._entries.get(name)
371 if entry is not None:
372 value = entry.value
373 if isinstance(value, list):
374 return value
375 elif isinstance(value, str):
376 return [value] if value else []
377 else:
378 msg = 'invalid value {} type {}'
379 raise RuntimeError(msg.format(value, type(value)))
380 else:
381 return default
382
383 def __contains__(self, name):
384 return name in self._entries
385
386 def __getitem__(self, name):
387 return self._entries[name].value
388
389 def __setitem__(self, name, entry):
390 if not isinstance(entry, CMakeCacheEntry):
391 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
392 raise TypeError(msg.format(type(entry), entry))
393 self._entries[name] = entry
394
395 def __delitem__(self, name):
396 del self._entries[name]
397
398 def __iter__(self):
399 return iter(self._entries.values())
400
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 Nashif3ba1d432017-12-05 15:28:44 -0500408
Andrew Boie6acbe632015-07-17 12:03:52 -0700409class ConfigurationError(SanityCheckException):
410 def __init__(self, cfile, message):
411 self.cfile = cfile
412 self.message = message
413
414 def __str__(self):
415 return repr(self.cfile + ": " + self.message)
416
Anas Nashif3ba1d432017-12-05 15:28:44 -0500417
Andrew Boie6acbe632015-07-17 12:03:52 -0700418class MakeError(SanityCheckException):
419 pass
420
Anas Nashif3ba1d432017-12-05 15:28:44 -0500421
Andrew Boie6acbe632015-07-17 12:03:52 -0700422class BuildError(MakeError):
423 pass
424
Anas Nashif3ba1d432017-12-05 15:28:44 -0500425
Andrew Boie6acbe632015-07-17 12:03:52 -0700426class ExecutionError(MakeError):
427 pass
428
Anas Nashif3ba1d432017-12-05 15:28:44 -0500429
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800430log_file = None
431
Andrew Boie6acbe632015-07-17 12:03:52 -0700432# Debug Functions
Anas Nashif654ec5982019-04-11 08:38:21 -0400433def info(what, show_time=True):
434 if options.timestamps and show_time:
435 date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
436 what = "{}: {}".format(date, what)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800437 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300438 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800439 if log_file:
440 log_file.write(what + "\n")
441 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700442
Anas Nashif3ba1d432017-12-05 15:28:44 -0500443
Andrew Boie6acbe632015-07-17 12:03:52 -0700444def error(what):
Anas Nashif654ec5982019-04-11 08:38:21 -0400445 if options.timestamps:
446 date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
447 what = "{}: {}".format(date, what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700448 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800449 if log_file:
450 log_file(what + "\n")
451 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700452
Anas Nashif3ba1d432017-12-05 15:28:44 -0500453
Andrew Boie08ce5a52016-02-22 13:28:10 -0800454def debug(what):
455 if VERBOSE >= 1:
456 info(what)
457
Anas Nashif3ba1d432017-12-05 15:28:44 -0500458
Andrew Boie6acbe632015-07-17 12:03:52 -0700459def verbose(what):
460 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800461 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700462
Anas Nashif576be982017-12-23 20:20:27 -0500463class HarnessImporter:
464
465 def __init__(self, name):
466 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
467 module = __import__("harness")
468 if name:
469 my_class = getattr(module, name)
470 else:
471 my_class = getattr(module, "Test")
472
473 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500474
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300475class Handler:
Anas Nashifd18ec532019-04-11 23:20:39 -0400476 def __init__(self, instance, type_str="build"):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300477 """Constructor
478
479 @param name Arbitrary name of the created thread
Anas Nashif66bdb322017-11-25 17:20:07 -0500480 @param outdir Working directory, should be where handler pid file (qemu.pid for example)
481 gets created by the build system
482 @param log_fn Absolute path to write out handler's log data
483 @param timeout Kill the handler process if it doesn't finish up within
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300484 the given number of seconds
485 """
486 self.lock = threading.Lock()
487 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500488 self.run = False
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300489 self.metrics = {}
Anas Nashifc8390f12017-11-25 17:14:12 -0500490 self.metrics["handler_time"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300491 self.metrics["ram_size"] = 0
492 self.metrics["rom_size"] = 0
Anas Nashifd18ec532019-04-11 23:20:39 -0400493 self.type_str = type_str
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300494
Anas Nashifdf7ee612018-07-07 06:09:01 -0500495 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100496 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500497 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500498
Anas Nashifd3384fb2018-02-22 06:44:16 -0600499 self.name = instance.name
500 self.instance = instance
501 self.timeout = instance.test.timeout
Anas Nashiff18ad9d2018-11-20 09:03:17 -0500502 self.sourcedir = instance.test.test_path
Anas Nashifd3384fb2018-02-22 06:44:16 -0600503 self.outdir = instance.outdir
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500504 self.log = os.path.join(self.outdir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600505 self.returncode = 0
506 self.set_state("running", {})
507
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300508 def set_state(self, state, metrics):
509 self.lock.acquire()
510 self.state = state
511 self.metrics.update(metrics)
512 self.lock.release()
513
514 def get_state(self):
515 self.lock.acquire()
516 ret = (self.state, self.metrics)
517 self.lock.release()
518 return ret
519
Anas Nashifdf7ee612018-07-07 06:09:01 -0500520class BinaryHandler(Handler):
Anas Nashifd18ec532019-04-11 23:20:39 -0400521 def __init__(self, instance, type_str):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500522 """Constructor
523
524 @param instance Test Instance
525 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400526 super().__init__(instance, type_str)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500527
528 self.valgrind = False
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100529 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500530
Jan Kowalewski265895b2019-01-07 16:40:24 +0100531 def try_kill_process_by_pid(self):
532 if self.pid_fn != None:
533 pid = int(open(self.pid_fn).read())
534 os.unlink(self.pid_fn)
535 self.pid_fn = None # clear so we don't try to kill the binary twice
536 try:
537 os.kill(pid, signal.SIGTERM)
538 except ProcessLookupError:
539 pass
540
Anas Nashifdf7ee612018-07-07 06:09:01 -0500541 def _output_reader(self, proc, harness):
542 log_out_fp = open(self.log, "wt")
543 for line in iter(proc.stdout.readline, b''):
544 verbose("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
545 log_out_fp.write(line.decode('utf-8'))
546 log_out_fp.flush()
547 harness.handle(line.decode('utf-8').rstrip())
548 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100549 try:
550 #POSIX arch based ztests end on their own,
551 #so let's give it up to 100ms to do so
552 proc.wait(0.1)
553 except subprocess.TimeoutExpired:
554 proc.terminate()
555 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500556 break
557
558 log_out_fp.close()
559
560 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500561
562 harness_name = self.instance.test.harness.capitalize()
563 harness_import = HarnessImporter(harness_name)
564 harness = harness_import.instance
565 harness.configure(self.instance)
566
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500567 if self.call_make_run:
568 if options.ninja:
569 generator_cmd = "ninja"
570 else:
571 generator_cmd = "make"
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100572 command = [generator_cmd, "run"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500573 else:
574 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500575
Alberto Escolar Piedrasb91f3742019-07-02 10:06:22 +0200576 if self.valgrind and shutil.which("valgrind"):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500577 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100578 "--leak-check=full",
579 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
580 "--log-file="+self.outdir+"/valgrind.log"
581 ] + command
Anas Nashifdf7ee612018-07-07 06:09:01 -0500582
Marc Herbertaf1090c2019-04-30 14:11:29 -0700583 verbose("Spawning process: " +
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100584 " ".join(shlex.quote(word) for word in command) + os.linesep +
585 "Spawning process in directory: " + self.outdir)
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200586
587 start_time = time.time();
588
Jan Van Winkel6b9e1602019-01-03 19:18:35 +0100589 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=self.outdir) as proc:
Marc Herbertaf1090c2019-04-30 14:11:29 -0700590 verbose("Spawning BinaryHandler Thread for %s" % self.name)
Anas Nashifdf7ee612018-07-07 06:09:01 -0500591 t = threading.Thread(target=self._output_reader, args=(proc, harness, ))
592 t.start()
593 t.join(self.timeout)
594 if t.is_alive():
Jan Kowalewski265895b2019-01-07 16:40:24 +0100595 self.try_kill_process_by_pid()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500596 proc.terminate()
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100597 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500598 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500599 proc.wait()
600 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500601
Alberto Escolar Piedras649368c2019-07-02 09:59:35 +0200602 self.metrics["handler_time"] = time.time() - start_time;
603
Anas Nashifdf7ee612018-07-07 06:09:01 -0500604 if options.enable_coverage:
605 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir,
606 "gcov", self.sourcedir, "-b", "-s", self.outdir], shell=True)
607
Jan Kowalewski265895b2019-01-07 16:40:24 +0100608 self.try_kill_process_by_pid()
609
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500610 # FIME: This is needed when killing the simulator, the console is
611 # garbled and needs to be reset. Did not find a better way to do that.
612
613 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500614 self.instance.results = harness.tests
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100615 if self.terminated==False and self.returncode != 0:
616 #When a process is killed, the default handler returns 128 + SIGTERM
617 #so in that case the return code itself is not meaningful
618 self.set_state("error", {})
619 elif harness.state:
Anas Nashifdf7ee612018-07-07 06:09:01 -0500620 self.set_state(harness.state, {})
621 else:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100622 self.set_state("timeout", {})
Anas Nashif73440ea2018-02-19 10:57:03 -0600623
624class DeviceHandler(Handler):
625
Anas Nashifd18ec532019-04-11 23:20:39 -0400626 def __init__(self, instance, type_str):
Anas Nashif73440ea2018-02-19 10:57:03 -0600627 """Constructor
628
629 @param instance Test Instance
630 """
Anas Nashifd18ec532019-04-11 23:20:39 -0400631 super().__init__(instance, type_str)
Anas Nashif73440ea2018-02-19 10:57:03 -0600632
Marti Bolivar5591ca22019-02-07 15:53:39 -0700633 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500634 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600635
Marti Bolivar5591ca22019-02-07 15:53:39 -0700636 ser_fileno = ser.fileno()
637 readlist = [halt_fileno, ser_fileno]
638
Anas Nashif73440ea2018-02-19 10:57:03 -0600639 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700640 readable, _, _ = select.select(readlist, [], [], self.timeout)
641
642 if halt_fileno in readable:
643 verbose('halted')
644 ser.close()
645 break
646 if ser_fileno not in readable:
647 continue # Timeout.
648
649 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500650 try:
651 serial_line = ser.readline()
652 except TypeError:
653 pass
Anas Nashif59237602019-03-03 10:36:35 -0500654 except serial.serialutil.SerialException:
655 ser.close()
656 break
Anas Nashif61e21632018-04-08 13:30:16 -0500657
Marti Bolivar5591ca22019-02-07 15:53:39 -0700658 # Just because ser_fileno has data doesn't mean an entire line
659 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600660 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600661 sl = serial_line.decode('utf-8', 'ignore')
662 verbose("DEVICE: {0}".format(sl.rstrip()))
663
664 log_out_fp.write(sl)
665 log_out_fp.flush()
666 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700667
Anas Nashif73440ea2018-02-19 10:57:03 -0600668 if harness.state:
669 ser.close()
670 break
671
672 log_out_fp.close()
673
674 def handle(self):
675 out_state = "failed"
676
Andy Doan79c48842019-02-08 10:09:04 -0600677 if options.west_flash is not None:
678 command = ["west", "flash", "--skip-rebuild", "-d", self.outdir]
679 # There are two ways this option is used.
680 # 1) bare: --west-flash
681 # This results in options.west_flash == []
682 # 2) with a value: --west-flash="--board-id=42"
683 # This results in options.west_flash == "--board-id=42"
684 if options.west_flash != []:
685 command.append('--')
686 command.append(options.west_flash)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600687 else:
Andy Doan79c48842019-02-08 10:09:04 -0600688 if options.ninja:
689 generator_cmd = "ninja"
690 else:
691 generator_cmd = "make"
Anas Nashifd3384fb2018-02-22 06:44:16 -0600692
Andy Doan79c48842019-02-08 10:09:04 -0600693 command = [generator_cmd, "-C", self.outdir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600694
Anas Nashif73440ea2018-02-19 10:57:03 -0600695 device = options.device_serial
696 ser = serial.Serial(
697 device,
698 baudrate=115200,
699 parity=serial.PARITY_NONE,
700 stopbits=serial.STOPBITS_ONE,
701 bytesize=serial.EIGHTBITS,
702 timeout=self.timeout
703 )
704
705 ser.flush()
706
707 harness_name = self.instance.test.harness.capitalize()
708 harness_import = HarnessImporter(harness_name)
709 harness = harness_import.instance
710 harness.configure(self.instance)
Marti Bolivar5591ca22019-02-07 15:53:39 -0700711 rpipe, wpipe = os.pipe()
Anas Nashif73440ea2018-02-19 10:57:03 -0600712
Marti Bolivar5591ca22019-02-07 15:53:39 -0700713 t = threading.Thread(target=self.monitor_serial, daemon=True,
714 args=(ser, rpipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600715 t.start()
716
Andy Doan79c48842019-02-08 10:09:04 -0600717 logging.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500718 try:
Marti Bolivar303b5222019-02-07 15:50:55 -0700719 if VERBOSE:
720 subprocess.check_call(command)
721 else:
722 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif61e21632018-04-08 13:30:16 -0500723 except subprocess.CalledProcessError:
Marti Bolivar5591ca22019-02-07 15:53:39 -0700724 os.write(wpipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600725
726 t.join(self.timeout)
727 if t.is_alive():
728 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600729
730 if ser.isOpen():
731 ser.close()
732
Anas Nashifd3384fb2018-02-22 06:44:16 -0600733 if out_state == "timeout":
734 for c in self.instance.test.cases:
735 if c not in harness.tests:
736 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500737
738 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600739 if harness.state:
740 self.set_state(harness.state, {})
741 else:
742 self.set_state(out_state, {})
743
Anas Nashif3ba1d432017-12-05 15:28:44 -0500744
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300745class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700746 """Spawns a thread to monitor QEMU output from pipes
747
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400748 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700749 We need to do this as once qemu starts, it runs forever until killed.
750 Test cases emit special messages to the console as they run, we check
751 for these to collect whether the test passed or failed.
752 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700753
754 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500755 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700756 fifo_in = fifo_fn + ".in"
757 fifo_out = fifo_fn + ".out"
758
759 # These in/out nodes are named from QEMU's perspective, not ours
760 if os.path.exists(fifo_in):
761 os.unlink(fifo_in)
762 os.mkfifo(fifo_in)
763 if os.path.exists(fifo_out):
764 os.unlink(fifo_out)
765 os.mkfifo(fifo_out)
766
767 # We don't do anything with out_fp but we need to open it for
768 # writing so that QEMU doesn't block, due to the way pipes work
769 out_fp = open(fifo_in, "wb")
770 # Disable internal buffering, we don't
771 # want read() or poll() to ever block if there is data in there
772 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800773 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700774
775 start_time = time.time()
776 timeout_time = start_time + timeout
777 p = select.poll()
778 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400779 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700780
781 metrics = {}
782 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500783 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700784 while True:
785 this_timeout = int((timeout_time - time.time()) * 1000)
786 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400787 if not out_state:
788 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700789 break
790
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500791 try:
792 c = in_fp.read(1).decode("utf-8")
793 except UnicodeDecodeError:
794 # Test is writing something weird, fail
795 out_state = "unexpected byte"
796 break
797
Andrew Boie6acbe632015-07-17 12:03:52 -0700798 if c == "":
799 # EOF, this shouldn't happen unless QEMU crashes
800 out_state = "unexpected eof"
801 break
802 line = line + c
803 if c != "\n":
804 continue
805
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300806 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700807 log_out_fp.write(line)
808 log_out_fp.flush()
809 line = line.strip()
810 verbose("QEMU: %s" % line)
811
Anas Nashif576be982017-12-23 20:20:27 -0500812 harness.handle(line)
813 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400814 # if we have registered a fail make sure the state is not
815 # overridden by a false success message coming from the
816 # testsuite
817 if out_state != 'failed':
818 out_state = harness.state
819
820 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700821 # the timeout and wait for 2 more seconds to catch anything
822 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700823 # coverage is enabled since dumping this information can
824 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500825 if not timeout_extended or harness.capture_coverage:
826 timeout_extended= True
827 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700828 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500829 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500830 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700831
832 # TODO: Add support for getting numerical performance data
833 # from test cases. Will involve extending test case reporting
834 # APIs. Add whatever gets reported to the metrics dictionary
835 line = ""
836
Anas Nashifc8390f12017-11-25 17:14:12 -0500837 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700838 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500839 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700840 handler.set_state(out_state, metrics)
841
842 log_out_fp.close()
843 out_fp.close()
844 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400845 if os.path.exists(pid_fn):
846 pid = int(open(pid_fn).read())
847 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700848
Anas Nashifd6476ee2019-04-11 11:40:09 -0400849 try:
850 if pid:
851 os.kill(pid, signal.SIGTERM)
852 except ProcessLookupError:
853 # Oh well, as long as it's dead! User probably sent Ctrl-C
854 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800855
Andrew Boie6acbe632015-07-17 12:03:52 -0700856 os.unlink(fifo_in)
857 os.unlink(fifo_out)
858
Anas Nashifd18ec532019-04-11 23:20:39 -0400859 def __init__(self, instance, type_str):
Andrew Boie6acbe632015-07-17 12:03:52 -0700860 """Constructor
861
Anas Nashifd3384fb2018-02-22 06:44:16 -0600862 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700863 """
Anas Nashif576be982017-12-23 20:20:27 -0500864
Anas Nashifd18ec532019-04-11 23:20:39 -0400865 super().__init__(instance, type_str)
Anas Nashif576be982017-12-23 20:20:27 -0500866
Andrew Boie6acbe632015-07-17 12:03:52 -0700867 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500868 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700869
870 # We pass this to QEMU which looks for fifos with .in and .out
871 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500872 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700873
Anas Nashif576be982017-12-23 20:20:27 -0500874 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700875 if os.path.exists(self.pid_fn):
876 os.unlink(self.pid_fn)
877
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500878 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500879
880 harness_import = HarnessImporter(instance.test.harness.capitalize())
881 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600882 harness.configure(self.instance)
883 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
884 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300885 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500886 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600887
888 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700889 self.thread.daemon = True
Marc Herbertaf1090c2019-04-30 14:11:29 -0700890 verbose("Spawning QEMUHandler Thread for %s 'make run'" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700891 self.thread.start()
892
Andrew Boie6acbe632015-07-17 12:03:52 -0700893 def get_fifo(self):
894 return self.fifo_fn
895
Anas Nashif3ba1d432017-12-05 15:28:44 -0500896
Andrew Boie6acbe632015-07-17 12:03:52 -0700897class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700898
Erwin Rolcb3d1272018-02-10 11:40:40 +0100899 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
900 "ccm_noinit"]
Daniel Leungc8066c52019-03-09 00:35:40 -0800901 rw_sections = ["datas", "initlevel", "exceptions", "initshell",
Andrew Boiec2e01df2018-11-12 15:16:54 -0800902 "_static_thread_area", "_k_timer_area",
Andrew Boie506f15c2018-11-08 14:44:31 -0800903 "_k_mem_slab_area", "_k_mem_pool_area", "sw_isr_table",
Andrew Boiec253a682019-01-29 12:56:02 -0800904 "_k_sem_area", "_k_mutex_area", "app_shmem_regions",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700905 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
906 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200907 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700908 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100909 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500910 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
911 'log_backends_sections', 'log_dynamic_sections',
Johann Fischerfcffebf2018-09-13 00:53:15 +0200912 'log_const_sections',"app_smem", 'shell_root_cmds_sections',
Adithya Baglody1fa8cf92018-10-19 10:15:19 -0700913 'log_const_sections',"app_smem", "font_entry_sections",
Anas Nashifdb9592a2018-10-08 10:19:41 -0400914 "priv_stacks_noinit", "_TEXT_SECTION_NAME_2",
Kumar Galae6393dd2019-02-16 10:08:33 -0600915 '_GCOV_BSS_SECTION_NAME', 'gcov', 'nocache']
Anas Nashifdb9592a2018-10-08 10:19:41 -0400916
Andrew Boie73b4ee62015-10-07 11:33:22 -0700917 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700918 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andrew Boie506f15c2018-11-08 14:44:31 -0800919 "rodata", "devconfig", "net_l2", "vector", "sw_isr_table",
Luiz Augusto von Dentza3bea882019-05-20 15:11:39 +0300920 "_bt_settings_area", "_bt_channels_area","_bt_services_area",
921 "vectors", "net_socket_register"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700922
Andrew Boie52fef672016-11-29 12:21:59 -0800923 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700924 """Constructor
925
Andrew Boiebbd670c2015-08-17 13:16:11 -0700926 @param filename Path to the output binary
927 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700928 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700929 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700930 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700931 magic = f.read(4)
932
Anas Nashifb4bdd662018-08-15 17:12:28 -0500933 try:
934 if (magic != b'\x7fELF'):
935 raise SanityRuntimeError("%s is not an ELF binary" % filename)
936 except Exception as e:
937 print(str(e))
938 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -0700939
940 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500941 # GREP can not be used as it returns an error if the symbol is not
942 # found.
943 is_xip_command = "nm " + filename + \
944 " | awk '/CONFIG_XIP/ { print $3 }'"
945 is_xip_output = subprocess.check_output(
946 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
947 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -0500948 try:
949 if is_xip_output.endswith("no symbols"):
950 raise SanityRuntimeError("%s has no symbol information" % filename)
951 except Exception as e:
952 print(str(e))
953 sys.exit(2)
954
Andrew Boie6acbe632015-07-17 12:03:52 -0700955 self.is_xip = (len(is_xip_output) != 0)
956
Andrew Boiebbd670c2015-08-17 13:16:11 -0700957 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700958 self.sections = []
959 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700960 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800961 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700962
963 self._calculate_sizes()
964
965 def get_ram_size(self):
966 """Get the amount of RAM the application will use up on the device
967
968 @return amount of RAM, in bytes
969 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700970 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700971
972 def get_rom_size(self):
973 """Get the size of the data that this application uses on device's flash
974
975 @return amount of ROM, in bytes
976 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700977 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700978
979 def unrecognized_sections(self):
980 """Get a list of sections inside the binary that weren't recognized
981
David B. Kinder29963c32017-06-16 12:32:42 -0700982 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700983 """
984 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700985 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700986 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700987 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700988 return slist
989
990 def _calculate_sizes(self):
991 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700992 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500993 objdump_output = subprocess.check_output(
994 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700995
996 for line in objdump_output:
997 words = line.split()
998
999 if (len(words) == 0): # Skip lines that are too short
1000 continue
1001
1002 index = words[0]
1003 if (not index[0].isdigit()): # Skip lines that do not start
1004 continue # with a digit
1005
1006 name = words[1] # Skip lines with section names
1007 if (name[0] == '.'): # starting with '.'
1008 continue
1009
Andrew Boie73b4ee62015-10-07 11:33:22 -07001010 # TODO this doesn't actually reflect the size in flash or RAM as
1011 # it doesn't include linker-imposed padding between sections.
1012 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001013 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001014 if size == 0:
1015 continue
1016
Andrew Boie73b4ee62015-10-07 11:33:22 -07001017 load_addr = int(words[4], 16)
1018 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001019
1020 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001021 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001022 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001023 if name in SizeCalculator.alloc_sections:
1024 self.ram_size += size
1025 stype = "alloc"
1026 elif name in SizeCalculator.rw_sections:
1027 self.ram_size += size
1028 self.rom_size += size
1029 stype = "rw"
1030 elif name in SizeCalculator.ro_sections:
1031 self.rom_size += size
1032 if not self.is_xip:
1033 self.ram_size += size
1034 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001035 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001036 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001037 if name not in self.extra_sections:
1038 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001039
Anas Nashif3ba1d432017-12-05 15:28:44 -05001040 self.sections.append({"name": name, "load_addr": load_addr,
1041 "size": size, "virt_addr": virt_addr,
1042 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001043
1044
1045class MakeGoal:
1046 """Metadata class representing one of the sub-makes called by MakeGenerator
1047
David B. Kinder29963c32017-06-16 12:32:42 -07001048 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -07001049 with TestInstances to get a complete picture of what happened during a test.
1050 MakeGenerator is used for tasks outside of building tests (such as
1051 defconfigs) which is why MakeGoal is a separate class from TestInstance.
1052 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001053
Anas Nashif4d25b502017-11-25 17:37:17 -05001054 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -07001055 self.name = name
1056 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -05001057 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001058 self.make_log = make_log
1059 self.build_log = build_log
1060 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -05001061 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001062 self.make_state = "waiting"
1063 self.failed = False
1064 self.finished = False
1065 self.reason = None
1066 self.metrics = {}
1067
1068 def get_error_log(self):
1069 if self.make_state == "waiting":
1070 # Shouldn't ever see this; breakage in the main Makefile itself.
1071 return self.make_log
1072 elif self.make_state == "building":
1073 # Failure when calling the sub-make to build the code
1074 return self.build_log
1075 elif self.make_state == "running":
Marc Herbert75276a72019-05-13 14:41:59 -07001076 # Failure in sub-make for "make run", qemu probably failed.
1077 # Return qemu's output if there is one, otherwise make's.
1078 h = Path(self.handler_log)
1079 if h.exists() and h.stat().st_size > 0:
1080 return self.handler_log
1081 else:
1082 return self.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001083 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -05001084 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -05001085 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001086
1087 def fail(self, reason):
1088 self.failed = True
1089 self.finished = True
1090 self.reason = reason
1091
1092 def success(self):
1093 self.finished = True
1094
1095 def __str__(self):
1096 if self.finished:
1097 if self.failed:
1098 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
1099 self.get_error_log())
1100 else:
1101 return "[%s] passed" % self.name
1102 else:
1103 return "[%s] in progress (%s)" % (self.name, self.make_state)
1104
1105
1106class MakeGenerator:
1107 """Generates a Makefile which just calls a bunch of sub-make sessions
1108
1109 In any given test suite we may need to build dozens if not hundreds of
1110 test cases. The cleanest way to parallelize this is to just let Make
1111 do the parallelization, sharing the jobserver among all the different
1112 sub-make targets.
1113 """
1114
1115 GOAL_HEADER_TMPL = """.PHONY: {goal}
1116{goal}:
1117"""
1118
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001119 MAKE_RULE_TMPL_CMAKE = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -04001120\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -05001121\t\t-G"{generator}"\\
Marc Herbertd7ca9032019-07-06 13:21:03 -07001122\t\t-S{directory}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001123\t\t-B{outdir}\\
1124\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
1125\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -05001126\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001127\t\t{args}\\
1128\t\t>{logfile} 2>&1
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001129"""
1130 MAKE_RULE_TMPL_BLD = """\t{generator_cmd} -C {outdir}\\
Anas Nashifa8a13882017-12-30 13:01:06 -05001131\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001132\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -07001133"""
Anas Nashif20f553f2018-03-23 11:26:41 -05001134 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
1135\t{generator_cmd} -C {outdir}\\
1136\t\t{verb} {make_args}\\
1137\t\t>>{logfile} 2>&1
1138"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001139
1140 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
1141
Anas Nashif3ba1d432017-12-05 15:28:44 -05001142 re_make = re.compile(
1143 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -07001144
Anas Nashif37f9dc52018-02-23 08:53:46 -06001145 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001146 """MakeGenerator constructor
1147
1148 @param base_outdir Intended to be the base out directory. A make.log
1149 file will be created here which contains the output of the
1150 top-level Make session, as well as the dynamic control Makefile
1151 @param verbose If true, pass V=1 to all the sub-makes which greatly
1152 increases their verbosity
1153 """
1154 self.goals = {}
1155 if not os.path.exists(base_outdir):
1156 os.makedirs(base_outdir)
1157 self.logfile = os.path.join(base_outdir, "make.log")
1158 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -06001159 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -07001160
1161 def _get_rule_header(self, name):
1162 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
1163
Anas Nashif3ba1d432017-12-05 15:28:44 -05001164 def _get_sub_make(self, name, phase, workdir, outdir,
1165 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -04001166 """
1167 @param args Arguments given to CMake
1168 @param make_args Arguments given to the Makefile generated by CMake
1169 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001170 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -05001171 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -07001172 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -05001173
1174 if self.deprecations:
1175 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -08001176
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +02001177 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -05001178
Anas Nashif25f6ab62018-03-06 07:15:11 -06001179 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001180 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -07001181 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001182 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -06001183 else:
1184 generator = "Unix Makefiles"
1185 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -07001186 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -05001187
Anas Nashif20f553f2018-03-23 11:26:41 -05001188 if phase == 'running':
1189 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
1190 generator_cmd=generator_cmd,
1191 phase=phase,
1192 goal=name,
1193 outdir=outdir,
1194 verb=verb,
1195 logfile=logfile,
1196 make_args=make_args
1197 )
1198 else:
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001199 cmake_rule = MakeGenerator.MAKE_RULE_TMPL_CMAKE.format(
Anas Nashif20f553f2018-03-23 11:26:41 -05001200 generator=generator,
Anas Nashif20f553f2018-03-23 11:26:41 -05001201 phase=phase,
1202 goal=name,
1203 outdir=outdir,
1204 cflags=cflags,
1205 ldflags=ldflags,
1206 directory=workdir,
Anas Nashif20f553f2018-03-23 11:26:41 -05001207 args=args,
1208 logfile=logfile,
Anas Nashif20f553f2018-03-23 11:26:41 -05001209 )
Andrew Boie6acbe632015-07-17 12:03:52 -07001210
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001211 if options.cmake_only:
1212 build_rule = ""
1213 else:
1214 build_rule = MakeGenerator.MAKE_RULE_TMPL_BLD.format(
1215 generator_cmd=generator_cmd,
1216 outdir=outdir,
1217 verb=verb,
1218 make_args=make_args,
1219 logfile=logfile,
1220 )
1221
1222 return cmake_rule + build_rule
1223
Andrew Boie6acbe632015-07-17 12:03:52 -07001224 def _get_rule_footer(self, name):
1225 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
1226
Anas Nashif3ba1d432017-12-05 15:28:44 -05001227 def add_build_goal(self, name, directory, outdir,
1228 args, buildlog, make_args=""):
Anas Nashif13773752018-07-06 18:20:23 -05001229 """Add a goal to invoke a build session
Andrew Boie6acbe632015-07-17 12:03:52 -07001230
1231 @param name A unique string name for this build goal. The results
1232 dictionary returned by execute() will be keyed by this name.
1233 @param directory Absolute path to working directory, will be passed
1234 to make -C
1235 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001236 cmake via -B=<path>
1237 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -07001238 environment variables or specific Make goals
1239 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001240
Anas Nashif13773752018-07-06 18:20:23 -05001241 if not os.path.exists(outdir):
1242 os.makedirs(outdir)
1243
1244 build_logfile = os.path.join(outdir, buildlog)
1245 text = self._get_rule_header(name)
1246 text += self._get_sub_make(name, "building", directory, outdir, build_logfile,
1247 args, make_args=make_args)
1248 text += self._get_rule_footer(name)
1249
1250 self.goals[name] = MakeGoal( name, text, None, self.logfile, build_logfile, None, None)
1251
1252 def add_goal(self, instance, type, args, make_args=""):
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001253
Anas Nashif13773752018-07-06 18:20:23 -05001254 """Add a goal to build a Zephyr project and then run it using a handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001255
1256 The generated make goal invokes Make twice, the first time it will
1257 build the default goal, and the second will invoke the 'qemu' goal.
Anas Nashif13773752018-07-06 18:20:23 -05001258 The output of the handler session will be monitored, and terminated
Andrew Boie6acbe632015-07-17 12:03:52 -07001259 either upon pass/fail result of the test program, or the timeout
1260 is reached.
1261
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001262 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 """
1264
Anas Nashif576be982017-12-23 20:20:27 -05001265 name = instance.name
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001266 directory = instance.test.test_path
Anas Nashif576be982017-12-23 20:20:27 -05001267 outdir = instance.outdir
1268
Andrew Boie6acbe632015-07-17 12:03:52 -07001269 build_logfile = os.path.join(outdir, "build.log")
1270 run_logfile = os.path.join(outdir, "run.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001271
Anas Nashif13773752018-07-06 18:20:23 -05001272 if not os.path.exists(outdir):
1273 os.makedirs(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001274
Anas Nashif13773752018-07-06 18:20:23 -05001275 handler = None
1276 if type == "qemu":
Anas Nashifd18ec532019-04-11 23:20:39 -04001277 handler = QEMUHandler(instance, "qemu")
Anas Nashif13773752018-07-06 18:20:23 -05001278 elif type == "native":
Anas Nashifd18ec532019-04-11 23:20:39 -04001279 handler = BinaryHandler(instance, "native")
Marc Herbert9e573382019-07-03 12:49:42 -07001280 # defined by __build_dir in cmake/boilerplate.cmake
Anas Nashifdf7ee612018-07-07 06:09:01 -05001281 handler.binary = os.path.join(outdir, "zephyr", "zephyr.exe")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001282 if options.enable_coverage:
1283 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001284 elif type == "nsim":
Anas Nashifd18ec532019-04-11 23:20:39 -04001285 handler = BinaryHandler(instance, "nsim")
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001286 handler.call_make_run = True
Anas Nashif13773752018-07-06 18:20:23 -05001287 elif type == "unit":
Anas Nashifd18ec532019-04-11 23:20:39 -04001288 handler = BinaryHandler(instance, "unit")
Anas Nashifdf7ee612018-07-07 06:09:01 -05001289 handler.binary = os.path.join(outdir, "testbinary")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001290 if options.enable_coverage:
1291 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif13773752018-07-06 18:20:23 -05001292 elif type == "device":
Anas Nashifd18ec532019-04-11 23:20:39 -04001293 handler = DeviceHandler(instance, "device")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001294 elif type == "renode":
Anas Nashifd18ec532019-04-11 23:20:39 -04001295 handler = BinaryHandler(instance, "renode")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001296 handler.pid_fn = os.path.join(instance.outdir, "renode.pid")
1297 handler.call_make_run = True
Anas Nashif576be982017-12-23 20:20:27 -05001298
Anas Nashif13773752018-07-06 18:20:23 -05001299 if type == 'qemu':
1300 args.append("QEMU_PIPE=%s" % handler.get_fifo())
1301
Andy Doancbecadd2019-02-08 10:19:10 -06001302 text = self._get_rule_header(name)
1303 if not options.test_only:
1304 text += self._get_sub_make(name, "building", directory, outdir,
1305 build_logfile, args, make_args=make_args)
Anas Nashif13773752018-07-06 18:20:23 -05001306 if handler and handler.run:
1307 text += self._get_sub_make(name, "running", directory,
1308 outdir, run_logfile,
1309 args, make_args="run")
Anas Nashif4d25b502017-11-25 17:37:17 -05001310
Anas Nashif13773752018-07-06 18:20:23 -05001311 text += self._get_rule_footer(name)
Anas Nashif73440ea2018-02-19 10:57:03 -06001312
Anas Nashif73440ea2018-02-19 10:57:03 -06001313 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001314 run_logfile, handler.log if handler else None)
Anas Nashif73440ea2018-02-19 10:57:03 -06001315
Anas Nashif13773752018-07-06 18:20:23 -05001316
Anas Nashif37f9dc52018-02-23 08:53:46 -06001317 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001318 """Add a goal to build/test a TestInstance object
1319
1320 @param ti TestInstance object to build. The status dictionary returned
1321 by execute() will be keyed by its .name field.
1322 """
1323 args = ti.test.extra_args[:]
Anas Nashiff9e73c92019-02-12 13:59:08 -05001324
1325 # merge overlay files into one variable
1326 overlays = ""
1327 idx = 0
1328 for a in args:
1329 m = re.search('OVERLAY_CONFIG="(.*)"', a)
1330 if m:
1331 overlays += m.group(1)
1332 del args[idx]
1333 idx += 1
1334
Anas Nashif3cbffef2018-11-07 23:50:54 -05001335 if len(ti.test.extra_configs) > 0 or options.coverage:
Anas Nashiff9e73c92019-02-12 13:59:08 -05001336 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
1337 os.path.join(ti.outdir, "overlay.conf")))
Anas Nashiffa695d22017-10-04 16:14:27 -04001338
Anas Nashif3cbffef2018-11-07 23:50:54 -05001339 if ti.test.type == "unit" and options.enable_coverage:
1340 args.append("COVERAGE=1")
1341
Anas Nashiffb91ad62017-10-31 08:33:17 -04001342 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001343 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001344
Anas Nashif37f9dc52018-02-23 08:53:46 -06001345 do_build_only = ti.build_only or options.build_only
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001346 do_run = not do_build_only and not options.cmake_only
Andy Rossdc4151f2019-01-03 14:17:43 -08001347 skip_slow = ti.test.slow and not options.enable_slow
Anas Nashif5df8cff2018-02-23 08:37:14 -06001348
Anas Nashif13773752018-07-06 18:20:23 -05001349 # FIXME: Need refactoring and cleanup
1350 type = None
Anas Nashif5df8cff2018-02-23 08:37:14 -06001351 if ti.platform.qemu_support and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001352 type = "qemu"
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001353 elif ti.test.type == "unit":
Anas Nashif13773752018-07-06 18:20:23 -05001354 type = "unit"
Anas Nashif5df8cff2018-02-23 08:37:14 -06001355 elif ti.platform.type == "native" and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001356 type = "native"
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001357 elif ti.platform.simulation == "nsim" and do_run:
Anas Nashife24350c2018-07-11 15:09:22 -05001358 if find_executable("nsimdrv"):
1359 type = "nsim"
Jan Kowalewski265895b2019-01-07 16:40:24 +01001360 elif ti.platform.simulation == "renode" and do_run:
1361 if find_executable("renode"):
1362 type = "renode"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001363 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif13773752018-07-06 18:20:23 -05001364 type = "device"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001365
Andy Rossdc4151f2019-01-03 14:17:43 -08001366 if not skip_slow:
1367 self.add_goal(ti, type, args)
1368 else:
1369 verbose("Skipping slow test: " + ti.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001370
1371 def execute(self, callback_fn=None, context=None):
1372 """Execute all the registered build goals
1373
1374 @param callback_fn If not None, a callback function will be called
1375 as individual goals transition between states. This function
1376 should accept two parameters: a string state and an arbitrary
1377 context object, supplied here
1378 @param context Context object to pass to the callback function.
1379 Type and semantics are specific to that callback function.
1380 @return A dictionary mapping goal names to final status.
1381 """
1382
Andrew Boie08ce5a52016-02-22 13:28:10 -08001383 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001384 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001385 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001386 # Create our dynamic Makefile and execute it.
1387 # Watch stderr output which is where we will keep
1388 # track of build state
Marc Herbert6f011c92019-03-20 13:58:55 -07001389 tf.write('\n# Generated by %s which is expected\n' % __file__)
Marc Herbertaf1090c2019-04-30 14:11:29 -07001390 tf.write('# to create QEMU_PIPE, spawn zephyr.exe, etc. \n\n')
Marc Herbert6f011c92019-03-20 13:58:55 -07001391 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07001392 tf.write(goal.text)
Marc Herbert6f011c92019-03-20 13:58:55 -07001393 tf.write("all: %s\n" % (" ".join(sorted(self.goals.keys()))))
Andrew Boie6acbe632015-07-17 12:03:52 -07001394 tf.flush()
1395
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03001396 cmd = ["make", "-k", "-j", str(JOBS), "-f", tf.name, "all"]
Andy Rossdec163f2018-05-21 10:12:59 -07001397
Anas Nashiff2cb20c2019-06-18 14:45:40 -04001398 # assure language neutral environment
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001399 make_env = os.environ.copy()
1400 make_env['LC_MESSAGES'] = 'C.UTF-8'
Marc Herbert9e573382019-07-03 12:49:42 -07001401 verbose("In %s, spawning: " % os.getcwd()
1402 + " ".join(shlex.quote(word)for word in cmd))
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001403 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1404 stdout=devnull, env=make_env)
Andrew Boie6acbe632015-07-17 12:03:52 -07001405
1406 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001407 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001408 make_log.write(line)
1409 verbose("MAKE: " + repr(line.strip()))
1410 m = MakeGenerator.re_make.match(line)
1411 if not m:
1412 continue
1413
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001414 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001415 if error:
1416 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001417 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001418 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001419 # nonzero status.
1420 # Need to distinguish this case from a compilation failure.
Anas Nashif042d9b72019-04-11 10:31:01 -04001421 if goal.make_state == "building":
1422 goal.fail("build_error")
1423 elif goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001424 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001425 else:
Anas Nashif042d9b72019-04-11 10:31:01 -04001426 goal.fail("unknown_error")
1427
Andrew Boie6acbe632015-07-17 12:03:52 -07001428 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001429 goal = self.goals[name]
1430 goal.make_state = state
1431
Andrew Boie6acbe632015-07-17 12:03:52 -07001432 if state == "finished":
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001433 if goal.handler and not options.cmake_only:
Anas Nashif576be982017-12-23 20:20:27 -05001434 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001435 goal.handler.handle()
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001436 goal.handler_log = goal.handler.log
Anas Nashifcc164222017-12-26 11:02:46 -05001437
Anas Nashif4d25b502017-11-25 17:37:17 -05001438 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001439 goal.metrics.update(metrics)
1440 if thread_status == "passed":
1441 goal.success()
1442 else:
1443 goal.fail(thread_status)
1444 else:
1445 goal.success()
1446
1447 if callback_fn:
1448 callback_fn(context, self.goals, goal)
1449
1450 p.wait()
1451 return self.goals
1452
1453
1454# "list" - List of strings
1455# "list:<type>" - List of <type>
1456# "set" - Set of unordered, unique strings
1457# "set:<type>" - Set of <type>
1458# "float" - Floating point
1459# "int" - Integer
1460# "bool" - Boolean
1461# "str" - String
1462
1463# XXX Be sure to update __doc__ if you change any of this!!
1464
Anas Nashif3ba1d432017-12-05 15:28:44 -05001465platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
Anas Nashif924a4e72018-10-18 12:25:55 -04001466 "supported_toolchains": {"type": "list", "default": []},
1467 "env": {"type": "list", "default": []}
1468 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001469
Anas Nashif3ba1d432017-12-05 15:28:44 -05001470testcase_valid_keys = {"tags": {"type": "set", "required": False},
1471 "type": {"type": "str", "default": "integration"},
1472 "extra_args": {"type": "list"},
1473 "extra_configs": {"type": "list"},
1474 "build_only": {"type": "bool", "default": False},
1475 "build_on_all": {"type": "bool", "default": False},
1476 "skip": {"type": "bool", "default": False},
1477 "slow": {"type": "bool", "default": False},
1478 "timeout": {"type": "int", "default": 60},
1479 "min_ram": {"type": "int", "default": 8},
1480 "depends_on": {"type": "set"},
1481 "min_flash": {"type": "int", "default": 32},
1482 "arch_whitelist": {"type": "set"},
1483 "arch_exclude": {"type": "set"},
1484 "extra_sections": {"type": "list", "default": []},
1485 "platform_exclude": {"type": "set"},
1486 "platform_whitelist": {"type": "set"},
1487 "toolchain_exclude": {"type": "set"},
1488 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001489 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001490 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301491 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001492 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001493
1494
1495class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001496 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001497 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001498
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001499 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001500 """Instantiate a new SanityConfigParser object
1501
Anas Nashifa792a3d2017-04-04 18:47:49 -04001502 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001503 """
Anas Nashif255625b2017-12-05 15:08:26 -05001504 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001505 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001506 self.tests = {}
1507 self.common = {}
1508 if 'tests' in self.data:
1509 self.tests = self.data['tests']
1510 if 'common' in self.data:
1511 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001512
1513 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001514 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001515 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001516 if typestr == "str":
1517 return v
1518
1519 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001520 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001521
1522 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001523 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001524
1525 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001526 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001527
Anas Nashif3ba1d432017-12-05 15:28:44 -05001528 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001529 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001530 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001531 vs = v.split()
1532 if len(typestr) > 4 and typestr[4] == ":":
1533 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1534 else:
1535 return vs
1536
1537 elif typestr.startswith("set"):
1538 vs = v.split()
1539 if len(typestr) > 3 and typestr[3] == ":":
1540 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1541 else:
1542 return set(vs)
1543
Anas Nashif576be982017-12-23 20:20:27 -05001544 elif typestr.startswith("map"):
1545 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001546 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001547 raise ConfigurationError(
1548 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001549
Anas Nashifb4754ed2017-12-05 17:27:58 -05001550 def get_test(self, name, valid_keys):
1551 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001552
Anas Nashifb4754ed2017-12-05 17:27:58 -05001553 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001554 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001555 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001556 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001557 here, it will generate an error. Each value in this dictionary
1558 is another dictionary containing metadata:
1559
1560 "default" - Default value if not given
1561 "type" - Data type to convert the text value to. Simple types
1562 supported are "str", "float", "int", "bool" which will get
1563 converted to respective Python data types. "set" and "list"
1564 may also be specified which will split the value by
1565 whitespace (but keep the elements as strings). finally,
1566 "list:<type>" and "set:<type>" may be given which will
1567 perform a type conversion after splitting the value up.
1568 "required" - If true, raise an error if not defined. If false
1569 and "default" isn't specified, a type conversion will be
1570 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001571 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001572 type conversion and default values filled in per valid_keys
1573 """
1574
1575 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001576 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001577 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001578
Anas Nashifb4754ed2017-12-05 17:27:58 -05001579 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001580 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001581 raise ConfigurationError(
1582 self.filename,
1583 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001584 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001585
Anas Nashiffa695d22017-10-04 16:14:27 -04001586 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001587 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001588 d[k] += " " + v
1589 else:
1590 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001591
Andrew Boie08ce5a52016-02-22 13:28:10 -08001592 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001593 if k not in d:
1594 if "required" in kinfo:
1595 required = kinfo["required"]
1596 else:
1597 required = False
1598
1599 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001600 raise ConfigurationError(
1601 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001602 "missing required value for '%s' in test '%s'" %
1603 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001604 else:
1605 if "default" in kinfo:
1606 default = kinfo["default"]
1607 else:
1608 default = self._cast_value("", kinfo["type"])
1609 d[k] = default
1610 else:
1611 try:
1612 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001613 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001614 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001615 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1616 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001617
1618 return d
1619
1620
1621class Platform:
1622 """Class representing metadata for a particular platform
1623
Anas Nashifc7406082015-12-13 15:00:31 -05001624 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001625
1626 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001627 os.path.join(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001628 ZEPHYR_BASE,
Anas Nashif3ba1d432017-12-05 15:28:44 -05001629 "scripts",
1630 "sanity_chk",
1631 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001632
Anas Nashifa792a3d2017-04-04 18:47:49 -04001633 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001634 """Constructor.
1635
Anas Nashif877d3ca2017-12-05 17:39:29 -05001636 @param cfile Path to platform configuration file, which gives
1637 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001638 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001639 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001640 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001641
Anas Nashif255625b2017-12-05 15:08:26 -05001642 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001643 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001644 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001645 self.ram = data.get("ram", 128)
1646 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001647 self.ignore_tags = testing.get("ignore_tags", [])
1648 self.default = testing.get("default", False)
1649 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001650 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001651 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001652 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001653 for item in supp_feature.split(":"):
1654 self.supported.add(item)
1655
Anas Nashif8acdbd72018-01-04 14:15:22 -05001656 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001657 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001658 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001659 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001660 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001661 self.env = data.get("env", [])
1662 self.env_satisfied = True
1663 for env in self.env:
1664 if os.environ.get(env, None) == None:
1665 self.env_satisfied = False
Andrew Boie41878222016-11-03 11:58:53 -07001666 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001667 pass
1668
Andrew Boie6acbe632015-07-17 12:03:52 -07001669 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001670 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001671
1672
1673class Architecture:
1674 """Class representing metadata for a particular architecture
1675 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001676
Anas Nashifa792a3d2017-04-04 18:47:49 -04001677 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001678 """Architecture constructor
1679
Anas Nashif877d3ca2017-12-05 17:39:29 -05001680 @param name String name for this architecture
1681 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001682 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001683 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001684
Anas Nashifa792a3d2017-04-04 18:47:49 -04001685 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001686
1687 def __repr__(self):
1688 return "<arch %s>" % self.name
1689
1690
1691class TestCase:
1692 """Class representing a test application
1693 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001694
Anas Nashif7fae29c2017-10-09 13:19:12 -04001695 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001696 """TestCase constructor.
1697
Anas Nashif877d3ca2017-12-05 17:39:29 -05001698 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001699 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001700 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001701
Andrew Boie6acbe632015-07-17 12:03:52 -07001702 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001703 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001704 the test case is <workdir>/<name>.
1705
Marc Herbert1c8632c2019-04-15 17:58:45 -07001706 @param testcase_root os.path.abspath() of one of the --testcase-root
1707 @param workdir Sub-directory of testcase_root where the
1708 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001709 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001710 in the test case configuration file. For many test cases that just
1711 define one test, can be anything and is usually "test". This is
1712 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001713 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001714 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001715 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001716 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001717 self.test_path = os.path.join(testcase_root, workdir)
1718
Anas Nashifaae71d72018-04-21 22:26:48 -05001719 self.id = name
1720 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001721 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001722 self.tags = tc_dict["tags"]
1723 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001724 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001725 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001726 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001727 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001728 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001729 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001730 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1731 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001732 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001733 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001734 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001735 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001736 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001737 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001738 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001739 self.min_ram = tc_dict["min_ram"]
1740 self.depends_on = tc_dict["depends_on"]
1741 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001742 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001743
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001744 self.name = self.get_unique(testcase_root, workdir, name)
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001745
Anas Nashif2cf0df02015-10-10 09:29:43 -04001746 self.defconfig = {}
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001747 self.dt_config = {}
Anas Nashif45a97862019-01-09 08:46:42 -05001748 self.cmake_cache = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001749 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001750
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001751
1752 def get_unique(self, testcase_root, workdir, name):
1753
Marc Herbert1c8632c2019-04-15 17:58:45 -07001754 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001755 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001756 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001757 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001758 relative_tc_root = os.path.relpath(canonical_testcase_root,
1759 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001760 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001761 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001762
Marc Herbert1c8632c2019-04-15 17:58:45 -07001763 # workdir can be "."
1764 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001765 return unique
1766
Anas Nashifaae71d72018-04-21 22:26:48 -05001767 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001768 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001769 # do not match until end-of-line, otherwise we won't allow
1770 # stc_regex below to catch the ones that are declared in the same
1771 # line--as we only search starting the end of this match
1772 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001773 re.MULTILINE)
1774 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001775 br"^\s*" # empy space at the beginning is ok
1776 # catch the case where it is declared in the same sentence, e.g:
1777 #
1778 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1779 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1780 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1781 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1782 # Consume the argument that becomes the extra testcse
1783 br"\(\s*"
1784 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1785 # _setup_teardown() variant has two extra arguments that we ignore
1786 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1787 br"\s*\)",
1788 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001789 re.MULTILINE)
1790 suite_run_regex = re.compile(
1791 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1792 re.MULTILINE)
1793 achtung_regex = re.compile(
1794 br"(#ifdef|#endif)",
1795 re.MULTILINE)
1796 warnings = None
1797
1798 with open(inf_name) as inf:
1799 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1800 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001801 suite_regex_match = suite_regex.search(main_c)
1802 if not suite_regex_match:
1803 # can't find ztest_test_suite, maybe a client, because
1804 # it includes ztest.h
1805 return None, None
1806
1807 suite_run_match = suite_run_regex.search(main_c)
1808 if not suite_run_match:
1809 raise ValueError("can't find ztest_run_test_suite")
1810
1811 achtung_matches = re.findall(
1812 achtung_regex,
1813 main_c[suite_regex_match.end():suite_run_match.start()])
1814 if achtung_matches:
1815 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001816 % ", ".join(set([
1817 match.decode() for match in achtung_matches
1818 ]))
1819 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001820 stc_regex,
1821 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001822 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001823 return matches, warnings
1824
1825 def scan_path(self, path):
1826 subcases = []
1827 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1828 try:
1829 _subcases, warnings = self.scan_file(filename)
1830 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001831 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001832 if _subcases:
1833 subcases += _subcases
1834 except ValueError as e:
Flavio Ceolinbf878ce2019-04-19 22:24:09 -07001835 error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001836 return subcases
1837
1838
1839 def parse_subcases(self):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001840 results = self.scan_path(self.test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001841 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001842 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001843 self.cases.append(name)
1844
Anas Nashiff16e92c2019-03-31 16:58:12 -04001845 if not results:
1846 self.cases.append(self.id)
1847
Anas Nashifaae71d72018-04-21 22:26:48 -05001848
Anas Nashif75547e22018-02-24 08:32:14 -06001849 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001850 return self.name
1851
1852
Andrew Boie6acbe632015-07-17 12:03:52 -07001853class TestInstance:
1854 """Class representing the execution of a particular TestCase on a platform
1855
1856 @param test The TestCase object we want to build/execute
1857 @param platform Platform object that we want to build and run against
1858 @param base_outdir Base directory for all test results. The actual
1859 out directory used is <outdir>/<platform>/<test case name>
1860 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001861
Anas Nashif37f9dc52018-02-23 08:53:46 -06001862 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001863 self.test = test
1864 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001865 self.name = os.path.join(platform.name, test.name)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001866 self.outdir = os.path.join(base_outdir, platform.name, test.name)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001867
1868 self.build_only = options.build_only or test.build_only \
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001869 or self.check_dependency() or options.cmake_only
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001870 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001871
Marc Herbert0f7255c2019-04-05 14:14:21 -07001872 def __lt__(self, other):
1873 return self.name < other.name
1874
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001875 def check_dependency(self):
1876 build_only = False
1877 if self.test.harness == 'console':
1878 if "fixture" in self.test.harness_config:
1879 fixture = self.test.harness_config['fixture']
1880 if fixture not in options.fixture:
1881 build_only = True
1882 elif self.test.harness:
1883 build_only = True
1884
1885 return build_only
1886
Anas Nashifdbd76492018-11-23 20:24:19 -05001887 def create_overlay(self, platform):
Anas Nashif3cbffef2018-11-07 23:50:54 -05001888 file = os.path.join(self.outdir, "overlay.conf")
1889 os.makedirs(self.outdir, exist_ok=True)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001890 with open(file, "w") as f:
1891 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001892
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001893 if len(self.test.extra_configs) > 0:
1894 content = "\n".join(self.test.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001895
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001896 if options.enable_coverage:
1897 if platform in options.coverage_platform:
1898 content = content + "\nCONFIG_COVERAGE=y"
1899
1900 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001901
Andrew Boie6acbe632015-07-17 12:03:52 -07001902 def calculate_sizes(self):
1903 """Get the RAM/ROM sizes of a test case.
1904
1905 This can only be run after the instance has been executed by
1906 MakeGenerator, otherwise there won't be any binaries to measure.
1907
1908 @return A SizeCalculator object
1909 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001910 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001911 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001912 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001913 if (len(fns) != 1):
1914 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001915 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001916
1917 def __repr__(self):
1918 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1919
1920
Andrew Boie4ef16c52015-08-28 12:36:03 -07001921def defconfig_cb(context, goals, goal):
1922 if not goal.failed:
1923 return
1924
1925 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001926 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001927 if INLINE_LOGS:
1928 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001929 data = fp.read()
1930 sys.stdout.write(data)
1931 if log_file:
1932 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001933 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001934 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001935
Andrew Boie6acbe632015-07-17 12:03:52 -07001936
1937class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001938 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001939 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001940
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001941 yaml_tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001942 os.path.join(ZEPHYR_BASE,
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001943 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001944
Anas Nashif37f9dc52018-02-23 08:53:46 -06001945 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001946 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001947 self.arches = {}
1948 self.testcases = {}
1949 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001950 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001951 self.instances = {}
1952 self.goals = None
1953 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001954 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001955
Andrew Boie3d348712016-04-08 11:52:13 -07001956 for testcase_root in testcase_roots:
1957 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001958
Andrew Boie3d348712016-04-08 11:52:13 -07001959 debug("Reading test case configuration files under %s..." %
1960 testcase_root)
1961 for dirpath, dirnames, filenames in os.walk(testcase_root,
1962 topdown=True):
1963 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001964 if 'sample.yaml' in filenames:
1965 filename = 'sample.yaml'
1966 elif 'testcase.yaml' in filenames:
1967 filename = 'testcase.yaml'
1968 else:
1969 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001970
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001971 verbose("Found possible test case in " + dirpath)
1972 dirnames[:] = []
1973 yaml_path = os.path.join(dirpath, filename)
1974 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001975 parsed_data = SanityConfigParser(
1976 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001977
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001978 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001979
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001980 for name in parsed_data.tests.keys():
1981 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1982 tc = TestCase(testcase_root, workdir, name, tc_dict,
1983 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001984 tc.parse_subcases()
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001985 self.testcases[tc.name] = tc
1986
1987 except Exception as e:
1988 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001989 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001990
Andrew Boie6acbe632015-07-17 12:03:52 -07001991
Anas Nashif86c8e232017-10-09 13:42:28 -04001992 for board_root in board_root_list:
1993 board_root = os.path.abspath(board_root)
1994
Anas Nashif3ba1d432017-12-05 15:28:44 -05001995 debug(
1996 "Reading platform configuration files under %s..." %
1997 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001998 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1999 verbose("Found plaform configuration " + fn)
2000 try:
2001 platform = Platform(fn)
Anas Nashiff3d48e12018-07-24 08:14:42 -05002002 if platform.sanitycheck:
2003 self.platforms.append(platform)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05002004 except RuntimeError as e:
2005 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05002006 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002007
Anas Nashifa792a3d2017-04-04 18:47:49 -04002008 arches = []
2009 for p in self.platforms:
2010 arches.append(p.arch)
2011 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002012 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04002013 arch = Architecture(a, aplatforms)
2014 self.arches[a] = arch
2015
Andrew Boie6acbe632015-07-17 12:03:52 -07002016 self.instances = {}
2017
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002018 def get_platform(self, name):
2019 selected_platform = None
2020 for platform in self.platforms:
2021 if platform.name == name:
2022 selected_platform = platform
2023 break
2024 return selected_platform
Anas Nashifb4bdd662018-08-15 17:12:28 -05002025
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002026 def get_last_failed(self):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002027 try:
2028 if not os.path.exists(LAST_SANITY):
2029 raise SanityRuntimeError("Couldn't find last sanity run.")
2030 except Exception as e:
2031 print(str(e))
2032 sys.exit(2)
2033
Andrew Boie6acbe632015-07-17 12:03:52 -07002034 result = []
2035 with open(LAST_SANITY, "r") as fp:
2036 cr = csv.DictReader(fp)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002037 instance_list = []
Andrew Boie6acbe632015-07-17 12:03:52 -07002038 for row in cr:
2039 if row["passed"] == "True":
2040 continue
2041 test = row["test"]
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002042 platform = self.get_platform(row["platform"])
2043 instance = TestInstance(self.testcases[test], platform, self.outdir)
2044 instance.create_overlay(platform.name)
2045 instance_list.append(instance)
2046 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07002047
Anas Nashifbd166f42017-09-02 12:32:08 -04002048 def load_from_file(self, file):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002049 try:
2050 if not os.path.exists(file):
2051 raise SanityRuntimeError(
2052 "Couldn't find input file with list of tests.")
2053 except Exception as e:
2054 print(str(e))
2055 sys.exit(2)
2056
Anas Nashifbd166f42017-09-02 12:32:08 -04002057 with open(file, "r") as fp:
2058 cr = csv.reader(fp)
2059 instance_list = []
2060 for row in cr:
2061 name = os.path.join(row[0], row[1])
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002062 platform = self.get_platform(row[2])
2063 instance = TestInstance(self.testcases[name], platform, self.outdir)
2064 instance.create_overlay(platform.name)
Anas Nashifbd166f42017-09-02 12:32:08 -04002065 instance_list.append(instance)
2066 self.add_instances(instance_list)
2067
Anas Nashif4f028882017-12-30 11:48:43 -05002068 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04002069
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002070 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2071 os.environ.get("ZEPHYR_GCC_VARIANT", None)
Anas Nashifb4bdd662018-08-15 17:12:28 -05002072
Sebastian Bøe5681f872018-10-12 16:03:49 +02002073 if toolchain == "gccarmemb":
2074 # Remove this translation when gccarmemb is no longer supported.
2075 toolchain = "gnuarmemb"
Anas Nashifb4bdd662018-08-15 17:12:28 -05002076
2077 try:
2078 if not toolchain:
2079 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
2080 except Exception as e:
2081 print(str(e))
2082 sys.exit(2)
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002083
2084
Andrew Boie6acbe632015-07-17 12:03:52 -07002085 instances = []
2086 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05002087 platform_filter = options.platform
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002088 testcase_filter = run_individual_tests
Anas Nashif4f028882017-12-30 11:48:43 -05002089 arch_filter = options.arch
2090 tag_filter = options.tag
2091 exclude_tag = options.exclude_tag
2092 config_filter = options.config
2093 extra_args = options.extra_args
2094 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04002095
Andrew Boie6acbe632015-07-17 12:03:52 -07002096 verbose("platform filter: " + str(platform_filter))
2097 verbose(" arch_filter: " + str(arch_filter))
2098 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04002099 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07002100 verbose(" config_filter: " + str(config_filter))
2101
Andrew Boie821d8322016-03-22 10:08:35 -07002102 default_platforms = False
2103
2104 if all_plats:
2105 info("Selecting all possible platforms per test case")
2106 # When --all used, any --platform arguments ignored
2107 platform_filter = []
2108 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07002109 info("Selecting default platforms per test case")
2110 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07002111
Sebastian Bøe781e3982017-11-09 11:43:33 +01002112 mg = MakeGenerator(self.outdir)
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002113 defconfig_list = {}
2114 dt_list = {}
Anas Nashif45a97862019-01-09 08:46:42 -05002115 cmake_list = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08002116 for tc_name, tc in self.testcases.items():
2117 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04002118 for plat in arch.platforms:
2119 instance = TestInstance(tc, plat, self.outdir)
2120
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002121 if (arch_name == "unit") != (tc.type == "unit"):
2122 continue
2123
Anas Nashifbfab06b2017-06-22 09:22:24 -04002124 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002125 platform_filter = []
2126
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002127 if tc.skip:
2128 continue
2129
Anas Nashif2cf0df02015-10-10 09:29:43 -04002130 if tag_filter and not tc.tags.intersection(tag_filter):
2131 continue
2132
Anas Nashifdfa86e22016-10-24 17:08:56 -04002133 if exclude_tag and tc.tags.intersection(exclude_tag):
2134 continue
2135
Anas Nashif2cf0df02015-10-10 09:29:43 -04002136 if testcase_filter and tc_name not in testcase_filter:
2137 continue
2138
Anas Nashif2cf0df02015-10-10 09:29:43 -04002139 if arch_filter and arch_name not in arch_filter:
2140 continue
2141
2142 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2143 continue
2144
2145 if tc.arch_exclude and arch.name in tc.arch_exclude:
2146 continue
2147
2148 if tc.platform_exclude and plat.name in tc.platform_exclude:
2149 continue
2150
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002151 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2152 continue
2153
Anas Nashif2cf0df02015-10-10 09:29:43 -04002154 if platform_filter and plat.name not in platform_filter:
2155 continue
2156
Anas Nashif62224182017-08-09 23:55:53 -04002157 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002158 continue
2159
2160 if set(plat.ignore_tags) & tc.tags:
2161 continue
2162
Kumar Gala5141d522017-07-07 08:05:48 -05002163 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002164 dep_intersection = tc.depends_on.intersection(
2165 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002166 if dep_intersection != set(tc.depends_on):
2167 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002168
2169 if plat.flash < tc.min_flash:
2170 continue
2171
Anas Nashif2cf0df02015-10-10 09:29:43 -04002172 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2173 continue
2174
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002175 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2176 continue
2177
Anas Nashif924a4e72018-10-18 12:25:55 -04002178 if (plat.env_satisfied and tc.tc_filter
2179 and (plat.default or all_plats or platform_filter)
Anas Nashif07d54c02018-07-21 19:29:08 -05002180 and (toolchain in plat.supported_toolchains or options.force_toolchain)):
Anas Nashif8ea9d022015-11-10 12:24:20 -05002181 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04002182 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07002183 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002184 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07002185 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07002186 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05002187 # each other since they all try to build them
2188 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04002189
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002190 o = os.path.join(self.outdir, plat.name, tc.name)
Anas Nashif45a97862019-01-09 08:46:42 -05002191 cmake_cache_path = os.path.join(o, "CMakeCache.txt")
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002192 generated_dt_confg = "include/generated/generated_dts_board.conf"
2193 dt_config_path = os.path.join(o, "zephyr", generated_dt_confg)
2194 dt_list[tc, plat, tc.name.split("/")[-1]] = dt_config_path
Anas Nashif45a97862019-01-09 08:46:42 -05002195 cmake_list[tc, plat, tc.name.split("/")[-1]] = cmake_cache_path
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002196 defconfig_list[tc, plat, tc.name.split("/")[-1]] = os.path.join(o, "zephyr", ".config")
Anas Nashif13773752018-07-06 18:20:23 -05002197 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002198 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.test_path),
Anas Nashif13773752018-07-06 18:20:23 -05002199 o, args, "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04002200
2201 info("Building testcase defconfigs...")
2202 results = mg.execute(defconfig_cb)
2203
Andrew Boie9e69c542019-04-09 12:14:59 -07002204 info("Filtering test cases...")
Andrew Boie08ce5a52016-02-22 13:28:10 -08002205 for name, goal in results.items():
Anas Nashifb4bdd662018-08-15 17:12:28 -05002206 try:
2207 if goal.failed:
2208 raise SanityRuntimeError("Couldn't build some defconfigs")
2209 except Exception as e:
2210 error(str(e))
2211 sys.exit(2)
2212
Anas Nashif2cf0df02015-10-10 09:29:43 -04002213
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002214 for k, out_config in defconfig_list.items():
Andrew Boie41878222016-11-03 11:58:53 -07002215 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04002216 defconfig = {}
2217 with open(out_config, "r") as fp:
2218 for line in fp.readlines():
2219 m = TestSuite.config_re.match(line)
2220 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07002221 if line.strip() and not line.startswith("#"):
2222 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002223 continue
2224 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07002225 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04002226
Anas Nashif45a97862019-01-09 08:46:42 -05002227 for k, cache_file in cmake_list.items():
2228 if not os.path.exists(out_config):
2229 continue
2230
2231 test, plat, name = k
2232 cmake_conf = {}
2233 try:
2234 cache = CMakeCache.from_file(cache_file)
2235 except FileNotFoundError:
2236 cache = {}
2237
2238 for k in iter(cache):
2239 cmake_conf[k.name] = k.value
2240
2241 test.cmake_cache[plat] = cmake_conf
2242
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002243 for k, out_config in dt_list.items():
2244 if not os.path.exists(out_config):
2245 continue
2246
2247 test, plat, name = k
2248 dt_conf = {}
2249 with open(out_config, "r") as fp:
2250 for line in fp.readlines():
2251 m = TestSuite.dt_re.match(line)
2252 if not m:
2253 if line.strip() and not line.startswith("#"):
2254 sys.stderr.write("Unrecognized line %s\n" % line)
2255 continue
2256 dt_conf[m.group(1)] = m.group(2).strip()
2257 test.dt_config[plat] = dt_conf
2258
Andrew Boie08ce5a52016-02-22 13:28:10 -08002259 for tc_name, tc in self.testcases.items():
2260 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002261 instance_list = []
2262 for plat in arch.platforms:
2263 instance = TestInstance(tc, plat, self.outdir)
2264
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002265 if (arch_name == "unit") != (tc.type == "unit"):
2266 # Discard silently
2267 continue
2268
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002269 if tc.skip:
2270 discards[instance] = "Skip filter"
2271 continue
2272
Anas Nashifbfab06b2017-06-22 09:22:24 -04002273 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002274 platform_filter = []
2275
Andrew Boie6acbe632015-07-17 12:03:52 -07002276 if tag_filter and not tc.tags.intersection(tag_filter):
2277 discards[instance] = "Command line testcase tag filter"
2278 continue
2279
Anas Nashifdfa86e22016-10-24 17:08:56 -04002280 if exclude_tag and tc.tags.intersection(exclude_tag):
2281 discards[instance] = "Command line testcase exclude filter"
2282 continue
2283
Andrew Boie6acbe632015-07-17 12:03:52 -07002284 if testcase_filter and tc_name not in testcase_filter:
2285 discards[instance] = "Testcase name filter"
2286 continue
2287
Andrew Boie6acbe632015-07-17 12:03:52 -07002288 if arch_filter and arch_name not in arch_filter:
2289 discards[instance] = "Command line testcase arch filter"
2290 continue
2291
2292 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2293 discards[instance] = "Not in test case arch whitelist"
2294 continue
2295
Anas Nashif30d13872015-10-05 10:02:45 -04002296 if tc.arch_exclude and arch.name in tc.arch_exclude:
2297 discards[instance] = "In test case arch exclude"
2298 continue
2299
2300 if tc.platform_exclude and plat.name in tc.platform_exclude:
2301 discards[instance] = "In test case platform exclude"
2302 continue
2303
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002304 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2305 discards[instance] = "In test case toolchain exclude"
2306 continue
2307
Andrew Boie6acbe632015-07-17 12:03:52 -07002308 if platform_filter and plat.name not in platform_filter:
2309 discards[instance] = "Command line platform filter"
2310 continue
2311
2312 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2313 discards[instance] = "Not in testcase platform whitelist"
2314 continue
2315
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002316 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2317 discards[instance] = "Not in testcase toolchain whitelist"
2318 continue
2319
Anas Nashif924a4e72018-10-18 12:25:55 -04002320 if not plat.env_satisfied:
2321 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2322 continue
2323
2324 if not options.force_toolchain \
2325 and toolchain and (toolchain not in plat.supported_toolchains) \
2326 and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05002327 discards[instance] = "Not supported by the toolchain"
2328 continue
2329
Anas Nashif62224182017-08-09 23:55:53 -04002330 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002331 discards[instance] = "Not enough RAM"
2332 continue
2333
Kumar Gala5141d522017-07-07 08:05:48 -05002334 if tc.depends_on:
Anas Nashif07d54c02018-07-21 19:29:08 -05002335 dep_intersection = tc.depends_on.intersection(set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002336 if dep_intersection != set(tc.depends_on):
2337 discards[instance] = "No hardware support"
2338 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002339
Anas Nashif62224182017-08-09 23:55:53 -04002340 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002341 discards[instance] = "Not enough FLASH"
2342 continue
2343
2344 if set(plat.ignore_tags) & tc.tags:
2345 discards[instance] = "Excluded tags per platform"
2346 continue
2347
Anas Nashif674bb282018-01-09 09:12:15 -05002348 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05002349 "ARCH": arch.name,
2350 "PLATFORM": plat.name
2351 }
Javier B Perez79414542016-08-08 12:24:59 -05002352 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002353 for p, tdefconfig in tc.defconfig.items():
2354 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002355 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002356 break
2357
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002358 for p, tdefconfig in tc.dt_config.items():
2359 if p == plat:
2360 defconfig.update(tdefconfig)
2361 break
2362
Anas Nashif45a97862019-01-09 08:46:42 -05002363 for p, tdefconfig in tc.cmake_cache.items():
2364 if p == plat:
2365 defconfig.update(tdefconfig)
2366 break
2367
Andrew Boie3ea78922016-03-24 14:46:00 -07002368 if tc.tc_filter:
2369 try:
2370 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002371 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002372 sys.stderr.write(
2373 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002374 raise se
2375 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002376 discards[instance] = (
2377 "defconfig doesn't satisfy expression '%s'" %
2378 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002379 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002380
Andrew Boie6acbe632015-07-17 12:03:52 -07002381 instance_list.append(instance)
2382
2383 if not instance_list:
2384 # Every platform in this arch was rejected already
2385 continue
2386
Anas Nashifa792a3d2017-04-04 18:47:49 -04002387 if default_platforms and not tc.build_on_all:
2388 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002389 instances = list(
2390 filter(
2391 lambda tc: tc.platform.default,
2392 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002393 self.add_instances(instances)
2394 else:
Anas Nashifab747062017-12-05 17:59:01 -05002395 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002396
Anas Nashif3ba1d432017-12-05 15:28:44 -05002397 for instance in list(
2398 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002399 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002400 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002401 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002402
2403 for name, case in self.instances.items():
Anas Nashifdbd76492018-11-23 20:24:19 -05002404 case.create_overlay(case.platform.name)
Anas Nashifab351f42018-04-08 08:57:48 -05002405
Andrew Boie6acbe632015-07-17 12:03:52 -07002406 self.discards = discards
Anas Nashif49b22d42019-06-14 13:45:34 -04002407
Andrew Boie6acbe632015-07-17 12:03:52 -07002408 return discards
2409
Andrew Boie821d8322016-03-22 10:08:35 -07002410 def add_instances(self, ti_list):
2411 for ti in ti_list:
2412 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002413
Anas Nashif37f9dc52018-02-23 08:53:46 -06002414 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002415
2416 def calc_one_elf_size(name, goal):
2417 if not goal.failed:
Alberto Escolar Piedrasb1045fe2018-07-14 13:11:02 +02002418 if self.instances[name].platform.type != "native":
2419 i = self.instances[name]
2420 sc = i.calculate_sizes()
2421 goal.metrics["ram_size"] = sc.get_ram_size()
2422 goal.metrics["rom_size"] = sc.get_rom_size()
2423 goal.metrics["unrecognized"] = sc.unrecognized_sections()
2424 else:
2425 goal.metrics["ram_size"] = 0
2426 goal.metrics["rom_size"] = 0
2427 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002428
Anas Nashif37f9dc52018-02-23 08:53:46 -06002429 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002430 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002431 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002432 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002433
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002434 if not options.disable_size_report and not options.cmake_only:
Andy Ross9c9162d2019-01-03 10:50:53 -08002435 # Parallelize size calculation
2436 executor = concurrent.futures.ThreadPoolExecutor(JOBS)
2437 futures = [executor.submit(calc_one_elf_size, name, goal)
2438 for name, goal in self.goals.items()]
2439 concurrent.futures.wait(futures)
2440 else:
2441 for goal in self.goals.values():
2442 goal.metrics["ram_size"] = 0
2443 goal.metrics["rom_size"] = 0
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002444 goal.metrics["handler_time"] = 0
Andy Ross9c9162d2019-01-03 10:50:53 -08002445 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002446
Andrew Boie6acbe632015-07-17 12:03:52 -07002447 return self.goals
2448
Marc Herbert682961a2019-03-20 16:48:49 -07002449 def save_tests(self, filename):
Anas Nashifbd166f42017-09-02 12:32:08 -04002450 with open(filename, "at") as csvfile:
2451 fieldnames = ['path', 'test', 'platform', 'arch']
2452 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2453 for instance in self.instances.values():
2454 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002455 "path": os.path.dirname(instance.test.name),
2456 "test": os.path.basename(instance.test.name),
2457 "platform": instance.platform.name,
2458 "arch": instance.platform.arch
2459 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002460 cw.writerow(rowdict)
2461
Andrew Boie6acbe632015-07-17 12:03:52 -07002462 def discard_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002463
2464 try:
2465 if self.discards is None:
2466 raise SanityRuntimeError("apply_filters() hasn't been run!")
2467 except Exception as e:
2468 error(str(e))
2469 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002470
Anas Nashifbd166f42017-09-02 12:32:08 -04002471 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002472 fieldnames = ["test", "arch", "platform", "reason"]
2473 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2474 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002475 for instance, reason in sorted(self.discards.items()):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002476 rowdict = {"test": instance.test.name,
2477 "arch": instance.platform.arch,
2478 "platform": instance.platform.name,
2479 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002480 cw.writerow(rowdict)
2481
2482 def compare_metrics(self, filename):
2483 # name, datatype, lower results better
2484 interesting_metrics = [("ram_size", int, True),
2485 ("rom_size", int, True)]
2486
Anas Nashifb4bdd662018-08-15 17:12:28 -05002487 try:
2488 if self.goals is None:
2489 raise SanityRuntimeError("execute() hasn't been run!")
2490 except Exception as e:
2491 print(str(e))
2492 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002493
2494 if not os.path.exists(filename):
2495 info("Cannot compare metrics, %s not found" % filename)
2496 return []
2497
2498 results = []
2499 saved_metrics = {}
2500 with open(filename) as fp:
2501 cr = csv.DictReader(fp)
2502 for row in cr:
2503 d = {}
2504 for m, _, _ in interesting_metrics:
2505 d[m] = row[m]
2506 saved_metrics[(row["test"], row["platform"])] = d
2507
Andrew Boie08ce5a52016-02-22 13:28:10 -08002508 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002509 i = self.instances[name]
2510 mkey = (i.test.name, i.platform.name)
2511 if mkey not in saved_metrics:
2512 continue
2513 sm = saved_metrics[mkey]
2514 for metric, mtype, lower_better in interesting_metrics:
2515 if metric not in goal.metrics:
2516 continue
2517 if sm[metric] == "":
2518 continue
2519 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002520 if delta == 0:
2521 continue
2522 results.append((i, metric, goal.metrics[metric], delta,
2523 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002524 return results
2525
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002526 def testcase_target_report(self, report_file):
2527
2528 run = "Sanitycheck"
2529 eleTestsuite = None
2530 append = options.only_failed
2531
2532 errors = 0
2533 passes = 0
2534 fails = 0
2535 duration = 0
2536 skips = 0
2537
2538 for identifier, ti in self.instances.items():
2539 for k in ti.results.keys():
2540 if ti.results[k] == 'PASS':
2541 passes += 1
2542 elif ti.results[k] == 'BLOCK':
2543 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002544 elif ti.results[k] == 'SKIP':
2545 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002546 else:
2547 fails += 1
2548
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002549 eleTestsuites = ET.Element('testsuites')
2550 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2551 name=run, time="%d" % duration,
2552 tests="%d" % (errors + passes + fails),
2553 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002554 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002555
2556 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002557
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002558 # print out test results
2559 for identifier, ti in self.instances.items():
2560 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002561
2562 eleTestcase = ET.SubElement(
2563 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002564 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002565 if ti.results[k] in ['FAIL', 'BLOCK']:
2566 el = None
2567
2568 if ti.results[k] == 'FAIL':
2569 el = ET.SubElement(
2570 eleTestcase,
2571 'failure',
2572 type="failure",
2573 message="failed")
2574 elif ti.results[k] == 'BLOCK':
2575 el = ET.SubElement(
2576 eleTestcase,
2577 'error',
2578 type="failure",
2579 message="failed")
2580 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
Anas Nashif366ed112019-03-31 20:36:30 -04002581 log_file = os.path.join(p, "handler.log")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002582
Anas Nashif366ed112019-03-31 20:36:30 -04002583 if os.path.exists(log_file):
2584 with open(log_file, "rb") as f:
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002585 log = f.read().decode("utf-8")
Anas Nashif366ed112019-03-31 20:36:30 -04002586 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2587 el.text = filtered_string
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002588
Anas Nashif61e21632018-04-08 13:30:16 -05002589 elif ti.results[k] == 'SKIP':
2590 el = ET.SubElement(
2591 eleTestcase,
Anas Nashif2c7636b2018-09-02 13:11:19 -04002592 'skipped',
2593 type="skipped",
2594 message="Skipped")
Anas Nashif61e21632018-04-08 13:30:16 -05002595
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002596 result = ET.tostring(eleTestsuites)
2597 f = open(report_file, 'wb')
2598 f.write(result)
2599 f.close()
2600
2601
Anas Nashif4f028882017-12-30 11:48:43 -05002602 def testcase_xunit_report(self, filename, duration):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002603 try:
2604 if self.goals is None:
2605 raise SanityRuntimeError("execute() hasn't been run!")
2606 except Exception as e:
2607 print(str(e))
2608 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002609
2610 fails = 0
2611 passes = 0
2612 errors = 0
2613
2614 for name, goal in self.goals.items():
2615 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002616 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002617 errors += 1
2618 else:
2619 fails += 1
2620 else:
2621 passes += 1
2622
2623 run = "Sanitycheck"
2624 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002625 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002626
Anas Nashif0605fa32017-05-07 08:51:02 -04002627 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002628 tree = ET.parse(filename)
2629 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002630 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002631 else:
2632 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002633 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2634 name=run, time="%d" % duration,
2635 tests="%d" % (errors + passes + fails),
2636 failures="%d" % fails,
2637 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002638
Anas Nashifc8390f12017-11-25 17:14:12 -05002639 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002640 for name, goal in self.goals.items():
2641
2642 i = self.instances[name]
2643 if append:
2644 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002645 if tc.get('classname') == "%s:%s" % (
2646 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002647 eleTestsuite.remove(tc)
2648
Anas Nashif4d25b502017-11-25 17:37:17 -05002649 if not goal.failed and goal.handler:
2650 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002651
Anas Nashif3ba1d432017-12-05 15:28:44 -05002652 eleTestcase = ET.SubElement(
2653 eleTestsuite, 'testcase', classname="%s:%s" %
2654 (i.platform.name, i.test.name), name="%s" %
2655 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002656 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002657 failure = ET.SubElement(
2658 eleTestcase,
2659 'failure',
2660 type="failure",
2661 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002662 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002663 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002664 hl = os.path.join(p, "handler.log")
2665 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002666 if goal.reason != 'build_error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002667 if os.path.exists(hl):
2668 log_file = hl
2669 else:
2670 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002671
Anas Nashifc96c90a2019-02-05 07:38:32 -05002672 if os.path.exists(log_file):
2673 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002674 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002675 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2676 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002677 f.close()
Anas Nashifb3311ed2017-04-13 14:44:48 -04002678
2679 result = ET.tostring(eleTestsuites)
2680 f = open(filename, 'wb')
2681 f.write(result)
2682 f.close()
2683
Andrew Boie6acbe632015-07-17 12:03:52 -07002684 def testcase_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002685 try:
2686 if self.goals is None:
2687 raise SanityRuntimeError("execute() hasn't been run!")
2688 except Exception as e:
2689 print(str(e))
2690 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002691
Andrew Boie08ce5a52016-02-22 13:28:10 -08002692 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002693 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002694 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002695 "rom_size"]
2696 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2697 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002698 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07002699 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002700 rowdict = {"test": i.test.name,
2701 "arch": i.platform.arch,
2702 "platform": i.platform.name,
2703 "extra_args": " ".join(i.test.extra_args),
2704 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002705 if goal.failed:
2706 rowdict["passed"] = False
2707 rowdict["status"] = goal.reason
2708 else:
2709 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002710 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002711 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002712 rowdict["ram_size"] = goal.metrics["ram_size"]
2713 rowdict["rom_size"] = goal.metrics["rom_size"]
2714 cw.writerow(rowdict)
2715
2716
2717def parse_arguments():
2718
Anas Nashif3ba1d432017-12-05 15:28:44 -05002719 parser = argparse.ArgumentParser(
2720 description=__doc__,
2721 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002722 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002723
Marc Herbert932a33a2019-03-12 11:37:53 -07002724 case_select = parser.add_argument_group("Test case selection",
2725 """
2726Artificially long but functional example:
2727 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07002728 --testcase-root tests/ztest/base \\
2729 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07002730 --test tests/ztest/base/testing.ztest.verbose_0 \\
2731 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
2732
2733 "kernel.fifo.poll" is one of the test section names in
2734 __/fifo_api/testcase.yaml
2735 """)
Marc Herbertedf17592019-03-08 12:39:11 -08002736
Anas Nashif07d54c02018-07-21 19:29:08 -05002737 parser.add_argument("--force-toolchain", action="store_true",
2738 help="Do not filter based on toolchain, use the set "
2739 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002740 parser.add_argument(
2741 "-p", "--platform", action="append",
2742 help="Platform filter for testing. This option may be used multiple "
2743 "times. Testcases will only be built/run on the platforms "
2744 "specified. If this option is not used, then platforms marked "
2745 "as default in the platform metadata file will be chosen "
2746 "to build and test. ")
2747 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002748 "-a", "--arch", action="append",
2749 help="Arch filter for testing. Takes precedence over --platform. "
2750 "If unspecified, test all arches. Multiple invocations "
2751 "are treated as a logical 'or' relationship")
2752 parser.add_argument(
2753 "-t", "--tag", action="append",
2754 help="Specify tags to restrict which tests to run by tag value. "
2755 "Default is to not do any tag filtering. Multiple invocations "
2756 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002757 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002758 help="Specify tags of tests that should not run. "
2759 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08002760 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002761 "-f",
2762 "--only-failed",
2763 action="store_true",
2764 help="Run only those tests that failed the previous sanity check "
2765 "invocation.")
2766 parser.add_argument(
2767 "-c", "--config", action="append",
2768 help="Specify platform configuration values filtering. This can be "
2769 "specified two ways: <config>=<value> or just <config>. The "
2770 "defconfig for all platforms will be "
2771 "checked. For the <config>=<value> case, only match defconfig "
2772 "that have that value defined. For the <config> case, match "
2773 "defconfig that have that value assigned to any value. "
2774 "Prepend a '!' to invert the match.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002775
Marc Herbert0c465bb2019-03-11 17:28:36 -07002776 test_xor_subtest = case_select.add_mutually_exclusive_group()
2777
2778 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002779 "-s", "--test", action="append",
2780 help="Run only the specified test cases. These are named by "
Marc Herberte5cedca2019-04-08 14:02:34 -07002781 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002782
Marc Herbert0c465bb2019-03-11 17:28:36 -07002783 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002784 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07002785 help="""Recursively find sub-test functions and run the entire
2786 test section where they were found, including all sibling test
2787 functions. Sub-tests are named by:
2788 section.name.in.testcase.yaml.function_name_without_test_prefix
2789 Example: kernel.fifo.poll.fifo_loop
2790 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002791
Anas Nashif3ba1d432017-12-05 15:28:44 -05002792 parser.add_argument(
2793 "-l", "--all", action="store_true",
2794 help="Build/test on all platforms. Any --platform arguments "
2795 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002796
Anas Nashif3ba1d432017-12-05 15:28:44 -05002797 parser.add_argument(
2798 "-o", "--testcase-report",
Marc Herberte5cedca2019-04-08 14:02:34 -07002799 help="""Output a CSV spreadsheet containing results of the test run.
2800 The handler_time column is left blank for tests that were only
2801 compiled and not run.""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002802 parser.add_argument(
2803 "-d", "--discard-report",
2804 help="Output a CSV spreadsheet showing tests that were skipped "
2805 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002806 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002807 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002808
Anas Nashif3ba1d432017-12-05 15:28:44 -05002809 parser.add_argument(
2810 "-B", "--subset",
2811 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2812 "3/5 means run the 3rd fifth of the total. "
2813 "This option is useful when running a large number of tests on "
2814 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002815
2816 parser.add_argument(
2817 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002818 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002819
Anas Nashif3ba1d432017-12-05 15:28:44 -05002820 parser.add_argument(
2821 "-y", "--dry-run", action="store_true",
2822 help="Create the filtered list of test cases, but don't actually "
2823 "run them. Useful if you're just interested in "
2824 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002825
Anas Nashif75547e22018-02-24 08:32:14 -06002826 parser.add_argument("--list-tags", action="store_true",
2827 help="list all tags in selected tests")
2828
Anas Nashif49b22d42019-06-14 13:45:34 -04002829 parser.add_argument("--report-excluded",
2830 action="store_true",
2831 help="""List all tests that are never run based on current scope and
2832 coverage. If you are looking for accurate results, run this with
2833 --all, but this will take a while...""")
2834
Marc Herbertedf17592019-03-08 12:39:11 -08002835 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07002836 help="""List of all sub-test functions recursively found in
2837 all --testcase-root arguments. Note different sub-tests can share
2838 the same section name and come from different directories.
2839 The output is flattened and reports --sub-test names only,
2840 not their directories. For instance net.socket.getaddrinfo_ok
2841 and net.socket.fd_set belong to different directories.
2842 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002843
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002844 parser.add_argument("--export-tests", action="store",
2845 metavar="FILENAME",
2846 help="Export tests case meta-data to a file in CSV format.")
2847
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002848 parser.add_argument("--detailed-report",
2849 action="store",
2850 metavar="FILENAME",
Marc Herberte5cedca2019-04-08 14:02:34 -07002851 help="""Generate a junit report with detailed testcase results.
2852 Unlike the CSV file produced by --testcase-report, this XML
2853 report includes only tests which have run and none which were
2854 merely built. If an image with multiple tests crashes early then
2855 later tests are not accounted for either.""")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002856
Anas Nashif654ec5982019-04-11 08:38:21 -04002857 parser.add_argument("--timestamps",
2858 action="store_true",
2859 help="Print all messages with time stamps")
2860
Anas Nashif3ba1d432017-12-05 15:28:44 -05002861 parser.add_argument(
2862 "-r", "--release", action="store_true",
2863 help="Update the benchmark database with the results of this test "
2864 "run. Intended to be run by CI when tagging an official "
2865 "release. This database is used as a basis for comparison "
2866 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002867 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002868 help="Treat warning conditions as errors")
2869 parser.add_argument(
2870 "-v",
2871 "--verbose",
2872 action="count",
2873 default=0,
2874 help="Emit debugging information, call multiple times to increase "
2875 "verbosity")
2876 parser.add_argument(
2877 "-i", "--inline-logs", action="store_true",
2878 help="Upon test failure, print relevant log data to stdout "
2879 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002880 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002881 help="log also to file")
2882 parser.add_argument(
2883 "-m", "--last-metrics", action="store_true",
2884 help="Instead of comparing metrics from the last --release, "
2885 "compare with the results of the previous sanity check "
2886 "invocation")
2887 parser.add_argument(
2888 "-u",
2889 "--no-update",
2890 action="store_true",
2891 help="do not update the results of the last run of the sanity "
2892 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08002893 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002894 "-F",
2895 "--load-tests",
2896 metavar="FILENAME",
2897 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07002898 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002899
Marc Herbertedf17592019-03-08 12:39:11 -08002900 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002901 "-E",
2902 "--save-tests",
2903 metavar="FILENAME",
2904 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07002905 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002906
Andy Doancbecadd2019-02-08 10:19:10 -06002907 test_or_build = parser.add_mutually_exclusive_group()
2908 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002909 "-b", "--build-only", action="store_true",
2910 help="Only build the code, do not execute any of it in QEMU")
Andy Doancbecadd2019-02-08 10:19:10 -06002911 test_or_build.add_argument(
2912 "--test-only", action="store_true",
2913 help="""Only run device tests with current artifacts, do not build
2914 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002915 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002916 "--cmake-only", action="store_true",
2917 help="Test on device directly. Specify the serial device to "
2918 "use with the --device-serial option.")
2919
2920 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002921 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07002922 help="Number of jobs for building, defaults to number of CPU threads, "
2923 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06002924
2925 parser.add_argument(
2926 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002927 help="Test on device directly. Specify the serial device to "
2928 "use with the --device-serial option.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002929
2930 parser.add_argument(
2931 "-X", "--fixture", action="append", default=[],
2932 help="Specify a fixture that a board might support")
Anas Nashif73440ea2018-02-19 10:57:03 -06002933 parser.add_argument(
2934 "--device-serial",
Marc Herbert9e573382019-07-03 12:49:42 -07002935 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002936 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002937 "--show-footprint", action="store_true",
2938 help="Show footprint statistics and deltas since last release."
2939 )
2940 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002941 "-H", "--footprint-threshold", type=float, default=5,
2942 help="When checking test case footprint sizes, warn the user if "
2943 "the new app size is greater then the specified percentage "
2944 "from the last release. Default is 5. 0 to warn on any "
2945 "increase on app size")
2946 parser.add_argument(
2947 "-D", "--all-deltas", action="store_true",
2948 help="Show all footprint deltas, positive or negative. Implies "
2949 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002950 parser.add_argument(
2951 "-O", "--outdir",
Anas Nashiff114a132018-11-20 11:51:34 -05002952 default="%s/sanity-out" % os.getcwd(),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002953 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05002954 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002955 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002956 parser.add_argument(
2957 "-n", "--no-clean", action="store_true",
2958 help="Do not delete the outdir before building. Will result in "
2959 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08002960 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002961 "-T", "--testcase-root", action="append", default=[],
2962 help="Base directory to recursively search for test cases. All "
2963 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07002964 "called multiple times. Defaults to the 'samples/' and "
2965 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002966
Anas Nashif3ba1d432017-12-05 15:28:44 -05002967 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2968 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002969
Anas Nashif3ba1d432017-12-05 15:28:44 -05002970 parser.add_argument(
2971 "-A", "--board-root", action="append", default=board_root_list,
2972 help="Directory to search for board configuration files. All .yaml "
2973 "files in the directory will be processed.")
2974 parser.add_argument(
2975 "-z", "--size", action="append",
2976 help="Don't run sanity checks. Instead, produce a report to "
2977 "stdout detailing RAM/ROM sizes on the specified filenames. "
2978 "All other command line arguments ignored.")
2979 parser.add_argument(
2980 "-S", "--enable-slow", action="store_true",
2981 help="Execute time-consuming test cases that have been marked "
2982 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01002983 parser.add_argument(
2984 "--disable-unrecognized-section-test", action="store_true",
2985 default=False,
2986 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07002987 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002988 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002989 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002990 parser.add_argument("--disable-asserts", action="store_false",
2991 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07002992 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05002993 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002994 help="Error on deprecation warnings.")
Andy Ross9c9162d2019-01-03 10:50:53 -08002995 parser.add_argument("--disable-size-report", action="store_true",
2996 help="Skip expensive computation of ram/rom segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002997
2998 parser.add_argument(
2999 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003000 help="""Extra CMake cache entries to define when building test cases.
3001 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003002 prefixed with -D before being passed to CMake.
3003
3004 E.g
3005 "sanitycheck -x=USE_CCACHE=0"
3006 will translate to
3007 "cmake -DUSE_CCACHE=0"
3008
3009 which will ultimately disable ccache.
3010 """
3011 )
Andy Doan79c48842019-02-08 10:09:04 -06003012 parser.add_argument(
3013 "--west-flash", nargs='?', const=[],
3014 help="""Uses west instead of ninja or make to flash when running with
3015 --device-testing"
3016
3017 E.g
3018 sanitycheck --device-testing --device-serial /dev/ttyACM0 \
3019 --west-flash="--board-id=foobar"
3020 will translate to
3021 west flash -- --board-id=foobar
3022 """
3023 )
Andrew Boie8047a6f2019-07-02 15:43:29 -07003024
3025 if "ZEPHYR_SDK_INSTALL_DIR" in os.environ:
3026 # Use the x86 build of GCOV from the SDK. This can parse gcov data
3027 # from any arch built with the same GCC version.
3028 gcov_bin = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
3029 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
3030 else:
3031 # No SDK in use, just rely on PATH to find it
3032 gcov_bin = "gcov"
3033
3034 parser.add_argument("--gcov-tool", default=gcov_bin,
3035 help="Path to the gcov tool. Default is %s" % gcov_bin)
Sebastian Bøec2182612017-11-09 12:25:02 +01003036
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003037 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003038 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003039
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003040 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003041 help="Generate coverage reports. Implies "
3042 "--enable_coverage and --enable-slow")
Andrew Boie6acbe632015-07-17 12:03:52 -07003043
Andrew Boie8047a6f2019-07-02 15:43:29 -07003044 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003045 help="Plarforms to run coverage reports on. "
Andrew Boie8047a6f2019-07-02 15:43:29 -07003046 "This option may be used multiple times. "
3047 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003048
Andrew Boie6acbe632015-07-17 12:03:52 -07003049 return parser.parse_args()
3050
Anas Nashif3ba1d432017-12-05 15:28:44 -05003051
Andrew Boie6acbe632015-07-17 12:03:52 -07003052def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003053 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003054 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003055 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003056
3057 try:
3058 with open(filename) as fp:
3059 data = fp.read()
3060 except Exception as e:
3061 data = "Unable to read log data (%s)\n" % (str(e))
3062
3063 sys.stdout.write(data)
3064 if log_file:
3065 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003066 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003067 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003068 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003069
Anas Nashif3ba1d432017-12-05 15:28:44 -05003070
Andrew Boie6acbe632015-07-17 12:03:52 -07003071def terse_test_cb(instances, goals, goal):
3072 total_tests = len(goals)
3073 total_done = 0
3074 total_failed = 0
3075
Andrew Boie08ce5a52016-02-22 13:28:10 -08003076 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003077 if g.finished:
3078 total_done += 1
3079 if g.failed:
3080 total_failed += 1
3081
3082 if goal.failed:
3083 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05003084 info(
3085 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
3086 i.platform.name,
3087 i.test.name,
3088 COLOR_RED,
3089 COLOR_NORMAL,
Anas Nashif654ec5982019-04-11 08:38:21 -04003090 goal.reason), False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003091 log_info(goal.get_error_log())
Anas Nashif654ec5982019-04-11 08:38:21 -04003092 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003093
Anas Nashif3ba1d432017-12-05 15:28:44 -05003094 sys.stdout.write(
3095 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
3096 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
3097 int((float(total_done) / total_tests) * 100),
3098 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
3099 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07003100 sys.stdout.flush()
3101
Anas Nashif3ba1d432017-12-05 15:28:44 -05003102
Andrew Boie6acbe632015-07-17 12:03:52 -07003103def chatty_test_cb(instances, goals, goal):
3104 i = instances[goal.name]
3105
3106 if VERBOSE < 2 and not goal.finished:
3107 return
3108
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003109 total_tests = len(goals)
3110 total_tests_width = len(str(total_tests))
3111 total_done = 0
3112
3113 for k, g in goals.items():
3114 if g.finished:
3115 total_done += 1
3116
Andrew Boie6acbe632015-07-17 12:03:52 -07003117 if goal.failed:
3118 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
3119 elif goal.finished:
3120 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
3121 else:
3122 status = goal.make_state
3123
Anas Nashifd18ec532019-04-11 23:20:39 -04003124 if goal.handler:
3125 handler_type = goal.handler.type_str
Marc Herbert35dc9632019-06-20 10:00:20 -07003126 htime = goal.metrics.get("handler_time", None)
3127 if htime:
3128 handler_type += " {:.3f}s".format(htime)
Anas Nashifd18ec532019-04-11 23:20:39 -04003129 else:
3130 handler_type = "build"
3131
3132 info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003133 total_done, total_tests_width, total_tests, i.platform.name,
Anas Nashifd18ec532019-04-11 23:20:39 -04003134 i.test.name, status, handler_type))
Andrew Boie6acbe632015-07-17 12:03:52 -07003135 if goal.failed:
3136 log_info(goal.get_error_log())
3137
Andrew Boiebbd670c2015-08-17 13:16:11 -07003138
3139def size_report(sc):
3140 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07003141 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003142 for i in range(len(sc.sections)):
3143 v = sc.sections[i]
3144
Andrew Boie73b4ee62015-10-07 11:33:22 -07003145 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
3146 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3147 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003148
Andrew Boie73b4ee62015-10-07 11:33:22 -07003149 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003150 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003151 info("")
3152
Anas Nashiff29087e2019-01-25 09:37:38 -05003153def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003154 if VERBOSE:
3155 print("Working on %s" %intput_file)
3156 extracted_coverage_info = {}
3157 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003158 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003159 with open(intput_file, 'r') as fp:
3160 for line in fp.readlines():
3161 if re.search("GCOV_COVERAGE_DUMP_START", line):
3162 capture_data = True
3163 continue
3164 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003165 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003166 break
3167 # Loop until the coverage data is found.
3168 if not capture_data:
3169 continue
3170 if line.startswith("*"):
3171 sp = line.split("<")
3172 if len(sp) > 1:
3173 # Remove the leading delimiter "*"
3174 file_name = sp[0][1:]
3175 # Remove the trailing new line char
3176 hex_dump = sp[1][:-1]
3177 else:
3178 continue
3179 else:
3180 continue
3181 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003182 if not capture_data:
3183 capture_complete = True
3184 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003185
3186def create_gcda_files(extracted_coverage_info):
3187 if VERBOSE:
3188 print("Generating gcda files")
3189 for filename, hexdump_val in extracted_coverage_info.items():
3190 # if kobject_hash is given for coverage gcovr fails
3191 # hence skipping it problem only in gcovr v4.1
3192 if "kobject_hash" in filename:
3193 filename = (filename[:-4]) +"gcno"
3194 try:
3195 os.remove(filename)
3196 except:
3197 pass
3198 continue
3199
3200 with open(filename, 'wb') as fp:
3201 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003202
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003203def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003204
3205 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003206 gcov_data = retrieve_gcov_data(filename)
3207 capture_complete = gcov_data['complete']
3208 extracted_coverage_info = gcov_data['data']
3209 if capture_complete:
3210 create_gcda_files(extracted_coverage_info)
3211 verbose("Gcov data captured: {}".format(filename))
3212 else:
3213 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003214
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003215 gcov_tool = options.gcov_tool
3216
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003217 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3218 coveragefile = os.path.join(outdir, "coverage.info")
3219 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003220 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3221 "--capture", "--directory", outdir,
3222 "--rc", "lcov_branch_coverage=1",
3223 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003224 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003225 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003226 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003227 "--output-file", ztestfile,
3228 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3229
Anas Nashif3cbffef2018-11-07 23:50:54 -05003230 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003231 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003232 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3233 "--output-file", ztestfile,
3234 "--rc", "lcov_branch_coverage=1"],
3235 stdout=coveragelog)
3236 files = [coveragefile, ztestfile];
3237 else:
3238 files = [coveragefile];
3239
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003240 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003241 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003242 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3243 coveragefile, i, "--output-file",
3244 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003245 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003246
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003247 #The --ignore-errors source option is added to avoid it exiting due to
3248 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003249 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003250 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003251 "-output-directory",
3252 os.path.join(outdir, "coverage")] + files,
3253 stdout=coveragelog)
3254 if ret==0:
3255 info("HTML report generated: %s"%
3256 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05003257
Andrew Boiebbd670c2015-08-17 13:16:11 -07003258
Andrew Boie6acbe632015-07-17 12:03:52 -07003259def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003260 start_time = time.time()
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003261 global VERBOSE, INLINE_LOGS, JOBS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003262 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003263 global run_individual_tests
Andrew Boie1578ef72019-07-03 10:19:29 -07003264
3265 # XXX: Workaround for #17239
3266 resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 4096))
Anas Nashife10b6512017-12-30 13:01:45 -05003267 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003268
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003269 if options.coverage:
3270 options.enable_coverage = True
Andrew Boie8047a6f2019-07-02 15:43:29 -07003271 options.enable_slow = True
3272 if not options.coverage_platform:
3273 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003274
Anas Nashife10b6512017-12-30 13:01:45 -05003275 if options.size:
3276 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003277 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003278 sys.exit(0)
3279
Anas Nashif73440ea2018-02-19 10:57:03 -06003280
3281 if options.device_testing:
3282 if options.device_serial is None or len(options.platform) != 1:
3283 sys.exit(1)
3284
Anas Nashife10b6512017-12-30 13:01:45 -05003285 VERBOSE += options.verbose
3286 INLINE_LOGS = options.inline_logs
3287 if options.log_file:
3288 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003289
Anas Nashife10b6512017-12-30 13:01:45 -05003290 if options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003291 JOBS = options.jobs
Andrew Boie9f4f57e2019-07-02 17:00:08 -07003292 elif options.build_only:
3293 JOBS = multiprocessing.cpu_count() * 2
3294 else:
3295 JOBS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -07003296
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003297 # Decrease JOBS for Ninja, if jobs weren't explicitly set
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003298 if options.ninja and not options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003299 JOBS = int(JOBS * 0.75)
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003300
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003301 info("JOBS: %d" % JOBS);
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003302
Anas Nashife10b6512017-12-30 13:01:45 -05003303 if options.subset:
3304 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003305 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003306 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003307 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003308 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003309 return
3310
Anas Nashife10b6512017-12-30 13:01:45 -05003311 if os.path.exists(options.outdir) and not options.no_clean:
3312 info("Cleaning output directory " + options.outdir)
3313 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003314
Anas Nashife10b6512017-12-30 13:01:45 -05003315 if not options.testcase_root:
3316 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003317 os.path.join(ZEPHYR_BASE, "samples")]
3318
Anas Nashif37f9dc52018-02-23 08:53:46 -06003319 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04003320
Kumar Galac84235e2018-04-10 13:32:51 -05003321 if ts.load_errors:
3322 sys.exit(1)
3323
Anas Nashif75547e22018-02-24 08:32:14 -06003324 if options.list_tags:
3325 tags = set()
3326 for n,tc in ts.testcases.items():
3327 tags = tags.union(tc.tags)
3328
3329 for t in tags:
3330 print("- {}".format(t))
3331
3332 return
3333
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003334
3335 def export_tests(filename, tests):
3336 with open(filename, "wt") as csvfile:
3337 fieldnames = ['section', 'subsection', 'title', 'reference']
3338 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3339 for test in tests:
3340 data = test.split(".")
3341 subsec = " ".join(data[1].split("_")).title()
3342 rowdict = {
3343 "section": data[0].capitalize(),
3344 "subsection": subsec,
3345 "title": test,
3346 "reference": test
3347 }
3348 cw.writerow(rowdict)
3349
3350 if options.export_tests:
3351 cnt = 0
3352 unq = []
3353 for n,tc in ts.testcases.items():
3354 for c in tc.cases:
3355 unq.append(c)
3356
3357 tests = sorted(set(unq))
3358 export_tests(options.export_tests, tests)
3359 return
3360
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003361 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003362
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003363 if options.test:
3364 run_individual_tests = options.test
3365
Anas Nashif49b22d42019-06-14 13:45:34 -04003366
3367 def get_unique_tests(ts):
Anas Nashifa3abe962018-05-05 19:10:22 -05003368 unq = []
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003369 run_individual_tests = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05003370 for n,tc in ts.testcases.items():
3371 for c in tc.cases:
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003372 if options.sub_test and c in options.sub_test:
3373 if tc.name not in run_individual_tests:
3374 run_individual_tests.append(tc.name)
Anas Nashifa3abe962018-05-05 19:10:22 -05003375 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003376
Anas Nashif49b22d42019-06-14 13:45:34 -04003377 return unq
3378
3379 if options.list_tests or options.sub_test:
3380 cnt = 0
3381 unq = get_unique_tests(ts)
3382
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003383 if options.sub_test:
3384 if run_individual_tests:
3385 info("Running the following tests:")
3386 for t in run_individual_tests:
3387 print(" - {}".format(t))
3388 else:
3389 info("Tests not found")
3390 return
3391
3392 elif options.list_tests:
3393 for u in sorted(set(unq)):
3394 cnt = cnt + 1
3395 print(" - {}".format(u))
3396 print("{} total.".format(cnt))
3397 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003398
Anas Nashifbd166f42017-09-02 12:32:08 -04003399 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05003400 if options.load_tests:
3401 ts.load_from_file(options.load_tests)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05003402 elif options.only_failed:
3403 ts.get_last_failed()
Anas Nashifbd166f42017-09-02 12:32:08 -04003404 else:
Anas Nashif4f028882017-12-30 11:48:43 -05003405 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003406
Anas Nashif49b22d42019-06-14 13:45:34 -04003407
Anas Nashife10b6512017-12-30 13:01:45 -05003408 if options.discard_report:
3409 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07003410
Anas Nashif30551f42018-01-12 21:56:59 -05003411 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003412 # if we are using command line platform filter, no need to list every
3413 # other platform as excluded, we know that already.
3414 # Show only the discards that apply to the selected platforms on the
3415 # command line
3416
Andrew Boie08ce5a52016-02-22 13:28:10 -08003417 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003418 if options.platform and i.platform.name not in options.platform:
3419 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003420 debug(
3421 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3422 i.platform.name,
3423 i.test.name,
3424 COLOR_YELLOW,
3425 COLOR_NORMAL,
3426 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003427
Anas Nashif1a5bba72018-01-05 08:07:45 -05003428
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003429 def native_and_unit_first(a, b):
3430 if a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003431 return -1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003432 if b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003433 return 1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003434 if a[0].startswith('native_posix'):
3435 return -1
3436 if b[0].startswith('native_posix'):
3437 return 1
3438 if a[0].split("/",1)[0].endswith("_bsim"):
3439 return -1
3440 if b[0].split("/",1)[0].endswith("_bsim"):
3441 return 1
3442
Anas Nashif1a5bba72018-01-05 08:07:45 -05003443 return (a > b) - (a < b)
3444
3445 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003446 key=cmp_to_key(native_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04003447
Anas Nashif49b22d42019-06-14 13:45:34 -04003448
3449
3450 if options.report_excluded:
3451 all_tests = set(get_unique_tests(ts))
3452 to_be_run = set()
3453 for i,p in ts.instances.items():
3454 to_be_run.update(p.test.cases)
3455
3456 if (all_tests - to_be_run):
3457 print("Tests that never build or run:")
3458 for not_run in (all_tests - to_be_run):
3459 print("- {}".format(not_run))
3460
3461 return
3462
3463
Anas Nashife10b6512017-12-30 13:01:45 -05003464 if options.save_tests:
Marc Herbert682961a2019-03-20 16:48:49 -07003465 ts.save_tests(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04003466 return
3467
Anas Nashife10b6512017-12-30 13:01:45 -05003468 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05003469
Anas Nashife10b6512017-12-30 13:01:45 -05003470 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04003471 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003472 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003473 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003474 if subset == sets:
3475 end = total
3476 else:
3477 end = start + per_set
3478
Anas Nashif3ba1d432017-12-05 15:28:44 -05003479 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04003480 ts.instances = OrderedDict(sliced_instances)
3481
Andrew Boie6acbe632015-07-17 12:03:52 -07003482 info("%d tests selected, %d tests discarded due to filters" %
3483 (len(ts.instances), len(discards)))
3484
Anas Nashife10b6512017-12-30 13:01:45 -05003485 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07003486 return
3487
3488 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003489 goals = ts.execute(
3490 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003491 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07003492 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003493 goals = ts.execute(
3494 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003495 ts.instances)
Anas Nashif654ec5982019-04-11 08:38:21 -04003496 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003497
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003498 if options.detailed_report:
3499 ts.testcase_target_report(options.detailed_report)
3500
Daniel Leung7f850102016-04-08 11:07:32 -07003501 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05003502 if options.compare_report:
3503 report_to_use = options.compare_report
3504 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07003505 report_to_use = LAST_SANITY
3506 else:
3507 report_to_use = RELEASE_DATA
3508
3509 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07003510 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06003511 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07003512 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05003513 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07003514 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07003515 continue
3516
Andrew Boieea7928f2015-08-14 14:27:38 -07003517 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05003518 if not options.all_deltas and (percentage <
3519 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07003520 continue
3521
Daniel Leung00525c22016-04-11 10:27:56 -07003522 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07003523 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05003524 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07003525 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07003526 warnings += 1
3527
3528 if warnings:
3529 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05003530 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07003531
3532 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08003533 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003534 if goal.failed:
3535 failed += 1
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003536 elif goal.metrics.get("unrecognized") and not options.disable_unrecognized_section_test:
Andrew Boie73b4ee62015-10-07 11:33:22 -07003537 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
3538 (COLOR_RED, COLOR_NORMAL, goal.name,
3539 str(goal.metrics["unrecognized"])))
3540 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07003541
Anas Nashife10b6512017-12-30 13:01:45 -05003542 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003543 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003544 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003545
Anas Nashif0605fa32017-05-07 08:51:02 -04003546 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07003547 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003548 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
3549 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
3550 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07003551
Anas Nashife10b6512017-12-30 13:01:45 -05003552 if options.testcase_report:
3553 ts.testcase_report(options.testcase_report)
3554 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05003555 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07003556 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05003557 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07003558 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003559 if log_file:
3560 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05003561 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003562 sys.exit(1)
3563
Anas Nashif3ba1d432017-12-05 15:28:44 -05003564
Andrew Boie6acbe632015-07-17 12:03:52 -07003565if __name__ == "__main__":
3566 main()