blob: a7d3cf7a64acdd15c1b3e8d0ce1fb3c1e586e253 [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
Anas Nashif578ae402019-07-12 07:54:35 -0700144 matches. For example, if CONFIG_SOC="stm32f107xc" then
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700145
Anas Nashif578ae402019-07-12 07:54:35 -0700146 filter = CONFIG_SOC : "stm.*"
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700147
148 Would match it.
149
Anas Nashifa792a3d2017-04-04 18:47:49 -0400150The set of test cases that actually run depends on directives in the testcase
151filed and options passed in on the command line. If there is any confusion,
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]
Michael Scott421ce462019-06-18 09:37:46 -0700679 if options.west_runner is not None:
680 command.append("--runner")
681 command.append(options.west_runner)
Andy Doan79c48842019-02-08 10:09:04 -0600682 # There are two ways this option is used.
683 # 1) bare: --west-flash
684 # This results in options.west_flash == []
685 # 2) with a value: --west-flash="--board-id=42"
686 # This results in options.west_flash == "--board-id=42"
687 if options.west_flash != []:
688 command.append('--')
689 command.append(options.west_flash)
Anas Nashifd3384fb2018-02-22 06:44:16 -0600690 else:
Andy Doan79c48842019-02-08 10:09:04 -0600691 if options.ninja:
692 generator_cmd = "ninja"
693 else:
694 generator_cmd = "make"
Anas Nashifd3384fb2018-02-22 06:44:16 -0600695
Andy Doan79c48842019-02-08 10:09:04 -0600696 command = [generator_cmd, "-C", self.outdir, "flash"]
Anas Nashifd3384fb2018-02-22 06:44:16 -0600697
Anas Nashif73440ea2018-02-19 10:57:03 -0600698 device = options.device_serial
699 ser = serial.Serial(
700 device,
701 baudrate=115200,
702 parity=serial.PARITY_NONE,
703 stopbits=serial.STOPBITS_ONE,
704 bytesize=serial.EIGHTBITS,
705 timeout=self.timeout
706 )
707
708 ser.flush()
709
710 harness_name = self.instance.test.harness.capitalize()
711 harness_import = HarnessImporter(harness_name)
712 harness = harness_import.instance
713 harness.configure(self.instance)
Marti Bolivar5591ca22019-02-07 15:53:39 -0700714 rpipe, wpipe = os.pipe()
Anas Nashif73440ea2018-02-19 10:57:03 -0600715
Marti Bolivar5591ca22019-02-07 15:53:39 -0700716 t = threading.Thread(target=self.monitor_serial, daemon=True,
717 args=(ser, rpipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600718 t.start()
719
Andy Doan79c48842019-02-08 10:09:04 -0600720 logging.debug('Flash command: %s', command)
Anas Nashif61e21632018-04-08 13:30:16 -0500721 try:
Marti Bolivar303b5222019-02-07 15:50:55 -0700722 if VERBOSE:
723 subprocess.check_call(command)
724 else:
725 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif61e21632018-04-08 13:30:16 -0500726 except subprocess.CalledProcessError:
Marti Bolivar5591ca22019-02-07 15:53:39 -0700727 os.write(wpipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600728
729 t.join(self.timeout)
730 if t.is_alive():
731 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600732
733 if ser.isOpen():
734 ser.close()
735
Anas Nashifd3384fb2018-02-22 06:44:16 -0600736 if out_state == "timeout":
737 for c in self.instance.test.cases:
738 if c not in harness.tests:
739 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500740
741 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600742 if harness.state:
743 self.set_state(harness.state, {})
744 else:
745 self.set_state(out_state, {})
746
Anas Nashif3ba1d432017-12-05 15:28:44 -0500747
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300748class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700749 """Spawns a thread to monitor QEMU output from pipes
750
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400751 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700752 We need to do this as once qemu starts, it runs forever until killed.
753 Test cases emit special messages to the console as they run, we check
754 for these to collect whether the test passed or failed.
755 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700756
757 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500758 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700759 fifo_in = fifo_fn + ".in"
760 fifo_out = fifo_fn + ".out"
761
762 # These in/out nodes are named from QEMU's perspective, not ours
763 if os.path.exists(fifo_in):
764 os.unlink(fifo_in)
765 os.mkfifo(fifo_in)
766 if os.path.exists(fifo_out):
767 os.unlink(fifo_out)
768 os.mkfifo(fifo_out)
769
770 # We don't do anything with out_fp but we need to open it for
771 # writing so that QEMU doesn't block, due to the way pipes work
772 out_fp = open(fifo_in, "wb")
773 # Disable internal buffering, we don't
774 # want read() or poll() to ever block if there is data in there
775 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800776 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700777
778 start_time = time.time()
779 timeout_time = start_time + timeout
780 p = select.poll()
781 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400782 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700783
784 metrics = {}
785 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500786 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700787 while True:
788 this_timeout = int((timeout_time - time.time()) * 1000)
789 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400790 if not out_state:
791 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700792 break
793
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500794 try:
795 c = in_fp.read(1).decode("utf-8")
796 except UnicodeDecodeError:
797 # Test is writing something weird, fail
798 out_state = "unexpected byte"
799 break
800
Andrew Boie6acbe632015-07-17 12:03:52 -0700801 if c == "":
802 # EOF, this shouldn't happen unless QEMU crashes
803 out_state = "unexpected eof"
804 break
805 line = line + c
806 if c != "\n":
807 continue
808
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300809 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700810 log_out_fp.write(line)
811 log_out_fp.flush()
812 line = line.strip()
813 verbose("QEMU: %s" % line)
814
Anas Nashif576be982017-12-23 20:20:27 -0500815 harness.handle(line)
816 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400817 # if we have registered a fail make sure the state is not
818 # overridden by a false success message coming from the
819 # testsuite
820 if out_state != 'failed':
821 out_state = harness.state
822
823 # if we get some state, that means test is doing well, we reset
Marc Herbert9e573382019-07-03 12:49:42 -0700824 # the timeout and wait for 2 more seconds to catch anything
825 # printed late. We wait much longer if code
Andrew Boie095b82a2019-07-02 17:03:48 -0700826 # coverage is enabled since dumping this information can
827 # take some time.
Anas Nashiff29087e2019-01-25 09:37:38 -0500828 if not timeout_extended or harness.capture_coverage:
829 timeout_extended= True
830 if harness.capture_coverage:
Andrew Boied62e2292019-07-01 15:01:48 -0700831 timeout_time = time.time() + 30
Anas Nashiff29087e2019-01-25 09:37:38 -0500832 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500833 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700834
835 # TODO: Add support for getting numerical performance data
836 # from test cases. Will involve extending test case reporting
837 # APIs. Add whatever gets reported to the metrics dictionary
838 line = ""
839
Anas Nashifc8390f12017-11-25 17:14:12 -0500840 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700841 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500842 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700843 handler.set_state(out_state, metrics)
844
845 log_out_fp.close()
846 out_fp.close()
847 in_fp.close()
Anas Nashifd6476ee2019-04-11 11:40:09 -0400848 if os.path.exists(pid_fn):
849 pid = int(open(pid_fn).read())
850 os.unlink(pid_fn)
Andrew Boie6acbe632015-07-17 12:03:52 -0700851
Anas Nashifd6476ee2019-04-11 11:40:09 -0400852 try:
853 if pid:
854 os.kill(pid, signal.SIGTERM)
855 except ProcessLookupError:
856 # Oh well, as long as it's dead! User probably sent Ctrl-C
857 pass
Andrew Boie08ce5a52016-02-22 13:28:10 -0800858
Andrew Boie6acbe632015-07-17 12:03:52 -0700859 os.unlink(fifo_in)
860 os.unlink(fifo_out)
861
Anas Nashifd18ec532019-04-11 23:20:39 -0400862 def __init__(self, instance, type_str):
Andrew Boie6acbe632015-07-17 12:03:52 -0700863 """Constructor
864
Anas Nashifd3384fb2018-02-22 06:44:16 -0600865 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700866 """
Anas Nashif576be982017-12-23 20:20:27 -0500867
Anas Nashifd18ec532019-04-11 23:20:39 -0400868 super().__init__(instance, type_str)
Anas Nashif576be982017-12-23 20:20:27 -0500869
Andrew Boie6acbe632015-07-17 12:03:52 -0700870 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500871 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700872
873 # We pass this to QEMU which looks for fifos with .in and .out
874 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500875 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700876
Anas Nashif576be982017-12-23 20:20:27 -0500877 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700878 if os.path.exists(self.pid_fn):
879 os.unlink(self.pid_fn)
880
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500881 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500882
883 harness_import = HarnessImporter(instance.test.harness.capitalize())
884 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600885 harness.configure(self.instance)
886 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
887 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300888 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500889 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600890
891 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700892 self.thread.daemon = True
Marc Herbertaf1090c2019-04-30 14:11:29 -0700893 verbose("Spawning QEMUHandler Thread for %s 'make run'" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700894 self.thread.start()
895
Andrew Boie6acbe632015-07-17 12:03:52 -0700896 def get_fifo(self):
897 return self.fifo_fn
898
Anas Nashif3ba1d432017-12-05 15:28:44 -0500899
Andrew Boie6acbe632015-07-17 12:03:52 -0700900class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700901
Erwin Rolcb3d1272018-02-10 11:40:40 +0100902 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
903 "ccm_noinit"]
Daniel Leungc8066c52019-03-09 00:35:40 -0800904 rw_sections = ["datas", "initlevel", "exceptions", "initshell",
Andrew Boiec2e01df2018-11-12 15:16:54 -0800905 "_static_thread_area", "_k_timer_area",
Andrew Boie506f15c2018-11-08 14:44:31 -0800906 "_k_mem_slab_area", "_k_mem_pool_area", "sw_isr_table",
Andrew Boiec253a682019-01-29 12:56:02 -0800907 "_k_sem_area", "_k_mutex_area", "app_shmem_regions",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700908 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
909 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200910 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700911 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100912 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500913 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
914 'log_backends_sections', 'log_dynamic_sections',
Johann Fischerfcffebf2018-09-13 00:53:15 +0200915 'log_const_sections',"app_smem", 'shell_root_cmds_sections',
Adithya Baglody1fa8cf92018-10-19 10:15:19 -0700916 'log_const_sections',"app_smem", "font_entry_sections",
Anas Nashifdb9592a2018-10-08 10:19:41 -0400917 "priv_stacks_noinit", "_TEXT_SECTION_NAME_2",
Kumar Galae6393dd2019-02-16 10:08:33 -0600918 '_GCOV_BSS_SECTION_NAME', 'gcov', 'nocache']
Anas Nashifdb9592a2018-10-08 10:19:41 -0400919
Andrew Boie73b4ee62015-10-07 11:33:22 -0700920 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700921 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andrew Boie506f15c2018-11-08 14:44:31 -0800922 "rodata", "devconfig", "net_l2", "vector", "sw_isr_table",
Alberto Escolar Piedras010c6092019-07-12 09:53:30 +0200923 "_settings_handlers_area", "_bt_channels_area",
Alberto Escolar Piedrasf8908cd2019-07-12 17:35:21 +0200924 "_bt_br_channels_area", "_bt_services_area",
Jukka Rissanenf95938d2019-03-26 17:26:25 +0200925 "vectors", "net_socket_register", "net_ppp_proto"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700926
Andrew Boie52fef672016-11-29 12:21:59 -0800927 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700928 """Constructor
929
Andrew Boiebbd670c2015-08-17 13:16:11 -0700930 @param filename Path to the output binary
931 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700932 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700933 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700934 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700935 magic = f.read(4)
936
Anas Nashifb4bdd662018-08-15 17:12:28 -0500937 try:
938 if (magic != b'\x7fELF'):
939 raise SanityRuntimeError("%s is not an ELF binary" % filename)
940 except Exception as e:
941 print(str(e))
942 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -0700943
944 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500945 # GREP can not be used as it returns an error if the symbol is not
946 # found.
947 is_xip_command = "nm " + filename + \
948 " | awk '/CONFIG_XIP/ { print $3 }'"
949 is_xip_output = subprocess.check_output(
950 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
951 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -0500952 try:
953 if is_xip_output.endswith("no symbols"):
954 raise SanityRuntimeError("%s has no symbol information" % filename)
955 except Exception as e:
956 print(str(e))
957 sys.exit(2)
958
Andrew Boie6acbe632015-07-17 12:03:52 -0700959 self.is_xip = (len(is_xip_output) != 0)
960
Andrew Boiebbd670c2015-08-17 13:16:11 -0700961 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700962 self.sections = []
963 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700964 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800965 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700966
967 self._calculate_sizes()
968
969 def get_ram_size(self):
970 """Get the amount of RAM the application will use up on the device
971
972 @return amount of RAM, in bytes
973 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700974 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700975
976 def get_rom_size(self):
977 """Get the size of the data that this application uses on device's flash
978
979 @return amount of ROM, in bytes
980 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700981 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700982
983 def unrecognized_sections(self):
984 """Get a list of sections inside the binary that weren't recognized
985
David B. Kinder29963c32017-06-16 12:32:42 -0700986 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700987 """
988 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700989 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700990 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700991 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700992 return slist
993
994 def _calculate_sizes(self):
995 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700996 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500997 objdump_output = subprocess.check_output(
998 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700999
1000 for line in objdump_output:
1001 words = line.split()
1002
1003 if (len(words) == 0): # Skip lines that are too short
1004 continue
1005
1006 index = words[0]
1007 if (not index[0].isdigit()): # Skip lines that do not start
1008 continue # with a digit
1009
1010 name = words[1] # Skip lines with section names
1011 if (name[0] == '.'): # starting with '.'
1012 continue
1013
Andrew Boie73b4ee62015-10-07 11:33:22 -07001014 # TODO this doesn't actually reflect the size in flash or RAM as
1015 # it doesn't include linker-imposed padding between sections.
1016 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -07001017 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -07001018 if size == 0:
1019 continue
1020
Andrew Boie73b4ee62015-10-07 11:33:22 -07001021 load_addr = int(words[4], 16)
1022 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -07001023
1024 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -07001025 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -07001026 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -07001027 if name in SizeCalculator.alloc_sections:
1028 self.ram_size += size
1029 stype = "alloc"
1030 elif name in SizeCalculator.rw_sections:
1031 self.ram_size += size
1032 self.rom_size += size
1033 stype = "rw"
1034 elif name in SizeCalculator.ro_sections:
1035 self.rom_size += size
1036 if not self.is_xip:
1037 self.ram_size += size
1038 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -07001039 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -07001040 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -08001041 if name not in self.extra_sections:
1042 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -07001043
Anas Nashif3ba1d432017-12-05 15:28:44 -05001044 self.sections.append({"name": name, "load_addr": load_addr,
1045 "size": size, "virt_addr": virt_addr,
1046 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -07001047
1048
1049class MakeGoal:
1050 """Metadata class representing one of the sub-makes called by MakeGenerator
1051
David B. Kinder29963c32017-06-16 12:32:42 -07001052 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -07001053 with TestInstances to get a complete picture of what happened during a test.
1054 MakeGenerator is used for tasks outside of building tests (such as
1055 defconfigs) which is why MakeGoal is a separate class from TestInstance.
1056 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001057
Anas Nashif4d25b502017-11-25 17:37:17 -05001058 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -07001059 self.name = name
1060 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -05001061 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001062 self.make_log = make_log
1063 self.build_log = build_log
1064 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -05001065 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001066 self.make_state = "waiting"
1067 self.failed = False
1068 self.finished = False
1069 self.reason = None
1070 self.metrics = {}
1071
1072 def get_error_log(self):
1073 if self.make_state == "waiting":
1074 # Shouldn't ever see this; breakage in the main Makefile itself.
1075 return self.make_log
1076 elif self.make_state == "building":
1077 # Failure when calling the sub-make to build the code
1078 return self.build_log
1079 elif self.make_state == "running":
Marc Herbert75276a72019-05-13 14:41:59 -07001080 # Failure in sub-make for "make run", qemu probably failed.
1081 # Return qemu's output if there is one, otherwise make's.
1082 h = Path(self.handler_log)
1083 if h.exists() and h.stat().st_size > 0:
1084 return self.handler_log
1085 else:
1086 return self.run_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001087 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -05001088 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -05001089 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001090
1091 def fail(self, reason):
1092 self.failed = True
1093 self.finished = True
1094 self.reason = reason
1095
1096 def success(self):
1097 self.finished = True
1098
1099 def __str__(self):
1100 if self.finished:
1101 if self.failed:
1102 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
1103 self.get_error_log())
1104 else:
1105 return "[%s] passed" % self.name
1106 else:
1107 return "[%s] in progress (%s)" % (self.name, self.make_state)
1108
1109
1110class MakeGenerator:
1111 """Generates a Makefile which just calls a bunch of sub-make sessions
1112
1113 In any given test suite we may need to build dozens if not hundreds of
1114 test cases. The cleanest way to parallelize this is to just let Make
1115 do the parallelization, sharing the jobserver among all the different
1116 sub-make targets.
1117 """
1118
1119 GOAL_HEADER_TMPL = """.PHONY: {goal}
1120{goal}:
1121"""
1122
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001123 MAKE_RULE_TMPL_CMAKE = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -04001124\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -05001125\t\t-G"{generator}"\\
Marc Herbertd7ca9032019-07-06 13:21:03 -07001126\t\t-S{directory}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001127\t\t-B{outdir}\\
1128\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
1129\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -05001130\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001131\t\t{args}\\
1132\t\t>{logfile} 2>&1
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001133"""
1134 MAKE_RULE_TMPL_BLD = """\t{generator_cmd} -C {outdir}\\
Anas Nashifa8a13882017-12-30 13:01:06 -05001135\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001136\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -07001137"""
Anas Nashif20f553f2018-03-23 11:26:41 -05001138 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
1139\t{generator_cmd} -C {outdir}\\
1140\t\t{verb} {make_args}\\
1141\t\t>>{logfile} 2>&1
1142"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001143
1144 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
1145
Anas Nashif3ba1d432017-12-05 15:28:44 -05001146 re_make = re.compile(
1147 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -07001148
Anas Nashif37f9dc52018-02-23 08:53:46 -06001149 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001150 """MakeGenerator constructor
1151
1152 @param base_outdir Intended to be the base out directory. A make.log
1153 file will be created here which contains the output of the
1154 top-level Make session, as well as the dynamic control Makefile
1155 @param verbose If true, pass V=1 to all the sub-makes which greatly
1156 increases their verbosity
1157 """
1158 self.goals = {}
1159 if not os.path.exists(base_outdir):
1160 os.makedirs(base_outdir)
1161 self.logfile = os.path.join(base_outdir, "make.log")
1162 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -06001163 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -07001164
1165 def _get_rule_header(self, name):
1166 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
1167
Anas Nashif3ba1d432017-12-05 15:28:44 -05001168 def _get_sub_make(self, name, phase, workdir, outdir,
1169 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -04001170 """
1171 @param args Arguments given to CMake
1172 @param make_args Arguments given to the Makefile generated by CMake
1173 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001174 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -05001175 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -07001176 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -05001177
1178 if self.deprecations:
1179 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -08001180
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +02001181 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -05001182
Anas Nashif25f6ab62018-03-06 07:15:11 -06001183 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001184 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -07001185 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001186 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -06001187 else:
1188 generator = "Unix Makefiles"
1189 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -07001190 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -05001191
Anas Nashif20f553f2018-03-23 11:26:41 -05001192 if phase == 'running':
1193 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
1194 generator_cmd=generator_cmd,
1195 phase=phase,
1196 goal=name,
1197 outdir=outdir,
1198 verb=verb,
1199 logfile=logfile,
1200 make_args=make_args
1201 )
1202 else:
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001203 cmake_rule = MakeGenerator.MAKE_RULE_TMPL_CMAKE.format(
Anas Nashif20f553f2018-03-23 11:26:41 -05001204 generator=generator,
Anas Nashif20f553f2018-03-23 11:26:41 -05001205 phase=phase,
1206 goal=name,
1207 outdir=outdir,
1208 cflags=cflags,
1209 ldflags=ldflags,
1210 directory=workdir,
Anas Nashif20f553f2018-03-23 11:26:41 -05001211 args=args,
1212 logfile=logfile,
Anas Nashif20f553f2018-03-23 11:26:41 -05001213 )
Andrew Boie6acbe632015-07-17 12:03:52 -07001214
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001215 if options.cmake_only:
1216 build_rule = ""
1217 else:
1218 build_rule = MakeGenerator.MAKE_RULE_TMPL_BLD.format(
1219 generator_cmd=generator_cmd,
1220 outdir=outdir,
1221 verb=verb,
1222 make_args=make_args,
1223 logfile=logfile,
1224 )
1225
1226 return cmake_rule + build_rule
1227
Andrew Boie6acbe632015-07-17 12:03:52 -07001228 def _get_rule_footer(self, name):
1229 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
1230
Anas Nashif3ba1d432017-12-05 15:28:44 -05001231 def add_build_goal(self, name, directory, outdir,
1232 args, buildlog, make_args=""):
Anas Nashif13773752018-07-06 18:20:23 -05001233 """Add a goal to invoke a build session
Andrew Boie6acbe632015-07-17 12:03:52 -07001234
1235 @param name A unique string name for this build goal. The results
1236 dictionary returned by execute() will be keyed by this name.
1237 @param directory Absolute path to working directory, will be passed
1238 to make -C
1239 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001240 cmake via -B=<path>
1241 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -07001242 environment variables or specific Make goals
1243 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001244
Anas Nashif13773752018-07-06 18:20:23 -05001245 if not os.path.exists(outdir):
1246 os.makedirs(outdir)
1247
1248 build_logfile = os.path.join(outdir, buildlog)
1249 text = self._get_rule_header(name)
1250 text += self._get_sub_make(name, "building", directory, outdir, build_logfile,
1251 args, make_args=make_args)
1252 text += self._get_rule_footer(name)
1253
1254 self.goals[name] = MakeGoal( name, text, None, self.logfile, build_logfile, None, None)
1255
1256 def add_goal(self, instance, type, args, make_args=""):
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001257
Anas Nashif13773752018-07-06 18:20:23 -05001258 """Add a goal to build a Zephyr project and then run it using a handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001259
1260 The generated make goal invokes Make twice, the first time it will
1261 build the default goal, and the second will invoke the 'qemu' goal.
Anas Nashif13773752018-07-06 18:20:23 -05001262 The output of the handler session will be monitored, and terminated
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 either upon pass/fail result of the test program, or the timeout
1264 is reached.
1265
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001266 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001267 """
1268
Anas Nashif576be982017-12-23 20:20:27 -05001269 name = instance.name
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001270 directory = instance.test.test_path
Anas Nashif576be982017-12-23 20:20:27 -05001271 outdir = instance.outdir
1272
Andrew Boie6acbe632015-07-17 12:03:52 -07001273 build_logfile = os.path.join(outdir, "build.log")
1274 run_logfile = os.path.join(outdir, "run.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001275
Anas Nashif13773752018-07-06 18:20:23 -05001276 if not os.path.exists(outdir):
1277 os.makedirs(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001278
Anas Nashif13773752018-07-06 18:20:23 -05001279 handler = None
1280 if type == "qemu":
Anas Nashifd18ec532019-04-11 23:20:39 -04001281 handler = QEMUHandler(instance, "qemu")
Anas Nashif13773752018-07-06 18:20:23 -05001282 elif type == "native":
Anas Nashifd18ec532019-04-11 23:20:39 -04001283 handler = BinaryHandler(instance, "native")
Marc Herbert9e573382019-07-03 12:49:42 -07001284 # defined by __build_dir in cmake/boilerplate.cmake
Anas Nashifdf7ee612018-07-07 06:09:01 -05001285 handler.binary = os.path.join(outdir, "zephyr", "zephyr.exe")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001286 if options.enable_coverage:
1287 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001288 elif type == "nsim":
Anas Nashifd18ec532019-04-11 23:20:39 -04001289 handler = BinaryHandler(instance, "nsim")
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001290 handler.call_make_run = True
Anas Nashif13773752018-07-06 18:20:23 -05001291 elif type == "unit":
Anas Nashifd18ec532019-04-11 23:20:39 -04001292 handler = BinaryHandler(instance, "unit")
Anas Nashifdf7ee612018-07-07 06:09:01 -05001293 handler.binary = os.path.join(outdir, "testbinary")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001294 if options.enable_coverage:
1295 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif13773752018-07-06 18:20:23 -05001296 elif type == "device":
Anas Nashifd18ec532019-04-11 23:20:39 -04001297 handler = DeviceHandler(instance, "device")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001298 elif type == "renode":
Anas Nashifd18ec532019-04-11 23:20:39 -04001299 handler = BinaryHandler(instance, "renode")
Jan Kowalewski265895b2019-01-07 16:40:24 +01001300 handler.pid_fn = os.path.join(instance.outdir, "renode.pid")
1301 handler.call_make_run = True
Anas Nashif576be982017-12-23 20:20:27 -05001302
Anas Nashif13773752018-07-06 18:20:23 -05001303 if type == 'qemu':
1304 args.append("QEMU_PIPE=%s" % handler.get_fifo())
1305
Andy Doancbecadd2019-02-08 10:19:10 -06001306 text = self._get_rule_header(name)
1307 if not options.test_only:
1308 text += self._get_sub_make(name, "building", directory, outdir,
1309 build_logfile, args, make_args=make_args)
Anas Nashif13773752018-07-06 18:20:23 -05001310 if handler and handler.run:
1311 text += self._get_sub_make(name, "running", directory,
1312 outdir, run_logfile,
1313 args, make_args="run")
Anas Nashif4d25b502017-11-25 17:37:17 -05001314
Anas Nashif13773752018-07-06 18:20:23 -05001315 text += self._get_rule_footer(name)
Anas Nashif73440ea2018-02-19 10:57:03 -06001316
Anas Nashif73440ea2018-02-19 10:57:03 -06001317 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001318 run_logfile, handler.log if handler else None)
Anas Nashif73440ea2018-02-19 10:57:03 -06001319
Anas Nashif13773752018-07-06 18:20:23 -05001320
Anas Nashif37f9dc52018-02-23 08:53:46 -06001321 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001322 """Add a goal to build/test a TestInstance object
1323
1324 @param ti TestInstance object to build. The status dictionary returned
1325 by execute() will be keyed by its .name field.
1326 """
1327 args = ti.test.extra_args[:]
Anas Nashiff9e73c92019-02-12 13:59:08 -05001328
1329 # merge overlay files into one variable
1330 overlays = ""
1331 idx = 0
1332 for a in args:
1333 m = re.search('OVERLAY_CONFIG="(.*)"', a)
1334 if m:
1335 overlays += m.group(1)
1336 del args[idx]
1337 idx += 1
1338
Anas Nashif3cbffef2018-11-07 23:50:54 -05001339 if len(ti.test.extra_configs) > 0 or options.coverage:
Anas Nashiff9e73c92019-02-12 13:59:08 -05001340 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
Marc Herbertc7633de2019-07-06 15:52:31 -07001341 os.path.join(ti.outdir,
1342 "sanitycheck", "testcase_extra.conf")))
Anas Nashiffa695d22017-10-04 16:14:27 -04001343
Anas Nashif3cbffef2018-11-07 23:50:54 -05001344 if ti.test.type == "unit" and options.enable_coverage:
1345 args.append("COVERAGE=1")
1346
Anas Nashiffb91ad62017-10-31 08:33:17 -04001347 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001348 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001349
Anas Nashif37f9dc52018-02-23 08:53:46 -06001350 do_build_only = ti.build_only or options.build_only
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001351 do_run = not do_build_only and not options.cmake_only
Andy Rossdc4151f2019-01-03 14:17:43 -08001352 skip_slow = ti.test.slow and not options.enable_slow
Anas Nashif5df8cff2018-02-23 08:37:14 -06001353
Anas Nashif13773752018-07-06 18:20:23 -05001354 # FIXME: Need refactoring and cleanup
1355 type = None
Anas Nashif5df8cff2018-02-23 08:37:14 -06001356 if ti.platform.qemu_support and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001357 type = "qemu"
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001358 elif ti.test.type == "unit":
Anas Nashif13773752018-07-06 18:20:23 -05001359 type = "unit"
Anas Nashif5df8cff2018-02-23 08:37:14 -06001360 elif ti.platform.type == "native" and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001361 type = "native"
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001362 elif ti.platform.simulation == "nsim" and do_run:
Anas Nashife24350c2018-07-11 15:09:22 -05001363 if find_executable("nsimdrv"):
1364 type = "nsim"
Jan Kowalewski265895b2019-01-07 16:40:24 +01001365 elif ti.platform.simulation == "renode" and do_run:
1366 if find_executable("renode"):
1367 type = "renode"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001368 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif13773752018-07-06 18:20:23 -05001369 type = "device"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001370
Andy Rossdc4151f2019-01-03 14:17:43 -08001371 if not skip_slow:
1372 self.add_goal(ti, type, args)
1373 else:
1374 verbose("Skipping slow test: " + ti.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001375
1376 def execute(self, callback_fn=None, context=None):
1377 """Execute all the registered build goals
1378
1379 @param callback_fn If not None, a callback function will be called
1380 as individual goals transition between states. This function
1381 should accept two parameters: a string state and an arbitrary
1382 context object, supplied here
1383 @param context Context object to pass to the callback function.
1384 Type and semantics are specific to that callback function.
1385 @return A dictionary mapping goal names to final status.
1386 """
1387
Andrew Boie08ce5a52016-02-22 13:28:10 -08001388 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001389 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001390 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001391 # Create our dynamic Makefile and execute it.
1392 # Watch stderr output which is where we will keep
1393 # track of build state
Marc Herbert6f011c92019-03-20 13:58:55 -07001394 tf.write('\n# Generated by %s which is expected\n' % __file__)
Marc Herbertaf1090c2019-04-30 14:11:29 -07001395 tf.write('# to create QEMU_PIPE, spawn zephyr.exe, etc. \n\n')
Marc Herbert6f011c92019-03-20 13:58:55 -07001396 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07001397 tf.write(goal.text)
Marc Herbert6f011c92019-03-20 13:58:55 -07001398 tf.write("all: %s\n" % (" ".join(sorted(self.goals.keys()))))
Andrew Boie6acbe632015-07-17 12:03:52 -07001399 tf.flush()
1400
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03001401 cmd = ["make", "-k", "-j", str(JOBS), "-f", tf.name, "all"]
Andy Rossdec163f2018-05-21 10:12:59 -07001402
Anas Nashiff2cb20c2019-06-18 14:45:40 -04001403 # assure language neutral environment
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001404 make_env = os.environ.copy()
1405 make_env['LC_MESSAGES'] = 'C.UTF-8'
Marc Herbert9e573382019-07-03 12:49:42 -07001406 verbose("In %s, spawning: " % os.getcwd()
1407 + " ".join(shlex.quote(word)for word in cmd))
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001408 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1409 stdout=devnull, env=make_env)
Andrew Boie6acbe632015-07-17 12:03:52 -07001410
1411 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001412 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001413 make_log.write(line)
1414 verbose("MAKE: " + repr(line.strip()))
1415 m = MakeGenerator.re_make.match(line)
1416 if not m:
1417 continue
1418
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001419 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001420 if error:
1421 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001422 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001423 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001424 # nonzero status.
1425 # Need to distinguish this case from a compilation failure.
Anas Nashif042d9b72019-04-11 10:31:01 -04001426 if goal.make_state == "building":
1427 goal.fail("build_error")
1428 elif goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001429 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001430 else:
Anas Nashif042d9b72019-04-11 10:31:01 -04001431 goal.fail("unknown_error")
1432
Andrew Boie6acbe632015-07-17 12:03:52 -07001433 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001434 goal = self.goals[name]
1435 goal.make_state = state
1436
Andrew Boie6acbe632015-07-17 12:03:52 -07001437 if state == "finished":
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001438 if goal.handler and not options.cmake_only:
Anas Nashif576be982017-12-23 20:20:27 -05001439 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001440 goal.handler.handle()
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001441 goal.handler_log = goal.handler.log
Anas Nashifcc164222017-12-26 11:02:46 -05001442
Anas Nashif4d25b502017-11-25 17:37:17 -05001443 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001444 goal.metrics.update(metrics)
1445 if thread_status == "passed":
1446 goal.success()
1447 else:
1448 goal.fail(thread_status)
1449 else:
1450 goal.success()
1451
1452 if callback_fn:
1453 callback_fn(context, self.goals, goal)
1454
1455 p.wait()
1456 return self.goals
1457
1458
1459# "list" - List of strings
1460# "list:<type>" - List of <type>
1461# "set" - Set of unordered, unique strings
1462# "set:<type>" - Set of <type>
1463# "float" - Floating point
1464# "int" - Integer
1465# "bool" - Boolean
1466# "str" - String
1467
1468# XXX Be sure to update __doc__ if you change any of this!!
1469
Anas Nashif3ba1d432017-12-05 15:28:44 -05001470platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
Anas Nashif924a4e72018-10-18 12:25:55 -04001471 "supported_toolchains": {"type": "list", "default": []},
1472 "env": {"type": "list", "default": []}
1473 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001474
Anas Nashif3ba1d432017-12-05 15:28:44 -05001475testcase_valid_keys = {"tags": {"type": "set", "required": False},
1476 "type": {"type": "str", "default": "integration"},
1477 "extra_args": {"type": "list"},
1478 "extra_configs": {"type": "list"},
1479 "build_only": {"type": "bool", "default": False},
1480 "build_on_all": {"type": "bool", "default": False},
1481 "skip": {"type": "bool", "default": False},
1482 "slow": {"type": "bool", "default": False},
1483 "timeout": {"type": "int", "default": 60},
1484 "min_ram": {"type": "int", "default": 8},
1485 "depends_on": {"type": "set"},
1486 "min_flash": {"type": "int", "default": 32},
1487 "arch_whitelist": {"type": "set"},
1488 "arch_exclude": {"type": "set"},
1489 "extra_sections": {"type": "list", "default": []},
1490 "platform_exclude": {"type": "set"},
1491 "platform_whitelist": {"type": "set"},
1492 "toolchain_exclude": {"type": "set"},
1493 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001494 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001495 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301496 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001497 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001498
1499
1500class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001501 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001502 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001503
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001504 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001505 """Instantiate a new SanityConfigParser object
1506
Anas Nashifa792a3d2017-04-04 18:47:49 -04001507 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001508 """
Anas Nashif255625b2017-12-05 15:08:26 -05001509 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001510 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001511 self.tests = {}
1512 self.common = {}
1513 if 'tests' in self.data:
1514 self.tests = self.data['tests']
1515 if 'common' in self.data:
1516 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001517
1518 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001519 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001520 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001521 if typestr == "str":
1522 return v
1523
1524 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001525 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001526
1527 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001528 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001529
1530 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001531 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001532
Anas Nashif3ba1d432017-12-05 15:28:44 -05001533 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001534 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001535 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001536 vs = v.split()
1537 if len(typestr) > 4 and typestr[4] == ":":
1538 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1539 else:
1540 return vs
1541
1542 elif typestr.startswith("set"):
1543 vs = v.split()
1544 if len(typestr) > 3 and typestr[3] == ":":
1545 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1546 else:
1547 return set(vs)
1548
Anas Nashif576be982017-12-23 20:20:27 -05001549 elif typestr.startswith("map"):
1550 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001551 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001552 raise ConfigurationError(
1553 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001554
Anas Nashifb4754ed2017-12-05 17:27:58 -05001555 def get_test(self, name, valid_keys):
1556 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001557
Anas Nashifb4754ed2017-12-05 17:27:58 -05001558 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001559 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001560 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001561 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001562 here, it will generate an error. Each value in this dictionary
1563 is another dictionary containing metadata:
1564
1565 "default" - Default value if not given
1566 "type" - Data type to convert the text value to. Simple types
1567 supported are "str", "float", "int", "bool" which will get
1568 converted to respective Python data types. "set" and "list"
1569 may also be specified which will split the value by
1570 whitespace (but keep the elements as strings). finally,
1571 "list:<type>" and "set:<type>" may be given which will
1572 perform a type conversion after splitting the value up.
1573 "required" - If true, raise an error if not defined. If false
1574 and "default" isn't specified, a type conversion will be
1575 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001576 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001577 type conversion and default values filled in per valid_keys
1578 """
1579
1580 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001581 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001582 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001583
Anas Nashifb4754ed2017-12-05 17:27:58 -05001584 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001585 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001586 raise ConfigurationError(
1587 self.filename,
1588 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001589 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001590
Anas Nashiffa695d22017-10-04 16:14:27 -04001591 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001592 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001593 d[k] += " " + v
1594 else:
1595 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001596
Andrew Boie08ce5a52016-02-22 13:28:10 -08001597 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001598 if k not in d:
1599 if "required" in kinfo:
1600 required = kinfo["required"]
1601 else:
1602 required = False
1603
1604 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001605 raise ConfigurationError(
1606 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001607 "missing required value for '%s' in test '%s'" %
1608 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001609 else:
1610 if "default" in kinfo:
1611 default = kinfo["default"]
1612 else:
1613 default = self._cast_value("", kinfo["type"])
1614 d[k] = default
1615 else:
1616 try:
1617 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001618 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001619 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001620 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1621 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001622
1623 return d
1624
1625
1626class Platform:
1627 """Class representing metadata for a particular platform
1628
Anas Nashifc7406082015-12-13 15:00:31 -05001629 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001630
1631 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001632 os.path.join(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001633 ZEPHYR_BASE,
Anas Nashif3ba1d432017-12-05 15:28:44 -05001634 "scripts",
1635 "sanity_chk",
1636 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001637
Anas Nashifa792a3d2017-04-04 18:47:49 -04001638 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001639 """Constructor.
1640
Anas Nashif877d3ca2017-12-05 17:39:29 -05001641 @param cfile Path to platform configuration file, which gives
1642 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001643 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001644 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001645 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001646
Anas Nashif255625b2017-12-05 15:08:26 -05001647 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001648 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001649 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001650 self.ram = data.get("ram", 128)
1651 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001652 self.ignore_tags = testing.get("ignore_tags", [])
1653 self.default = testing.get("default", False)
1654 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001655 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001656 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001657 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001658 for item in supp_feature.split(":"):
1659 self.supported.add(item)
1660
Anas Nashif8acdbd72018-01-04 14:15:22 -05001661 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001662 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001663 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001664 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001665 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001666 self.env = data.get("env", [])
1667 self.env_satisfied = True
1668 for env in self.env:
1669 if os.environ.get(env, None) == None:
1670 self.env_satisfied = False
Andrew Boie41878222016-11-03 11:58:53 -07001671 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001672 pass
1673
Andrew Boie6acbe632015-07-17 12:03:52 -07001674 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001675 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001676
1677
1678class Architecture:
1679 """Class representing metadata for a particular architecture
1680 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001681
Anas Nashifa792a3d2017-04-04 18:47:49 -04001682 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001683 """Architecture constructor
1684
Anas Nashif877d3ca2017-12-05 17:39:29 -05001685 @param name String name for this architecture
1686 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001687 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001688 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001689
Anas Nashifa792a3d2017-04-04 18:47:49 -04001690 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001691
1692 def __repr__(self):
1693 return "<arch %s>" % self.name
1694
1695
1696class TestCase:
1697 """Class representing a test application
1698 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001699
Anas Nashif7fae29c2017-10-09 13:19:12 -04001700 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001701 """TestCase constructor.
1702
Anas Nashif877d3ca2017-12-05 17:39:29 -05001703 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001704 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001705 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001706
Andrew Boie6acbe632015-07-17 12:03:52 -07001707 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001708 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001709 the test case is <workdir>/<name>.
1710
Marc Herbert1c8632c2019-04-15 17:58:45 -07001711 @param testcase_root os.path.abspath() of one of the --testcase-root
1712 @param workdir Sub-directory of testcase_root where the
1713 .yaml test configuration file was found
Anas Nashif877d3ca2017-12-05 17:39:29 -05001714 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001715 in the test case configuration file. For many test cases that just
1716 define one test, can be anything and is usually "test". This is
1717 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001718 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001719 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001720 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001721 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001722 self.test_path = os.path.join(testcase_root, workdir)
1723
Anas Nashifaae71d72018-04-21 22:26:48 -05001724 self.id = name
1725 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001726 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001727 self.tags = tc_dict["tags"]
1728 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001729 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001730 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001731 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001732 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001733 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001734 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001735 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1736 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001737 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001738 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001739 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001740 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001741 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001742 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001743 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001744 self.min_ram = tc_dict["min_ram"]
1745 self.depends_on = tc_dict["depends_on"]
1746 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001747 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001748
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001749 self.name = self.get_unique(testcase_root, workdir, name)
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001750
Anas Nashif2cf0df02015-10-10 09:29:43 -04001751 self.defconfig = {}
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001752 self.dt_config = {}
Anas Nashif45a97862019-01-09 08:46:42 -05001753 self.cmake_cache = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001754 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001755
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001756
1757 def get_unique(self, testcase_root, workdir, name):
1758
Marc Herbert1c8632c2019-04-15 17:58:45 -07001759 canonical_testcase_root = os.path.realpath(testcase_root)
Tyler Hallbb359252019-06-19 02:11:35 -04001760 if Path(canonical_zephyr_base) in Path(canonical_testcase_root).parents:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001761 # This is in ZEPHYR_BASE, so include path in name for uniqueness
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001762 # FIXME: We should not depend on path of test for unique names.
Marc Herbert1c8632c2019-04-15 17:58:45 -07001763 relative_tc_root = os.path.relpath(canonical_testcase_root,
1764 start=canonical_zephyr_base)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001765 else:
Marc Herbert1c8632c2019-04-15 17:58:45 -07001766 relative_tc_root = ""
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001767
Marc Herbert1c8632c2019-04-15 17:58:45 -07001768 # workdir can be "."
1769 unique = os.path.normpath(os.path.join(relative_tc_root, workdir, name))
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001770 return unique
1771
Anas Nashifaae71d72018-04-21 22:26:48 -05001772 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001773 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001774 # do not match until end-of-line, otherwise we won't allow
1775 # stc_regex below to catch the ones that are declared in the same
1776 # line--as we only search starting the end of this match
1777 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001778 re.MULTILINE)
1779 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001780 br"^\s*" # empy space at the beginning is ok
1781 # catch the case where it is declared in the same sentence, e.g:
1782 #
1783 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1784 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1785 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1786 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1787 # Consume the argument that becomes the extra testcse
1788 br"\(\s*"
1789 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1790 # _setup_teardown() variant has two extra arguments that we ignore
1791 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1792 br"\s*\)",
1793 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001794 re.MULTILINE)
1795 suite_run_regex = re.compile(
1796 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1797 re.MULTILINE)
1798 achtung_regex = re.compile(
1799 br"(#ifdef|#endif)",
1800 re.MULTILINE)
1801 warnings = None
1802
1803 with open(inf_name) as inf:
1804 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1805 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001806 suite_regex_match = suite_regex.search(main_c)
1807 if not suite_regex_match:
1808 # can't find ztest_test_suite, maybe a client, because
1809 # it includes ztest.h
1810 return None, None
1811
1812 suite_run_match = suite_run_regex.search(main_c)
1813 if not suite_run_match:
1814 raise ValueError("can't find ztest_run_test_suite")
1815
1816 achtung_matches = re.findall(
1817 achtung_regex,
1818 main_c[suite_regex_match.end():suite_run_match.start()])
1819 if achtung_matches:
1820 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001821 % ", ".join(set([
1822 match.decode() for match in achtung_matches
1823 ]))
1824 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001825 stc_regex,
1826 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001827 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001828 return matches, warnings
1829
1830 def scan_path(self, path):
1831 subcases = []
1832 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1833 try:
1834 _subcases, warnings = self.scan_file(filename)
1835 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001836 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001837 if _subcases:
1838 subcases += _subcases
1839 except ValueError as e:
Flavio Ceolinbf878ce2019-04-19 22:24:09 -07001840 error("%s: can't find: %s" % (filename, e))
Anas Nashifaae71d72018-04-21 22:26:48 -05001841 return subcases
1842
1843
1844 def parse_subcases(self):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001845 results = self.scan_path(self.test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001846 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001847 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001848 self.cases.append(name)
1849
Anas Nashiff16e92c2019-03-31 16:58:12 -04001850 if not results:
1851 self.cases.append(self.id)
1852
Anas Nashifaae71d72018-04-21 22:26:48 -05001853
Anas Nashif75547e22018-02-24 08:32:14 -06001854 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001855 return self.name
1856
1857
Andrew Boie6acbe632015-07-17 12:03:52 -07001858class TestInstance:
1859 """Class representing the execution of a particular TestCase on a platform
1860
1861 @param test The TestCase object we want to build/execute
1862 @param platform Platform object that we want to build and run against
1863 @param base_outdir Base directory for all test results. The actual
1864 out directory used is <outdir>/<platform>/<test case name>
1865 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001866
Anas Nashif37f9dc52018-02-23 08:53:46 -06001867 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001868 self.test = test
1869 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001870 self.name = os.path.join(platform.name, test.name)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001871 self.outdir = os.path.join(base_outdir, platform.name, test.name)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001872
1873 self.build_only = options.build_only or test.build_only \
Kumar Gala84cf9dc2019-06-15 09:36:06 -05001874 or self.check_dependency() or options.cmake_only
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001875 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001876
Marc Herbert0f7255c2019-04-05 14:14:21 -07001877 def __lt__(self, other):
1878 return self.name < other.name
1879
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001880 def check_dependency(self):
1881 build_only = False
1882 if self.test.harness == 'console':
1883 if "fixture" in self.test.harness_config:
1884 fixture = self.test.harness_config['fixture']
1885 if fixture not in options.fixture:
1886 build_only = True
1887 elif self.test.harness:
1888 build_only = True
1889
1890 return build_only
1891
Anas Nashifdbd76492018-11-23 20:24:19 -05001892 def create_overlay(self, platform):
Marc Herbertc7633de2019-07-06 15:52:31 -07001893 # Create this in a "sanitycheck/" subdirectory otherwise this
1894 # will pass this overlay to kconfig.py *twice* and kconfig.cmake
1895 # will silently give that second time precedence over any
1896 # --extra-args=CONFIG_*
1897 subdir = os.path.join(self.outdir, "sanitycheck")
1898 os.makedirs(subdir, exist_ok=True)
1899 file = os.path.join(subdir, "testcase_extra.conf")
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001900 with open(file, "w") as f:
1901 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001902
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001903 if len(self.test.extra_configs) > 0:
1904 content = "\n".join(self.test.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001905
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001906 if options.enable_coverage:
1907 if platform in options.coverage_platform:
1908 content = content + "\nCONFIG_COVERAGE=y"
1909
1910 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001911
Andrew Boie6acbe632015-07-17 12:03:52 -07001912 def calculate_sizes(self):
1913 """Get the RAM/ROM sizes of a test case.
1914
1915 This can only be run after the instance has been executed by
1916 MakeGenerator, otherwise there won't be any binaries to measure.
1917
1918 @return A SizeCalculator object
1919 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001920 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001921 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001922 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001923 if (len(fns) != 1):
1924 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001925 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001926
1927 def __repr__(self):
1928 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1929
1930
Andrew Boie4ef16c52015-08-28 12:36:03 -07001931def defconfig_cb(context, goals, goal):
1932 if not goal.failed:
1933 return
1934
1935 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001936 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001937 if INLINE_LOGS:
1938 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001939 data = fp.read()
1940 sys.stdout.write(data)
1941 if log_file:
1942 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001943 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001944 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001945
Andrew Boie6acbe632015-07-17 12:03:52 -07001946
1947class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001948 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001949 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001950
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001951 yaml_tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001952 os.path.join(ZEPHYR_BASE,
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001953 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001954
Anas Nashif37f9dc52018-02-23 08:53:46 -06001955 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001956 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001957 self.arches = {}
1958 self.testcases = {}
1959 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001960 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001961 self.instances = {}
1962 self.goals = None
1963 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001964 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001965
Andrew Boie3d348712016-04-08 11:52:13 -07001966 for testcase_root in testcase_roots:
1967 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001968
Andrew Boie3d348712016-04-08 11:52:13 -07001969 debug("Reading test case configuration files under %s..." %
1970 testcase_root)
1971 for dirpath, dirnames, filenames in os.walk(testcase_root,
1972 topdown=True):
1973 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001974 if 'sample.yaml' in filenames:
1975 filename = 'sample.yaml'
1976 elif 'testcase.yaml' in filenames:
1977 filename = 'testcase.yaml'
1978 else:
1979 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001980
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001981 verbose("Found possible test case in " + dirpath)
1982 dirnames[:] = []
1983 yaml_path = os.path.join(dirpath, filename)
1984 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001985 parsed_data = SanityConfigParser(
1986 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001987
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001988 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001989
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001990 for name in parsed_data.tests.keys():
1991 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1992 tc = TestCase(testcase_root, workdir, name, tc_dict,
1993 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001994 tc.parse_subcases()
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001995 self.testcases[tc.name] = tc
1996
1997 except Exception as e:
1998 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001999 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02002000
Andrew Boie6acbe632015-07-17 12:03:52 -07002001
Anas Nashif86c8e232017-10-09 13:42:28 -04002002 for board_root in board_root_list:
2003 board_root = os.path.abspath(board_root)
2004
Anas Nashif3ba1d432017-12-05 15:28:44 -05002005 debug(
2006 "Reading platform configuration files under %s..." %
2007 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05002008 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
2009 verbose("Found plaform configuration " + fn)
2010 try:
2011 platform = Platform(fn)
Anas Nashiff3d48e12018-07-24 08:14:42 -05002012 if platform.sanitycheck:
2013 self.platforms.append(platform)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05002014 except RuntimeError as e:
2015 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05002016 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002017
Anas Nashifa792a3d2017-04-04 18:47:49 -04002018 arches = []
2019 for p in self.platforms:
2020 arches.append(p.arch)
2021 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002022 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04002023 arch = Architecture(a, aplatforms)
2024 self.arches[a] = arch
2025
Andrew Boie6acbe632015-07-17 12:03:52 -07002026 self.instances = {}
2027
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002028 def get_platform(self, name):
2029 selected_platform = None
2030 for platform in self.platforms:
2031 if platform.name == name:
2032 selected_platform = platform
2033 break
2034 return selected_platform
Anas Nashifb4bdd662018-08-15 17:12:28 -05002035
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002036 def get_last_failed(self):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002037 try:
2038 if not os.path.exists(LAST_SANITY):
2039 raise SanityRuntimeError("Couldn't find last sanity run.")
2040 except Exception as e:
2041 print(str(e))
2042 sys.exit(2)
2043
Andrew Boie6acbe632015-07-17 12:03:52 -07002044 result = []
2045 with open(LAST_SANITY, "r") as fp:
2046 cr = csv.DictReader(fp)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002047 instance_list = []
Andrew Boie6acbe632015-07-17 12:03:52 -07002048 for row in cr:
2049 if row["passed"] == "True":
2050 continue
2051 test = row["test"]
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002052 platform = self.get_platform(row["platform"])
2053 instance = TestInstance(self.testcases[test], platform, self.outdir)
2054 instance.create_overlay(platform.name)
2055 instance_list.append(instance)
2056 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07002057
Anas Nashifbd166f42017-09-02 12:32:08 -04002058 def load_from_file(self, file):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002059 try:
2060 if not os.path.exists(file):
2061 raise SanityRuntimeError(
2062 "Couldn't find input file with list of tests.")
2063 except Exception as e:
2064 print(str(e))
2065 sys.exit(2)
2066
Anas Nashifbd166f42017-09-02 12:32:08 -04002067 with open(file, "r") as fp:
2068 cr = csv.reader(fp)
2069 instance_list = []
2070 for row in cr:
2071 name = os.path.join(row[0], row[1])
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05002072 platform = self.get_platform(row[2])
2073 instance = TestInstance(self.testcases[name], platform, self.outdir)
2074 instance.create_overlay(platform.name)
Anas Nashifbd166f42017-09-02 12:32:08 -04002075 instance_list.append(instance)
2076 self.add_instances(instance_list)
2077
Anas Nashif4f028882017-12-30 11:48:43 -05002078 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04002079
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002080 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
2081 os.environ.get("ZEPHYR_GCC_VARIANT", None)
Anas Nashifb4bdd662018-08-15 17:12:28 -05002082
Sebastian Bøe5681f872018-10-12 16:03:49 +02002083 if toolchain == "gccarmemb":
2084 # Remove this translation when gccarmemb is no longer supported.
2085 toolchain = "gnuarmemb"
Anas Nashifb4bdd662018-08-15 17:12:28 -05002086
2087 try:
2088 if not toolchain:
2089 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
2090 except Exception as e:
2091 print(str(e))
2092 sys.exit(2)
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002093
2094
Andrew Boie6acbe632015-07-17 12:03:52 -07002095 instances = []
2096 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05002097 platform_filter = options.platform
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002098 testcase_filter = run_individual_tests
Anas Nashif4f028882017-12-30 11:48:43 -05002099 arch_filter = options.arch
2100 tag_filter = options.tag
2101 exclude_tag = options.exclude_tag
2102 config_filter = options.config
2103 extra_args = options.extra_args
2104 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04002105
Andrew Boie6acbe632015-07-17 12:03:52 -07002106 verbose("platform filter: " + str(platform_filter))
2107 verbose(" arch_filter: " + str(arch_filter))
2108 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04002109 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07002110 verbose(" config_filter: " + str(config_filter))
2111
Andrew Boie821d8322016-03-22 10:08:35 -07002112 default_platforms = False
2113
2114 if all_plats:
2115 info("Selecting all possible platforms per test case")
2116 # When --all used, any --platform arguments ignored
2117 platform_filter = []
2118 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07002119 info("Selecting default platforms per test case")
2120 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07002121
Sebastian Bøe781e3982017-11-09 11:43:33 +01002122 mg = MakeGenerator(self.outdir)
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002123 defconfig_list = {}
2124 dt_list = {}
Anas Nashif45a97862019-01-09 08:46:42 -05002125 cmake_list = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08002126 for tc_name, tc in self.testcases.items():
2127 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04002128 for plat in arch.platforms:
2129 instance = TestInstance(tc, plat, self.outdir)
2130
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002131 if (arch_name == "unit") != (tc.type == "unit"):
2132 continue
2133
Anas Nashifbfab06b2017-06-22 09:22:24 -04002134 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002135 platform_filter = []
2136
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002137 if tc.skip:
2138 continue
2139
Anas Nashif2cf0df02015-10-10 09:29:43 -04002140 if tag_filter and not tc.tags.intersection(tag_filter):
2141 continue
2142
Anas Nashifdfa86e22016-10-24 17:08:56 -04002143 if exclude_tag and tc.tags.intersection(exclude_tag):
2144 continue
2145
Anas Nashif2cf0df02015-10-10 09:29:43 -04002146 if testcase_filter and tc_name not in testcase_filter:
2147 continue
2148
Anas Nashif2cf0df02015-10-10 09:29:43 -04002149 if arch_filter and arch_name not in arch_filter:
2150 continue
2151
2152 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2153 continue
2154
2155 if tc.arch_exclude and arch.name in tc.arch_exclude:
2156 continue
2157
2158 if tc.platform_exclude and plat.name in tc.platform_exclude:
2159 continue
2160
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002161 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2162 continue
2163
Anas Nashif2cf0df02015-10-10 09:29:43 -04002164 if platform_filter and plat.name not in platform_filter:
2165 continue
2166
Anas Nashif62224182017-08-09 23:55:53 -04002167 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002168 continue
2169
2170 if set(plat.ignore_tags) & tc.tags:
2171 continue
2172
Kumar Gala5141d522017-07-07 08:05:48 -05002173 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002174 dep_intersection = tc.depends_on.intersection(
2175 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002176 if dep_intersection != set(tc.depends_on):
2177 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002178
2179 if plat.flash < tc.min_flash:
2180 continue
2181
Anas Nashif2cf0df02015-10-10 09:29:43 -04002182 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2183 continue
2184
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002185 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2186 continue
2187
Anas Nashif924a4e72018-10-18 12:25:55 -04002188 if (plat.env_satisfied and tc.tc_filter
2189 and (plat.default or all_plats or platform_filter)
Anas Nashif07d54c02018-07-21 19:29:08 -05002190 and (toolchain in plat.supported_toolchains or options.force_toolchain)):
Anas Nashif8ea9d022015-11-10 12:24:20 -05002191 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04002192 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07002193 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002194 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07002195 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07002196 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05002197 # each other since they all try to build them
2198 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04002199
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002200 o = os.path.join(self.outdir, plat.name, tc.name)
Anas Nashif45a97862019-01-09 08:46:42 -05002201 cmake_cache_path = os.path.join(o, "CMakeCache.txt")
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002202 generated_dt_confg = "include/generated/generated_dts_board.conf"
2203 dt_config_path = os.path.join(o, "zephyr", generated_dt_confg)
2204 dt_list[tc, plat, tc.name.split("/")[-1]] = dt_config_path
Anas Nashif45a97862019-01-09 08:46:42 -05002205 cmake_list[tc, plat, tc.name.split("/")[-1]] = cmake_cache_path
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002206 defconfig_list[tc, plat, tc.name.split("/")[-1]] = os.path.join(o, "zephyr", ".config")
Anas Nashif13773752018-07-06 18:20:23 -05002207 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002208 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.test_path),
Anas Nashif13773752018-07-06 18:20:23 -05002209 o, args, "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04002210
2211 info("Building testcase defconfigs...")
2212 results = mg.execute(defconfig_cb)
2213
Andrew Boie9e69c542019-04-09 12:14:59 -07002214 info("Filtering test cases...")
Andrew Boie08ce5a52016-02-22 13:28:10 -08002215 for name, goal in results.items():
Anas Nashifb4bdd662018-08-15 17:12:28 -05002216 try:
2217 if goal.failed:
2218 raise SanityRuntimeError("Couldn't build some defconfigs")
2219 except Exception as e:
2220 error(str(e))
2221 sys.exit(2)
2222
Anas Nashif2cf0df02015-10-10 09:29:43 -04002223
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002224 for k, out_config in defconfig_list.items():
Andrew Boie41878222016-11-03 11:58:53 -07002225 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04002226 defconfig = {}
2227 with open(out_config, "r") as fp:
2228 for line in fp.readlines():
2229 m = TestSuite.config_re.match(line)
2230 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07002231 if line.strip() and not line.startswith("#"):
2232 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002233 continue
2234 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07002235 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04002236
Anas Nashif45a97862019-01-09 08:46:42 -05002237 for k, cache_file in cmake_list.items():
2238 if not os.path.exists(out_config):
2239 continue
2240
2241 test, plat, name = k
2242 cmake_conf = {}
2243 try:
2244 cache = CMakeCache.from_file(cache_file)
2245 except FileNotFoundError:
2246 cache = {}
2247
2248 for k in iter(cache):
2249 cmake_conf[k.name] = k.value
2250
2251 test.cmake_cache[plat] = cmake_conf
2252
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002253 for k, out_config in dt_list.items():
2254 if not os.path.exists(out_config):
2255 continue
2256
2257 test, plat, name = k
2258 dt_conf = {}
2259 with open(out_config, "r") as fp:
2260 for line in fp.readlines():
2261 m = TestSuite.dt_re.match(line)
2262 if not m:
2263 if line.strip() and not line.startswith("#"):
2264 sys.stderr.write("Unrecognized line %s\n" % line)
2265 continue
2266 dt_conf[m.group(1)] = m.group(2).strip()
2267 test.dt_config[plat] = dt_conf
2268
Andrew Boie08ce5a52016-02-22 13:28:10 -08002269 for tc_name, tc in self.testcases.items():
2270 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002271 instance_list = []
2272 for plat in arch.platforms:
2273 instance = TestInstance(tc, plat, self.outdir)
2274
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002275 if (arch_name == "unit") != (tc.type == "unit"):
2276 # Discard silently
2277 continue
2278
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002279 if tc.skip:
2280 discards[instance] = "Skip filter"
2281 continue
2282
Anas Nashifbfab06b2017-06-22 09:22:24 -04002283 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002284 platform_filter = []
2285
Andrew Boie6acbe632015-07-17 12:03:52 -07002286 if tag_filter and not tc.tags.intersection(tag_filter):
2287 discards[instance] = "Command line testcase tag filter"
2288 continue
2289
Anas Nashifdfa86e22016-10-24 17:08:56 -04002290 if exclude_tag and tc.tags.intersection(exclude_tag):
2291 discards[instance] = "Command line testcase exclude filter"
2292 continue
2293
Andrew Boie6acbe632015-07-17 12:03:52 -07002294 if testcase_filter and tc_name not in testcase_filter:
2295 discards[instance] = "Testcase name filter"
2296 continue
2297
Andrew Boie6acbe632015-07-17 12:03:52 -07002298 if arch_filter and arch_name not in arch_filter:
2299 discards[instance] = "Command line testcase arch filter"
2300 continue
2301
2302 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2303 discards[instance] = "Not in test case arch whitelist"
2304 continue
2305
Anas Nashif30d13872015-10-05 10:02:45 -04002306 if tc.arch_exclude and arch.name in tc.arch_exclude:
2307 discards[instance] = "In test case arch exclude"
2308 continue
2309
2310 if tc.platform_exclude and plat.name in tc.platform_exclude:
2311 discards[instance] = "In test case platform exclude"
2312 continue
2313
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002314 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2315 discards[instance] = "In test case toolchain exclude"
2316 continue
2317
Andrew Boie6acbe632015-07-17 12:03:52 -07002318 if platform_filter and plat.name not in platform_filter:
2319 discards[instance] = "Command line platform filter"
2320 continue
2321
2322 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2323 discards[instance] = "Not in testcase platform whitelist"
2324 continue
2325
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002326 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2327 discards[instance] = "Not in testcase toolchain whitelist"
2328 continue
2329
Anas Nashif924a4e72018-10-18 12:25:55 -04002330 if not plat.env_satisfied:
2331 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2332 continue
2333
2334 if not options.force_toolchain \
2335 and toolchain and (toolchain not in plat.supported_toolchains) \
2336 and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05002337 discards[instance] = "Not supported by the toolchain"
2338 continue
2339
Anas Nashif62224182017-08-09 23:55:53 -04002340 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002341 discards[instance] = "Not enough RAM"
2342 continue
2343
Kumar Gala5141d522017-07-07 08:05:48 -05002344 if tc.depends_on:
Anas Nashif07d54c02018-07-21 19:29:08 -05002345 dep_intersection = tc.depends_on.intersection(set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002346 if dep_intersection != set(tc.depends_on):
2347 discards[instance] = "No hardware support"
2348 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002349
Anas Nashif62224182017-08-09 23:55:53 -04002350 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002351 discards[instance] = "Not enough FLASH"
2352 continue
2353
2354 if set(plat.ignore_tags) & tc.tags:
2355 discards[instance] = "Excluded tags per platform"
2356 continue
2357
Anas Nashif674bb282018-01-09 09:12:15 -05002358 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05002359 "ARCH": arch.name,
2360 "PLATFORM": plat.name
2361 }
Javier B Perez79414542016-08-08 12:24:59 -05002362 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002363 for p, tdefconfig in tc.defconfig.items():
2364 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002365 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002366 break
2367
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002368 for p, tdefconfig in tc.dt_config.items():
2369 if p == plat:
2370 defconfig.update(tdefconfig)
2371 break
2372
Anas Nashif45a97862019-01-09 08:46:42 -05002373 for p, tdefconfig in tc.cmake_cache.items():
2374 if p == plat:
2375 defconfig.update(tdefconfig)
2376 break
2377
Andrew Boie3ea78922016-03-24 14:46:00 -07002378 if tc.tc_filter:
2379 try:
2380 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002381 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002382 sys.stderr.write(
2383 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002384 raise se
2385 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002386 discards[instance] = (
2387 "defconfig doesn't satisfy expression '%s'" %
2388 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002389 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002390
Andrew Boie6acbe632015-07-17 12:03:52 -07002391 instance_list.append(instance)
2392
2393 if not instance_list:
2394 # Every platform in this arch was rejected already
2395 continue
2396
Anas Nashifa792a3d2017-04-04 18:47:49 -04002397 if default_platforms and not tc.build_on_all:
2398 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002399 instances = list(
2400 filter(
2401 lambda tc: tc.platform.default,
2402 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002403 self.add_instances(instances)
2404 else:
Anas Nashifab747062017-12-05 17:59:01 -05002405 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002406
Anas Nashif3ba1d432017-12-05 15:28:44 -05002407 for instance in list(
2408 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002409 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002410 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002411 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002412
2413 for name, case in self.instances.items():
Anas Nashifdbd76492018-11-23 20:24:19 -05002414 case.create_overlay(case.platform.name)
Anas Nashifab351f42018-04-08 08:57:48 -05002415
Andrew Boie6acbe632015-07-17 12:03:52 -07002416 self.discards = discards
Anas Nashif49b22d42019-06-14 13:45:34 -04002417
Andrew Boie6acbe632015-07-17 12:03:52 -07002418 return discards
2419
Andrew Boie821d8322016-03-22 10:08:35 -07002420 def add_instances(self, ti_list):
2421 for ti in ti_list:
2422 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002423
Anas Nashif37f9dc52018-02-23 08:53:46 -06002424 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002425
2426 def calc_one_elf_size(name, goal):
2427 if not goal.failed:
Alberto Escolar Piedrasb1045fe2018-07-14 13:11:02 +02002428 if self.instances[name].platform.type != "native":
2429 i = self.instances[name]
2430 sc = i.calculate_sizes()
2431 goal.metrics["ram_size"] = sc.get_ram_size()
2432 goal.metrics["rom_size"] = sc.get_rom_size()
2433 goal.metrics["unrecognized"] = sc.unrecognized_sections()
2434 else:
2435 goal.metrics["ram_size"] = 0
2436 goal.metrics["rom_size"] = 0
2437 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002438
Anas Nashif37f9dc52018-02-23 08:53:46 -06002439 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002440 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002441 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002442 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002443
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002444 if not options.disable_size_report and not options.cmake_only:
Andy Ross9c9162d2019-01-03 10:50:53 -08002445 # Parallelize size calculation
2446 executor = concurrent.futures.ThreadPoolExecutor(JOBS)
2447 futures = [executor.submit(calc_one_elf_size, name, goal)
2448 for name, goal in self.goals.items()]
2449 concurrent.futures.wait(futures)
2450 else:
2451 for goal in self.goals.values():
2452 goal.metrics["ram_size"] = 0
2453 goal.metrics["rom_size"] = 0
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002454 goal.metrics["handler_time"] = 0
Andy Ross9c9162d2019-01-03 10:50:53 -08002455 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002456
Andrew Boie6acbe632015-07-17 12:03:52 -07002457 return self.goals
2458
Marc Herbert682961a2019-03-20 16:48:49 -07002459 def save_tests(self, filename):
Anas Nashifbd166f42017-09-02 12:32:08 -04002460 with open(filename, "at") as csvfile:
2461 fieldnames = ['path', 'test', 'platform', 'arch']
2462 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2463 for instance in self.instances.values():
2464 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002465 "path": os.path.dirname(instance.test.name),
2466 "test": os.path.basename(instance.test.name),
2467 "platform": instance.platform.name,
2468 "arch": instance.platform.arch
2469 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002470 cw.writerow(rowdict)
2471
Andrew Boie6acbe632015-07-17 12:03:52 -07002472 def discard_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002473
2474 try:
2475 if self.discards is None:
2476 raise SanityRuntimeError("apply_filters() hasn't been run!")
2477 except Exception as e:
2478 error(str(e))
2479 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002480
Anas Nashifbd166f42017-09-02 12:32:08 -04002481 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002482 fieldnames = ["test", "arch", "platform", "reason"]
2483 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2484 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002485 for instance, reason in sorted(self.discards.items()):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002486 rowdict = {"test": instance.test.name,
2487 "arch": instance.platform.arch,
2488 "platform": instance.platform.name,
2489 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002490 cw.writerow(rowdict)
2491
2492 def compare_metrics(self, filename):
2493 # name, datatype, lower results better
2494 interesting_metrics = [("ram_size", int, True),
2495 ("rom_size", int, True)]
2496
Anas Nashifb4bdd662018-08-15 17:12:28 -05002497 try:
2498 if self.goals is None:
2499 raise SanityRuntimeError("execute() hasn't been run!")
2500 except Exception as e:
2501 print(str(e))
2502 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002503
2504 if not os.path.exists(filename):
2505 info("Cannot compare metrics, %s not found" % filename)
2506 return []
2507
2508 results = []
2509 saved_metrics = {}
2510 with open(filename) as fp:
2511 cr = csv.DictReader(fp)
2512 for row in cr:
2513 d = {}
2514 for m, _, _ in interesting_metrics:
2515 d[m] = row[m]
2516 saved_metrics[(row["test"], row["platform"])] = d
2517
Andrew Boie08ce5a52016-02-22 13:28:10 -08002518 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002519 i = self.instances[name]
2520 mkey = (i.test.name, i.platform.name)
2521 if mkey not in saved_metrics:
2522 continue
2523 sm = saved_metrics[mkey]
2524 for metric, mtype, lower_better in interesting_metrics:
2525 if metric not in goal.metrics:
2526 continue
2527 if sm[metric] == "":
2528 continue
2529 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002530 if delta == 0:
2531 continue
2532 results.append((i, metric, goal.metrics[metric], delta,
2533 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002534 return results
2535
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002536 def testcase_target_report(self, report_file):
2537
2538 run = "Sanitycheck"
2539 eleTestsuite = None
2540 append = options.only_failed
2541
2542 errors = 0
2543 passes = 0
2544 fails = 0
2545 duration = 0
2546 skips = 0
2547
2548 for identifier, ti in self.instances.items():
2549 for k in ti.results.keys():
2550 if ti.results[k] == 'PASS':
2551 passes += 1
2552 elif ti.results[k] == 'BLOCK':
2553 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002554 elif ti.results[k] == 'SKIP':
2555 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002556 else:
2557 fails += 1
2558
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002559 eleTestsuites = ET.Element('testsuites')
2560 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2561 name=run, time="%d" % duration,
2562 tests="%d" % (errors + passes + fails),
2563 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002564 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002565
2566 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002567
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002568 # print out test results
2569 for identifier, ti in self.instances.items():
2570 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002571
2572 eleTestcase = ET.SubElement(
2573 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002574 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002575 if ti.results[k] in ['FAIL', 'BLOCK']:
2576 el = None
2577
2578 if ti.results[k] == 'FAIL':
2579 el = ET.SubElement(
2580 eleTestcase,
2581 'failure',
2582 type="failure",
2583 message="failed")
2584 elif ti.results[k] == 'BLOCK':
2585 el = ET.SubElement(
2586 eleTestcase,
2587 'error',
2588 type="failure",
2589 message="failed")
2590 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
Anas Nashif366ed112019-03-31 20:36:30 -04002591 log_file = os.path.join(p, "handler.log")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002592
Anas Nashif366ed112019-03-31 20:36:30 -04002593 if os.path.exists(log_file):
2594 with open(log_file, "rb") as f:
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002595 log = f.read().decode("utf-8")
Anas Nashif366ed112019-03-31 20:36:30 -04002596 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2597 el.text = filtered_string
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002598
Anas Nashif61e21632018-04-08 13:30:16 -05002599 elif ti.results[k] == 'SKIP':
2600 el = ET.SubElement(
2601 eleTestcase,
Anas Nashif2c7636b2018-09-02 13:11:19 -04002602 'skipped',
2603 type="skipped",
2604 message="Skipped")
Anas Nashif61e21632018-04-08 13:30:16 -05002605
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002606 result = ET.tostring(eleTestsuites)
2607 f = open(report_file, 'wb')
2608 f.write(result)
2609 f.close()
2610
2611
Anas Nashif4f028882017-12-30 11:48:43 -05002612 def testcase_xunit_report(self, filename, duration):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002613 try:
2614 if self.goals is None:
2615 raise SanityRuntimeError("execute() hasn't been run!")
2616 except Exception as e:
2617 print(str(e))
2618 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002619
2620 fails = 0
2621 passes = 0
2622 errors = 0
2623
2624 for name, goal in self.goals.items():
2625 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002626 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002627 errors += 1
2628 else:
2629 fails += 1
2630 else:
2631 passes += 1
2632
2633 run = "Sanitycheck"
2634 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002635 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002636
Anas Nashif0605fa32017-05-07 08:51:02 -04002637 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002638 tree = ET.parse(filename)
2639 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002640 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002641 else:
2642 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002643 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2644 name=run, time="%d" % duration,
2645 tests="%d" % (errors + passes + fails),
2646 failures="%d" % fails,
2647 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002648
Anas Nashifc8390f12017-11-25 17:14:12 -05002649 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002650 for name, goal in self.goals.items():
2651
2652 i = self.instances[name]
2653 if append:
2654 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002655 if tc.get('classname') == "%s:%s" % (
2656 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002657 eleTestsuite.remove(tc)
2658
Anas Nashif4d25b502017-11-25 17:37:17 -05002659 if not goal.failed and goal.handler:
2660 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002661
Anas Nashif3ba1d432017-12-05 15:28:44 -05002662 eleTestcase = ET.SubElement(
2663 eleTestsuite, 'testcase', classname="%s:%s" %
2664 (i.platform.name, i.test.name), name="%s" %
2665 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002666 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002667 failure = ET.SubElement(
2668 eleTestcase,
2669 'failure',
2670 type="failure",
2671 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002672 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002673 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002674 hl = os.path.join(p, "handler.log")
2675 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002676 if goal.reason != 'build_error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002677 if os.path.exists(hl):
2678 log_file = hl
2679 else:
2680 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002681
Anas Nashifc96c90a2019-02-05 07:38:32 -05002682 if os.path.exists(log_file):
2683 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002684 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002685 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2686 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002687 f.close()
Anas Nashifb3311ed2017-04-13 14:44:48 -04002688
2689 result = ET.tostring(eleTestsuites)
2690 f = open(filename, 'wb')
2691 f.write(result)
2692 f.close()
2693
Andrew Boie6acbe632015-07-17 12:03:52 -07002694 def testcase_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002695 try:
2696 if self.goals is None:
2697 raise SanityRuntimeError("execute() hasn't been run!")
2698 except Exception as e:
2699 print(str(e))
2700 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002701
Andrew Boie08ce5a52016-02-22 13:28:10 -08002702 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002703 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002704 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002705 "rom_size"]
2706 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2707 cw.writeheader()
Marc Herbert0f7255c2019-04-05 14:14:21 -07002708 for name, goal in sorted(self.goals.items()):
Andrew Boie6acbe632015-07-17 12:03:52 -07002709 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002710 rowdict = {"test": i.test.name,
2711 "arch": i.platform.arch,
2712 "platform": i.platform.name,
2713 "extra_args": " ".join(i.test.extra_args),
2714 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002715 if goal.failed:
2716 rowdict["passed"] = False
2717 rowdict["status"] = goal.reason
2718 else:
2719 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002720 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002721 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002722 rowdict["ram_size"] = goal.metrics["ram_size"]
2723 rowdict["rom_size"] = goal.metrics["rom_size"]
2724 cw.writerow(rowdict)
2725
2726
2727def parse_arguments():
2728
Anas Nashif3ba1d432017-12-05 15:28:44 -05002729 parser = argparse.ArgumentParser(
2730 description=__doc__,
2731 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002732 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002733
Marc Herbert932a33a2019-03-12 11:37:53 -07002734 case_select = parser.add_argument_group("Test case selection",
2735 """
2736Artificially long but functional example:
2737 $ ./scripts/sanitycheck -v \\
Marc Herberte5cedca2019-04-08 14:02:34 -07002738 --testcase-root tests/ztest/base \\
2739 --testcase-root tests/kernel \\
Marc Herbert932a33a2019-03-12 11:37:53 -07002740 --test tests/ztest/base/testing.ztest.verbose_0 \\
2741 --test tests/kernel/fifo/fifo_api/kernel.fifo.poll
2742
2743 "kernel.fifo.poll" is one of the test section names in
2744 __/fifo_api/testcase.yaml
2745 """)
Marc Herbertedf17592019-03-08 12:39:11 -08002746
Anas Nashif07d54c02018-07-21 19:29:08 -05002747 parser.add_argument("--force-toolchain", action="store_true",
2748 help="Do not filter based on toolchain, use the set "
2749 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002750 parser.add_argument(
2751 "-p", "--platform", action="append",
2752 help="Platform filter for testing. This option may be used multiple "
2753 "times. Testcases will only be built/run on the platforms "
2754 "specified. If this option is not used, then platforms marked "
2755 "as default in the platform metadata file will be chosen "
2756 "to build and test. ")
2757 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002758 "-a", "--arch", action="append",
2759 help="Arch filter for testing. Takes precedence over --platform. "
2760 "If unspecified, test all arches. Multiple invocations "
2761 "are treated as a logical 'or' relationship")
2762 parser.add_argument(
2763 "-t", "--tag", action="append",
2764 help="Specify tags to restrict which tests to run by tag value. "
2765 "Default is to not do any tag filtering. Multiple invocations "
2766 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002767 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002768 help="Specify tags of tests that should not run. "
2769 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08002770 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002771 "-f",
2772 "--only-failed",
2773 action="store_true",
2774 help="Run only those tests that failed the previous sanity check "
2775 "invocation.")
2776 parser.add_argument(
2777 "-c", "--config", action="append",
2778 help="Specify platform configuration values filtering. This can be "
2779 "specified two ways: <config>=<value> or just <config>. The "
2780 "defconfig for all platforms will be "
2781 "checked. For the <config>=<value> case, only match defconfig "
2782 "that have that value defined. For the <config> case, match "
2783 "defconfig that have that value assigned to any value. "
2784 "Prepend a '!' to invert the match.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002785
Marc Herbert0c465bb2019-03-11 17:28:36 -07002786 test_xor_subtest = case_select.add_mutually_exclusive_group()
2787
2788 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002789 "-s", "--test", action="append",
2790 help="Run only the specified test cases. These are named by "
Marc Herberte5cedca2019-04-08 14:02:34 -07002791 "<path/relative/to/Zephyr/base/section.name.in.testcase.yaml>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002792
Marc Herbert0c465bb2019-03-11 17:28:36 -07002793 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002794 "--sub-test", action="append",
Marc Herbert932a33a2019-03-12 11:37:53 -07002795 help="""Recursively find sub-test functions and run the entire
2796 test section where they were found, including all sibling test
2797 functions. Sub-tests are named by:
2798 section.name.in.testcase.yaml.function_name_without_test_prefix
2799 Example: kernel.fifo.poll.fifo_loop
2800 """)
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002801
Anas Nashif3ba1d432017-12-05 15:28:44 -05002802 parser.add_argument(
2803 "-l", "--all", action="store_true",
2804 help="Build/test on all platforms. Any --platform arguments "
2805 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002806
Anas Nashif3ba1d432017-12-05 15:28:44 -05002807 parser.add_argument(
2808 "-o", "--testcase-report",
Marc Herberte5cedca2019-04-08 14:02:34 -07002809 help="""Output a CSV spreadsheet containing results of the test run.
2810 The handler_time column is left blank for tests that were only
2811 compiled and not run.""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002812 parser.add_argument(
2813 "-d", "--discard-report",
2814 help="Output a CSV spreadsheet showing tests that were skipped "
2815 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002816 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002817 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002818
Anas Nashif3ba1d432017-12-05 15:28:44 -05002819 parser.add_argument(
2820 "-B", "--subset",
2821 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2822 "3/5 means run the 3rd fifth of the total. "
2823 "This option is useful when running a large number of tests on "
2824 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002825
2826 parser.add_argument(
2827 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002828 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002829
Anas Nashif3ba1d432017-12-05 15:28:44 -05002830 parser.add_argument(
2831 "-y", "--dry-run", action="store_true",
2832 help="Create the filtered list of test cases, but don't actually "
2833 "run them. Useful if you're just interested in "
2834 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002835
Anas Nashif75547e22018-02-24 08:32:14 -06002836 parser.add_argument("--list-tags", action="store_true",
2837 help="list all tags in selected tests")
2838
Anas Nashif49b22d42019-06-14 13:45:34 -04002839 parser.add_argument("--report-excluded",
2840 action="store_true",
2841 help="""List all tests that are never run based on current scope and
2842 coverage. If you are looking for accurate results, run this with
2843 --all, but this will take a while...""")
2844
Marc Herbertedf17592019-03-08 12:39:11 -08002845 case_select.add_argument("--list-tests", action="store_true",
Marc Herbert932a33a2019-03-12 11:37:53 -07002846 help="""List of all sub-test functions recursively found in
2847 all --testcase-root arguments. Note different sub-tests can share
2848 the same section name and come from different directories.
2849 The output is flattened and reports --sub-test names only,
2850 not their directories. For instance net.socket.getaddrinfo_ok
2851 and net.socket.fd_set belong to different directories.
2852 """)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002853
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002854 parser.add_argument("--export-tests", action="store",
2855 metavar="FILENAME",
2856 help="Export tests case meta-data to a file in CSV format.")
2857
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002858 parser.add_argument("--detailed-report",
2859 action="store",
2860 metavar="FILENAME",
Marc Herberte5cedca2019-04-08 14:02:34 -07002861 help="""Generate a junit report with detailed testcase results.
2862 Unlike the CSV file produced by --testcase-report, this XML
2863 report includes only tests which have run and none which were
2864 merely built. If an image with multiple tests crashes early then
2865 later tests are not accounted for either.""")
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002866
Anas Nashif654ec5982019-04-11 08:38:21 -04002867 parser.add_argument("--timestamps",
2868 action="store_true",
2869 help="Print all messages with time stamps")
2870
Anas Nashif3ba1d432017-12-05 15:28:44 -05002871 parser.add_argument(
2872 "-r", "--release", action="store_true",
2873 help="Update the benchmark database with the results of this test "
2874 "run. Intended to be run by CI when tagging an official "
2875 "release. This database is used as a basis for comparison "
2876 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002877 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002878 help="Treat warning conditions as errors")
2879 parser.add_argument(
2880 "-v",
2881 "--verbose",
2882 action="count",
2883 default=0,
2884 help="Emit debugging information, call multiple times to increase "
2885 "verbosity")
2886 parser.add_argument(
2887 "-i", "--inline-logs", action="store_true",
2888 help="Upon test failure, print relevant log data to stdout "
2889 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002890 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002891 help="log also to file")
2892 parser.add_argument(
2893 "-m", "--last-metrics", action="store_true",
2894 help="Instead of comparing metrics from the last --release, "
2895 "compare with the results of the previous sanity check "
2896 "invocation")
2897 parser.add_argument(
2898 "-u",
2899 "--no-update",
2900 action="store_true",
2901 help="do not update the results of the last run of the sanity "
2902 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08002903 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002904 "-F",
2905 "--load-tests",
2906 metavar="FILENAME",
2907 action="store",
Marc Herbert932a33a2019-03-12 11:37:53 -07002908 help="Load list of tests and platforms to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002909
Marc Herbertedf17592019-03-08 12:39:11 -08002910 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002911 "-E",
2912 "--save-tests",
2913 metavar="FILENAME",
2914 action="store",
Marc Herbert682961a2019-03-20 16:48:49 -07002915 help="Append list of tests and platforms to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002916
Andy Doancbecadd2019-02-08 10:19:10 -06002917 test_or_build = parser.add_mutually_exclusive_group()
2918 test_or_build.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002919 "-b", "--build-only", action="store_true",
2920 help="Only build the code, do not execute any of it in QEMU")
Andy Doancbecadd2019-02-08 10:19:10 -06002921 test_or_build.add_argument(
2922 "--test-only", action="store_true",
2923 help="""Only run device tests with current artifacts, do not build
2924 the code""")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002925 parser.add_argument(
Kumar Gala84cf9dc2019-06-15 09:36:06 -05002926 "--cmake-only", action="store_true",
2927 help="Test on device directly. Specify the serial device to "
2928 "use with the --device-serial option.")
2929
2930 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002931 "-j", "--jobs", type=int,
Marc Herbert9e573382019-07-03 12:49:42 -07002932 help="Number of jobs for building, defaults to number of CPU threads, "
2933 "overcommited by factor 2 when --build-only")
Anas Nashif73440ea2018-02-19 10:57:03 -06002934
2935 parser.add_argument(
2936 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002937 help="Test on device directly. Specify the serial device to "
2938 "use with the --device-serial option.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002939
2940 parser.add_argument(
2941 "-X", "--fixture", action="append", default=[],
2942 help="Specify a fixture that a board might support")
Anas Nashif73440ea2018-02-19 10:57:03 -06002943 parser.add_argument(
2944 "--device-serial",
Marc Herbert9e573382019-07-03 12:49:42 -07002945 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002946 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002947 "--show-footprint", action="store_true",
2948 help="Show footprint statistics and deltas since last release."
2949 )
2950 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002951 "-H", "--footprint-threshold", type=float, default=5,
2952 help="When checking test case footprint sizes, warn the user if "
2953 "the new app size is greater then the specified percentage "
2954 "from the last release. Default is 5. 0 to warn on any "
2955 "increase on app size")
2956 parser.add_argument(
2957 "-D", "--all-deltas", action="store_true",
2958 help="Show all footprint deltas, positive or negative. Implies "
2959 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002960 parser.add_argument(
2961 "-O", "--outdir",
Anas Nashiff114a132018-11-20 11:51:34 -05002962 default="%s/sanity-out" % os.getcwd(),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002963 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05002964 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002965 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002966 parser.add_argument(
2967 "-n", "--no-clean", action="store_true",
2968 help="Do not delete the outdir before building. Will result in "
2969 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08002970 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002971 "-T", "--testcase-root", action="append", default=[],
2972 help="Base directory to recursively search for test cases. All "
2973 "testcase.yaml files under here will be processed. May be "
Marc Herbert932a33a2019-03-12 11:37:53 -07002974 "called multiple times. Defaults to the 'samples/' and "
2975 "'tests/' directories at the base of the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002976
Anas Nashif3ba1d432017-12-05 15:28:44 -05002977 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2978 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002979
Anas Nashif3ba1d432017-12-05 15:28:44 -05002980 parser.add_argument(
2981 "-A", "--board-root", action="append", default=board_root_list,
2982 help="Directory to search for board configuration files. All .yaml "
2983 "files in the directory will be processed.")
2984 parser.add_argument(
2985 "-z", "--size", action="append",
2986 help="Don't run sanity checks. Instead, produce a report to "
2987 "stdout detailing RAM/ROM sizes on the specified filenames. "
2988 "All other command line arguments ignored.")
2989 parser.add_argument(
2990 "-S", "--enable-slow", action="store_true",
2991 help="Execute time-consuming test cases that have been marked "
2992 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01002993 parser.add_argument(
2994 "--disable-unrecognized-section-test", action="store_true",
2995 default=False,
2996 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07002997 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002998 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002999 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06003000 parser.add_argument("--disable-asserts", action="store_false",
3001 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07003002 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05003003 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05003004 help="Error on deprecation warnings.")
Andy Ross9c9162d2019-01-03 10:50:53 -08003005 parser.add_argument("--disable-size-report", action="store_true",
3006 help="Skip expensive computation of ram/rom segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01003007
3008 parser.add_argument(
3009 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01003010 help="""Extra CMake cache entries to define when building test cases.
3011 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01003012 prefixed with -D before being passed to CMake.
3013
3014 E.g
3015 "sanitycheck -x=USE_CCACHE=0"
3016 will translate to
3017 "cmake -DUSE_CCACHE=0"
3018
3019 which will ultimately disable ccache.
3020 """
3021 )
Michael Scott421ce462019-06-18 09:37:46 -07003022
Andy Doan79c48842019-02-08 10:09:04 -06003023 parser.add_argument(
3024 "--west-flash", nargs='?', const=[],
3025 help="""Uses west instead of ninja or make to flash when running with
Michael Scott4ca54392019-07-09 14:21:30 -07003026 --device-testing.
Andy Doan79c48842019-02-08 10:09:04 -06003027
Michael Scott4ca54392019-07-09 14:21:30 -07003028 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3029 --west-flash="--board-id=foobar"
3030 will translate to "west flash -- --board-id=foobar"
3031
3032 NOTE: device-testing must be enabled to use this option.
Andy Doan79c48842019-02-08 10:09:04 -06003033 """
3034 )
Michael Scott421ce462019-06-18 09:37:46 -07003035 parser.add_argument(
3036 "--west-runner",
3037 help="""Uses the specified west runner instead of default when running
3038 with --west-flash.
3039
3040 E.g "sanitycheck --device-testing --device-serial /dev/ttyACM0
3041 --west-flash --west-runner=pyocd"
3042 will translate to "west flash --runner pyocd"
3043
3044 NOTE: west-flash must be enabled to use this option.
3045 """
3046 )
Andrew Boie8047a6f2019-07-02 15:43:29 -07003047
Andrew Boie49cf4862019-07-08 12:02:13 -07003048 parser.add_argument("--gcov-tool", default=None,
3049 help="Path to the gcov tool to use for code coverage "
3050 "reports")
Sebastian Bøec2182612017-11-09 12:25:02 +01003051
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003052 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05003053 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003054
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003055 parser.add_argument("-C", "--coverage", action="store_true",
Andrew Boie8047a6f2019-07-02 15:43:29 -07003056 help="Generate coverage reports. Implies "
3057 "--enable_coverage and --enable-slow")
Andrew Boie6acbe632015-07-17 12:03:52 -07003058
Andrew Boie8047a6f2019-07-02 15:43:29 -07003059 parser.add_argument("--coverage-platform", action="append", default=[],
Anas Nashifdbd76492018-11-23 20:24:19 -05003060 help="Plarforms to run coverage reports on. "
Andrew Boie8047a6f2019-07-02 15:43:29 -07003061 "This option may be used multiple times. "
3062 "Default to what was selected with --platform.")
Anas Nashifdbd76492018-11-23 20:24:19 -05003063
Andrew Boie6acbe632015-07-17 12:03:52 -07003064 return parser.parse_args()
3065
Anas Nashif3ba1d432017-12-05 15:28:44 -05003066
Andrew Boie6acbe632015-07-17 12:03:52 -07003067def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01003068 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003069 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003070 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08003071
3072 try:
3073 with open(filename) as fp:
3074 data = fp.read()
3075 except Exception as e:
3076 data = "Unable to read log data (%s)\n" % (str(e))
3077
3078 sys.stdout.write(data)
3079 if log_file:
3080 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003081 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07003082 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08003083 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07003084
Anas Nashif3ba1d432017-12-05 15:28:44 -05003085
Andrew Boie6acbe632015-07-17 12:03:52 -07003086def terse_test_cb(instances, goals, goal):
3087 total_tests = len(goals)
3088 total_done = 0
3089 total_failed = 0
3090
Andrew Boie08ce5a52016-02-22 13:28:10 -08003091 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003092 if g.finished:
3093 total_done += 1
3094 if g.failed:
3095 total_failed += 1
3096
3097 if goal.failed:
3098 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05003099 info(
3100 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
3101 i.platform.name,
3102 i.test.name,
3103 COLOR_RED,
3104 COLOR_NORMAL,
Anas Nashif654ec5982019-04-11 08:38:21 -04003105 goal.reason), False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003106 log_info(goal.get_error_log())
Anas Nashif654ec5982019-04-11 08:38:21 -04003107 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003108
Anas Nashif3ba1d432017-12-05 15:28:44 -05003109 sys.stdout.write(
3110 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
3111 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
3112 int((float(total_done) / total_tests) * 100),
3113 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
3114 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07003115 sys.stdout.flush()
3116
Anas Nashif3ba1d432017-12-05 15:28:44 -05003117
Andrew Boie6acbe632015-07-17 12:03:52 -07003118def chatty_test_cb(instances, goals, goal):
3119 i = instances[goal.name]
3120
3121 if VERBOSE < 2 and not goal.finished:
3122 return
3123
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003124 total_tests = len(goals)
3125 total_tests_width = len(str(total_tests))
3126 total_done = 0
3127
3128 for k, g in goals.items():
3129 if g.finished:
3130 total_done += 1
3131
Andrew Boie6acbe632015-07-17 12:03:52 -07003132 if goal.failed:
3133 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
3134 elif goal.finished:
3135 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
3136 else:
3137 status = goal.make_state
3138
Anas Nashifd18ec532019-04-11 23:20:39 -04003139 if goal.handler:
3140 handler_type = goal.handler.type_str
Marc Herbert35dc9632019-06-20 10:00:20 -07003141 htime = goal.metrics.get("handler_time", None)
3142 if htime:
3143 handler_type += " {:.3f}s".format(htime)
Anas Nashifd18ec532019-04-11 23:20:39 -04003144 else:
3145 handler_type = "build"
3146
3147 info("{:>{}}/{} {:<25} {:<50} {} ({})".format(
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03003148 total_done, total_tests_width, total_tests, i.platform.name,
Anas Nashifd18ec532019-04-11 23:20:39 -04003149 i.test.name, status, handler_type))
Andrew Boie6acbe632015-07-17 12:03:52 -07003150 if goal.failed:
3151 log_info(goal.get_error_log())
3152
Andrew Boiebbd670c2015-08-17 13:16:11 -07003153
3154def size_report(sc):
3155 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07003156 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07003157 for i in range(len(sc.sections)):
3158 v = sc.sections[i]
3159
Andrew Boie73b4ee62015-10-07 11:33:22 -07003160 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
3161 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
3162 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003163
Andrew Boie73b4ee62015-10-07 11:33:22 -07003164 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003165 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003166 info("")
3167
Anas Nashiff29087e2019-01-25 09:37:38 -05003168def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003169 if VERBOSE:
3170 print("Working on %s" %intput_file)
3171 extracted_coverage_info = {}
3172 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003173 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003174 with open(intput_file, 'r') as fp:
3175 for line in fp.readlines():
3176 if re.search("GCOV_COVERAGE_DUMP_START", line):
3177 capture_data = True
3178 continue
3179 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003180 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003181 break
3182 # Loop until the coverage data is found.
3183 if not capture_data:
3184 continue
3185 if line.startswith("*"):
3186 sp = line.split("<")
3187 if len(sp) > 1:
3188 # Remove the leading delimiter "*"
3189 file_name = sp[0][1:]
3190 # Remove the trailing new line char
3191 hex_dump = sp[1][:-1]
3192 else:
3193 continue
3194 else:
3195 continue
3196 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003197 if not capture_data:
3198 capture_complete = True
3199 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003200
3201def create_gcda_files(extracted_coverage_info):
3202 if VERBOSE:
3203 print("Generating gcda files")
3204 for filename, hexdump_val in extracted_coverage_info.items():
3205 # if kobject_hash is given for coverage gcovr fails
3206 # hence skipping it problem only in gcovr v4.1
3207 if "kobject_hash" in filename:
3208 filename = (filename[:-4]) +"gcno"
3209 try:
3210 os.remove(filename)
3211 except:
3212 pass
3213 continue
3214
3215 with open(filename, 'wb') as fp:
3216 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003217
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003218def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003219
3220 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003221 gcov_data = retrieve_gcov_data(filename)
3222 capture_complete = gcov_data['complete']
3223 extracted_coverage_info = gcov_data['data']
3224 if capture_complete:
3225 create_gcda_files(extracted_coverage_info)
3226 verbose("Gcov data captured: {}".format(filename))
3227 else:
3228 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003229
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003230 gcov_tool = options.gcov_tool
3231
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003232 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3233 coveragefile = os.path.join(outdir, "coverage.info")
3234 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003235 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3236 "--capture", "--directory", outdir,
3237 "--rc", "lcov_branch_coverage=1",
3238 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003239 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003240 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003241 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003242 "--output-file", ztestfile,
3243 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3244
Anas Nashif3cbffef2018-11-07 23:50:54 -05003245 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003246 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003247 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3248 "--output-file", ztestfile,
3249 "--rc", "lcov_branch_coverage=1"],
3250 stdout=coveragelog)
3251 files = [coveragefile, ztestfile];
3252 else:
3253 files = [coveragefile];
3254
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003255 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003256 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003257 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3258 coveragefile, i, "--output-file",
3259 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003260 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003261
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003262 #The --ignore-errors source option is added to avoid it exiting due to
3263 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003264 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003265 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003266 "-output-directory",
3267 os.path.join(outdir, "coverage")] + files,
3268 stdout=coveragelog)
3269 if ret==0:
3270 info("HTML report generated: %s"%
3271 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05003272
Andrew Boiebbd670c2015-08-17 13:16:11 -07003273
Andrew Boie6acbe632015-07-17 12:03:52 -07003274def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003275 start_time = time.time()
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003276 global VERBOSE, INLINE_LOGS, JOBS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003277 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003278 global run_individual_tests
Andrew Boie1578ef72019-07-03 10:19:29 -07003279
3280 # XXX: Workaround for #17239
3281 resource.setrlimit(resource.RLIMIT_NOFILE, (4096, 4096))
Anas Nashife10b6512017-12-30 13:01:45 -05003282 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003283
Michael Scott421ce462019-06-18 09:37:46 -07003284 if options.west_runner and not options.west_flash:
3285 error("west-runner requires west-flash to be enabled")
3286 sys.exit(1)
3287
Michael Scott4ca54392019-07-09 14:21:30 -07003288 if options.west_flash and not options.device_testing:
3289 error("west-flash requires device-testing to be enabled")
3290 sys.exit(1)
3291
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003292 if options.coverage:
3293 options.enable_coverage = True
Andrew Boie8047a6f2019-07-02 15:43:29 -07003294 options.enable_slow = True
3295 if not options.coverage_platform:
3296 options.coverage_platform = options.platform
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003297
Anas Nashife10b6512017-12-30 13:01:45 -05003298 if options.size:
3299 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003300 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003301 sys.exit(0)
3302
Anas Nashif73440ea2018-02-19 10:57:03 -06003303
3304 if options.device_testing:
3305 if options.device_serial is None or len(options.platform) != 1:
3306 sys.exit(1)
3307
Anas Nashife10b6512017-12-30 13:01:45 -05003308 VERBOSE += options.verbose
3309 INLINE_LOGS = options.inline_logs
3310 if options.log_file:
3311 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003312
Anas Nashife10b6512017-12-30 13:01:45 -05003313 if options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003314 JOBS = options.jobs
Andrew Boie9f4f57e2019-07-02 17:00:08 -07003315 elif options.build_only:
3316 JOBS = multiprocessing.cpu_count() * 2
3317 else:
3318 JOBS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -07003319
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003320 # Decrease JOBS for Ninja, if jobs weren't explicitly set
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003321 if options.ninja and not options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003322 JOBS = int(JOBS * 0.75)
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003323
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003324 info("JOBS: %d" % JOBS);
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003325
Anas Nashife10b6512017-12-30 13:01:45 -05003326 if options.subset:
3327 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003328 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003329 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003330 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003331 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003332 return
3333
Anas Nashife10b6512017-12-30 13:01:45 -05003334 if os.path.exists(options.outdir) and not options.no_clean:
3335 info("Cleaning output directory " + options.outdir)
3336 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003337
Anas Nashife10b6512017-12-30 13:01:45 -05003338 if not options.testcase_root:
3339 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003340 os.path.join(ZEPHYR_BASE, "samples")]
3341
Anas Nashif37f9dc52018-02-23 08:53:46 -06003342 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04003343
Kumar Galac84235e2018-04-10 13:32:51 -05003344 if ts.load_errors:
3345 sys.exit(1)
3346
Anas Nashif75547e22018-02-24 08:32:14 -06003347 if options.list_tags:
3348 tags = set()
3349 for n,tc in ts.testcases.items():
3350 tags = tags.union(tc.tags)
3351
3352 for t in tags:
3353 print("- {}".format(t))
3354
3355 return
3356
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003357
3358 def export_tests(filename, tests):
3359 with open(filename, "wt") as csvfile:
3360 fieldnames = ['section', 'subsection', 'title', 'reference']
3361 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3362 for test in tests:
3363 data = test.split(".")
3364 subsec = " ".join(data[1].split("_")).title()
3365 rowdict = {
3366 "section": data[0].capitalize(),
3367 "subsection": subsec,
3368 "title": test,
3369 "reference": test
3370 }
3371 cw.writerow(rowdict)
3372
3373 if options.export_tests:
3374 cnt = 0
3375 unq = []
3376 for n,tc in ts.testcases.items():
3377 for c in tc.cases:
3378 unq.append(c)
3379
3380 tests = sorted(set(unq))
3381 export_tests(options.export_tests, tests)
3382 return
3383
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003384 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003385
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003386 if options.test:
3387 run_individual_tests = options.test
3388
Anas Nashif49b22d42019-06-14 13:45:34 -04003389
3390 def get_unique_tests(ts):
Anas Nashifa3abe962018-05-05 19:10:22 -05003391 unq = []
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003392 run_individual_tests = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05003393 for n,tc in ts.testcases.items():
3394 for c in tc.cases:
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003395 if options.sub_test and c in options.sub_test:
3396 if tc.name not in run_individual_tests:
3397 run_individual_tests.append(tc.name)
Anas Nashifa3abe962018-05-05 19:10:22 -05003398 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003399
Anas Nashif49b22d42019-06-14 13:45:34 -04003400 return unq
3401
3402 if options.list_tests or options.sub_test:
3403 cnt = 0
3404 unq = get_unique_tests(ts)
3405
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003406 if options.sub_test:
3407 if run_individual_tests:
3408 info("Running the following tests:")
3409 for t in run_individual_tests:
3410 print(" - {}".format(t))
3411 else:
3412 info("Tests not found")
3413 return
3414
3415 elif options.list_tests:
3416 for u in sorted(set(unq)):
3417 cnt = cnt + 1
3418 print(" - {}".format(u))
3419 print("{} total.".format(cnt))
3420 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003421
Anas Nashifbd166f42017-09-02 12:32:08 -04003422 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05003423 if options.load_tests:
3424 ts.load_from_file(options.load_tests)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05003425 elif options.only_failed:
3426 ts.get_last_failed()
Anas Nashifbd166f42017-09-02 12:32:08 -04003427 else:
Anas Nashif4f028882017-12-30 11:48:43 -05003428 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003429
Anas Nashif49b22d42019-06-14 13:45:34 -04003430
Anas Nashife10b6512017-12-30 13:01:45 -05003431 if options.discard_report:
3432 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07003433
Anas Nashif30551f42018-01-12 21:56:59 -05003434 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003435 # if we are using command line platform filter, no need to list every
3436 # other platform as excluded, we know that already.
3437 # Show only the discards that apply to the selected platforms on the
3438 # command line
3439
Andrew Boie08ce5a52016-02-22 13:28:10 -08003440 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003441 if options.platform and i.platform.name not in options.platform:
3442 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003443 debug(
3444 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3445 i.platform.name,
3446 i.test.name,
3447 COLOR_YELLOW,
3448 COLOR_NORMAL,
3449 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003450
Anas Nashif1a5bba72018-01-05 08:07:45 -05003451
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003452 def native_and_unit_first(a, b):
3453 if a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003454 return -1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003455 if b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003456 return 1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003457 if a[0].startswith('native_posix'):
3458 return -1
3459 if b[0].startswith('native_posix'):
3460 return 1
3461 if a[0].split("/",1)[0].endswith("_bsim"):
3462 return -1
3463 if b[0].split("/",1)[0].endswith("_bsim"):
3464 return 1
3465
Anas Nashif1a5bba72018-01-05 08:07:45 -05003466 return (a > b) - (a < b)
3467
3468 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003469 key=cmp_to_key(native_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04003470
Anas Nashif49b22d42019-06-14 13:45:34 -04003471
3472
3473 if options.report_excluded:
3474 all_tests = set(get_unique_tests(ts))
3475 to_be_run = set()
3476 for i,p in ts.instances.items():
3477 to_be_run.update(p.test.cases)
3478
3479 if (all_tests - to_be_run):
3480 print("Tests that never build or run:")
3481 for not_run in (all_tests - to_be_run):
3482 print("- {}".format(not_run))
3483
3484 return
3485
3486
Anas Nashife10b6512017-12-30 13:01:45 -05003487 if options.save_tests:
Marc Herbert682961a2019-03-20 16:48:49 -07003488 ts.save_tests(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04003489 return
3490
Anas Nashife10b6512017-12-30 13:01:45 -05003491 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05003492
Anas Nashife10b6512017-12-30 13:01:45 -05003493 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04003494 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003495 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003496 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003497 if subset == sets:
3498 end = total
3499 else:
3500 end = start + per_set
3501
Anas Nashif3ba1d432017-12-05 15:28:44 -05003502 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04003503 ts.instances = OrderedDict(sliced_instances)
3504
Andrew Boie6acbe632015-07-17 12:03:52 -07003505 info("%d tests selected, %d tests discarded due to filters" %
3506 (len(ts.instances), len(discards)))
3507
Anas Nashife10b6512017-12-30 13:01:45 -05003508 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07003509 return
3510
3511 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003512 goals = ts.execute(
3513 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003514 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07003515 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003516 goals = ts.execute(
3517 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003518 ts.instances)
Anas Nashif654ec5982019-04-11 08:38:21 -04003519 info("", False)
Andrew Boie6acbe632015-07-17 12:03:52 -07003520
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003521 if options.detailed_report:
3522 ts.testcase_target_report(options.detailed_report)
3523
Daniel Leung7f850102016-04-08 11:07:32 -07003524 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05003525 if options.compare_report:
3526 report_to_use = options.compare_report
3527 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07003528 report_to_use = LAST_SANITY
3529 else:
3530 report_to_use = RELEASE_DATA
3531
3532 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07003533 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06003534 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07003535 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05003536 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07003537 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07003538 continue
3539
Andrew Boieea7928f2015-08-14 14:27:38 -07003540 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05003541 if not options.all_deltas and (percentage <
3542 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07003543 continue
3544
Daniel Leung00525c22016-04-11 10:27:56 -07003545 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07003546 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05003547 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07003548 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07003549 warnings += 1
3550
3551 if warnings:
3552 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05003553 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07003554
3555 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08003556 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003557 if goal.failed:
3558 failed += 1
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003559 elif goal.metrics.get("unrecognized") and not options.disable_unrecognized_section_test:
Andrew Boie73b4ee62015-10-07 11:33:22 -07003560 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
3561 (COLOR_RED, COLOR_NORMAL, goal.name,
3562 str(goal.metrics["unrecognized"])))
3563 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07003564
Anas Nashife10b6512017-12-30 13:01:45 -05003565 if options.coverage:
Andrew Boie49cf4862019-07-08 12:02:13 -07003566 if options.gcov_tool == None:
3567 using_posix = False
3568
3569 for plat in options.coverage_platform:
3570 ts_plat = ts.get_platform(plat)
3571 if ts_plat and ts_plat.type == "native":
3572 using_posix = True
3573
3574 if using_posix or "ZEPHYR_SDK_INSTALL_DIR" not in os.environ:
3575 options.gcov_tool = "gcov"
3576 else:
3577 options.gcov_tool = os.path.join(os.environ["ZEPHYR_SDK_INSTALL_DIR"],
3578 "i586-zephyr-elf/bin/i586-zephyr-elf-gcov")
3579
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003580 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003581 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003582
Anas Nashif0605fa32017-05-07 08:51:02 -04003583 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07003584 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003585 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
3586 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
3587 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07003588
Anas Nashife10b6512017-12-30 13:01:45 -05003589 if options.testcase_report:
3590 ts.testcase_report(options.testcase_report)
3591 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05003592 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07003593 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05003594 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07003595 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003596 if log_file:
3597 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05003598 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003599 sys.exit(1)
3600
Anas Nashif3ba1d432017-12-05 15:28:44 -05003601
Andrew Boie6acbe632015-07-17 12:03:52 -07003602if __name__ == "__main__":
3603 main()