blob: 53018ee3bbd054ee28f7c279cfca5f16229e835d [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 :
Andrew Boie6acbe632015-07-17 12:03:52 -07003"""Zephyr Sanity Tests
4
5This script scans for the set of unit test applications in the git
6repository and attempts to execute them. By default, it tries to
7build each test case on one platform per architecture, using a precedence
Paul Sokolovskyff70add2017-06-16 01:31:54 +03008list defined in an architecture configuration file, and if possible
Andrew Boie6acbe632015-07-17 12:03:52 -07009run the tests in the QEMU emulator.
10
Anas Nashifa792a3d2017-04-04 18:47:49 -040011Test cases are detected by the presence of a 'testcase.yaml' or a sample.yaml
12files in the application's project directory. This file may contain one or more
13blocks, each identifying a test scenario. The title of the block is a name for
14the test case, which only needs to be unique for the test cases specified in
15that testcase meta-data. The full canonical name for each test case is <path to
16test case>/<block>.
Andrew Boie6acbe632015-07-17 12:03:52 -070017
Anas Nashif3ba1d432017-12-05 15:28:44 -050018Each test block in the testcase meta data can define the following key/value
19pairs:
Andrew Boie6acbe632015-07-17 12:03:52 -070020
Anas Nashiffa695d22017-10-04 16:14:27 -040021 tags: <list of tags> (required)
Andrew Boie6acbe632015-07-17 12:03:52 -070022 A set of string tags for the testcase. Usually pertains to
23 functional domains but can be anything. Command line invocations
24 of this script can filter the set of tests to run based on tag.
25
Anas Nashifa792a3d2017-04-04 18:47:49 -040026 skip: <True|False> (default False)
Anas Nashif2bd99bc2015-10-12 13:10:57 -040027 skip testcase unconditionally. This can be used for broken tests.
28
Anas Nashifa792a3d2017-04-04 18:47:49 -040029 slow: <True|False> (default False)
Andy Rossdc4151f2019-01-03 14:17:43 -080030 Don't build or run this test case unless --enable-slow was passed
31 in on the command line. Intended for time-consuming test cases
32 that are only run under certain circumstances, like daily
33 builds.
Andrew Boie6bb087c2016-02-10 13:39:00 -080034
Anas Nashifa792a3d2017-04-04 18:47:49 -040035 extra_args: <list of extra arguments>
Sebastian Bøec2182612017-11-09 12:25:02 +010036 Extra cache entries to pass to CMake when building or running the
Andrew Boie6acbe632015-07-17 12:03:52 -070037 test case.
38
Anas Nashifebc329d2017-10-17 09:00:33 -040039 extra_configs: <list of extra configurations>
40 Extra configuration options to be merged with a master prj.conf
41 when building or running the test case.
42
Anas Nashifa792a3d2017-04-04 18:47:49 -040043 build_only: <True|False> (default False)
Andrew Boie6acbe632015-07-17 12:03:52 -070044 If true, don't try to run the test under QEMU even if the
45 selected platform supports it.
46
Anas Nashifa792a3d2017-04-04 18:47:49 -040047 build_on_all: <True|False> (default False)
48 If true, attempt to build test on all available platforms.
49
50 depends_on: <list of features>
51 A board or platform can announce what features it supports, this option
52 will enable the test only those platforms that provide this feature.
53
54 min_ram: <integer>
55 minimum amount of RAM needed for this test to build and run. This is
56 compared with information provided by the board metadata.
57
58 min_flash: <integer>
59 minimum amount of ROM needed for this test to build and run. This is
60 compared with information provided by the board metadata.
61
62 timeout: <number of seconds>
Andrew Boie6acbe632015-07-17 12:03:52 -070063 Length of time to run test in QEMU before automatically killing it.
64 Default to 60 seconds.
65
Anas Nashifa792a3d2017-04-04 18:47:49 -040066 arch_whitelist: <list of arches, such as x86, arm, arc>
Andrew Boie6acbe632015-07-17 12:03:52 -070067 Set of architectures that this test case should only be run for.
68
Anas Nashifa792a3d2017-04-04 18:47:49 -040069 arch_exclude: <list of arches, such as x86, arm, arc>
Anas Nashif30d13872015-10-05 10:02:45 -040070 Set of architectures that this test case should not run on.
71
Anas Nashifa792a3d2017-04-04 18:47:49 -040072 platform_whitelist: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040073 Set of platforms that this test case should only be run for.
74
Anas Nashifa792a3d2017-04-04 18:47:49 -040075 platform_exclude: <list of platforms>
Anas Nashif30d13872015-10-05 10:02:45 -040076 Set of platforms that this test case should not run on.
Andrew Boie6acbe632015-07-17 12:03:52 -070077
Anas Nashifa792a3d2017-04-04 18:47:49 -040078 extra_sections: <list of extra binary sections>
Andrew Boie52fef672016-11-29 12:21:59 -080079 When computing sizes, sanitycheck will report errors if it finds
80 extra, unexpected sections in the Zephyr binary unless they are named
81 here. They will not be included in the size calculation.
82
Anas Nashifa792a3d2017-04-04 18:47:49 -040083 filter: <expression>
Andrew Boie3ea78922016-03-24 14:46:00 -070084 Filter whether the testcase should be run by evaluating an expression
85 against an environment containing the following values:
86
87 { ARCH : <architecture>,
88 PLATFORM : <platform>,
Javier B Perez79414542016-08-08 12:24:59 -050089 <all CONFIG_* key/value pairs in the test's generated defconfig>,
Anas Nashif45a97862019-01-09 08:46:42 -050090 <all DT_* key/value pairs in the test's generated device tree file>,
91 <all CMake key/value pairs in the test's generated CMakeCache.txt file>,
Javier B Perez79414542016-08-08 12:24:59 -050092 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070093 }
94
95 The grammar for the expression language is as follows:
96
97 expression ::= expression "and" expression
98 | expression "or" expression
99 | "not" expression
100 | "(" expression ")"
101 | symbol "==" constant
102 | symbol "!=" constant
103 | symbol "<" number
104 | symbol ">" number
105 | symbol ">=" number
106 | symbol "<=" number
107 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700108 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -0700109 | symbol
110
111 list ::= "[" list_contents "]"
112
113 list_contents ::= constant
114 | list_contents "," constant
115
116 constant ::= number
117 | string
118
119
120 For the case where expression ::= symbol, it evaluates to true
121 if the symbol is defined to a non-empty string.
122
123 Operator precedence, starting from lowest to highest:
124
125 or (left associative)
126 and (left associative)
127 not (right associative)
128 all comparison operators (non-associative)
129
130 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
131 are all syntactic sugar for these expressions. For instance
132
133 arch_exclude = x86 arc
134
135 Is the same as:
136
137 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700138
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700139 The ':' operator compiles the string argument as a regular expression,
140 and then returns a true value only if the symbol's value in the environment
141 matches. For example, if CONFIG_SOC="quark_se" then
142
143 filter = CONFIG_SOC : "quark.*"
144
145 Would match it.
146
Anas Nashifa792a3d2017-04-04 18:47:49 -0400147The set of test cases that actually run depends on directives in the testcase
148filed and options passed in on the command line. If there is any confusion,
149running with -v or --discard-report can help show why particular test cases
150were skipped.
Andrew Boie6acbe632015-07-17 12:03:52 -0700151
152Metrics (such as pass/fail state and binary size) for the last code
153release are stored in scripts/sanity_chk/sanity_last_release.csv.
154To update this, pass the --all --release options.
155
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500156To load arguments from a file, write '+' before the file name, e.g.,
157+file_name. File content must be one or more valid arguments separated by
158line break instead of white spaces.
159
Andrew Boie6acbe632015-07-17 12:03:52 -0700160Most everyday users will run with no arguments.
Andy Rossdc4151f2019-01-03 14:17:43 -0800161
Andrew Boie6acbe632015-07-17 12:03:52 -0700162"""
163
Sebastian Bøe56d74712019-01-21 15:48:46 +0100164import os
165
166if os.name == 'nt':
167 print("Running sanitycheck on Windows is not supported yet.")
168 print("https://github.com/zephyrproject-rtos/zephyr/issues/2664")
169 exit(1)
170
Anas Nashifaae71d72018-04-21 22:26:48 -0500171import contextlib
Anas Nashifa4c368e2018-10-15 09:45:59 -0400172import string
Anas Nashifaae71d72018-04-21 22:26:48 -0500173import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700174import argparse
Andrew Boie6acbe632015-07-17 12:03:52 -0700175import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700176import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700177import subprocess
178import multiprocessing
179import select
180import shutil
181import signal
182import threading
183import time
184import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700185import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600186import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700187import concurrent
188import concurrent.futures
Anas Nashifb3311ed2017-04-13 14:44:48 -0400189import xml.etree.ElementTree as ET
Anas Nashife6fcc012017-05-17 09:29:09 -0400190from xml.sax.saxutils import escape
Anas Nashif035799f2017-05-13 21:31:53 -0400191from collections import OrderedDict
192from itertools import islice
Anas Nashif1a5bba72018-01-05 08:07:45 -0500193from functools import cmp_to_key
Anas Nashife24350c2018-07-11 15:09:22 -0500194from pathlib import Path
195from distutils.spawn import find_executable
Andrew Boie6acbe632015-07-17 12:03:52 -0700196
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700197import logging
Anas Nashif3ba1d432017-12-05 15:28:44 -0500198from sanity_chk import scl
199from sanity_chk import expr_parser
200
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700201log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500202logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700203
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +0300204ZEPHYR_BASE = os.environ.get("ZEPHYR_BASE")
205if not ZEPHYR_BASE:
Anas Nashif427cdd32015-08-06 07:25:42 -0400206 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700207 exit(1)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700208
Andrew Boie3ea78922016-03-24 14:46:00 -0700209sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
210
Andrew Boie3ea78922016-03-24 14:46:00 -0700211
Andrew Boie6acbe632015-07-17 12:03:52 -0700212VERBOSE = 0
213LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
214 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400215LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
Anas Nashif3ba1d432017-12-05 15:28:44 -0500216 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700217RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
218 "sanity_last_release.csv")
Oleg Zhurakivskyyf3bc9672018-08-17 18:31:38 +0300219JOBS = multiprocessing.cpu_count() * 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700220
221if os.isatty(sys.stdout.fileno()):
222 TERMINAL = True
223 COLOR_NORMAL = '\033[0m'
224 COLOR_RED = '\033[91m'
225 COLOR_GREEN = '\033[92m'
226 COLOR_YELLOW = '\033[93m'
227else:
228 TERMINAL = False
229 COLOR_NORMAL = ""
230 COLOR_RED = ""
231 COLOR_GREEN = ""
232 COLOR_YELLOW = ""
233
Anas Nashif45a97862019-01-09 08:46:42 -0500234class CMakeCacheEntry:
235 '''Represents a CMake cache entry.
236
237 This class understands the type system in a CMakeCache.txt, and
238 converts the following cache types to Python types:
239
240 Cache Type Python type
241 ---------- -------------------------------------------
242 FILEPATH str
243 PATH str
244 STRING str OR list of str (if ';' is in the value)
245 BOOL bool
246 INTERNAL str OR list of str (if ';' is in the value)
247 ---------- -------------------------------------------
248 '''
249
250 # Regular expression for a cache entry.
251 #
252 # CMake variable names can include escape characters, allowing a
253 # wider set of names than is easy to match with a regular
254 # expression. To be permissive here, use a non-greedy match up to
255 # the first colon (':'). This breaks if the variable name has a
256 # colon inside, but it's good enough.
257 CACHE_ENTRY = re.compile(
258 r'''(?P<name>.*?) # name
259 :(?P<type>FILEPATH|PATH|STRING|BOOL|INTERNAL) # type
260 =(?P<value>.*) # value
261 ''', re.X)
262
263 @classmethod
264 def _to_bool(cls, val):
265 # Convert a CMake BOOL string into a Python bool.
266 #
267 # "True if the constant is 1, ON, YES, TRUE, Y, or a
268 # non-zero number. False if the constant is 0, OFF, NO,
269 # FALSE, N, IGNORE, NOTFOUND, the empty string, or ends in
270 # the suffix -NOTFOUND. Named boolean constants are
271 # case-insensitive. If the argument is not one of these
272 # constants, it is treated as a variable."
273 #
274 # https://cmake.org/cmake/help/v3.0/command/if.html
275 val = val.upper()
276 if val in ('ON', 'YES', 'TRUE', 'Y'):
277 return 1
278 elif val in ('OFF', 'NO', 'FALSE', 'N', 'IGNORE', 'NOTFOUND', ''):
279 return 0
280 elif val.endswith('-NOTFOUND'):
281 return 0
282 else:
283 try:
284 v = int(val)
285 return v != 0
286 except ValueError as exc:
287 raise ValueError('invalid bool {}'.format(val)) from exc
288
289 @classmethod
290 def from_line(cls, line, line_no):
291 # Comments can only occur at the beginning of a line.
292 # (The value of an entry could contain a comment character).
293 if line.startswith('//') or line.startswith('#'):
294 return None
295
296 # Whitespace-only lines do not contain cache entries.
297 if not line.strip():
298 return None
299
300 m = cls.CACHE_ENTRY.match(line)
301 if not m:
302 return None
303
304 name, type_, value = (m.group(g) for g in ('name', 'type', 'value'))
305 if type_ == 'BOOL':
306 try:
307 value = cls._to_bool(value)
308 except ValueError as exc:
309 args = exc.args + ('on line {}: {}'.format(line_no, line),)
310 raise ValueError(args) from exc
311 elif type_ == 'STRING' or type_ == 'INTERNAL':
312 # If the value is a CMake list (i.e. is a string which
313 # contains a ';'), convert to a Python list.
314 if ';' in value:
315 value = value.split(';')
316
317 return CMakeCacheEntry(name, value)
318
319 def __init__(self, name, value):
320 self.name = name
321 self.value = value
322
323 def __str__(self):
324 fmt = 'CMakeCacheEntry(name={}, value={})'
325 return fmt.format(self.name, self.value)
326
327
328class CMakeCache:
329 '''Parses and represents a CMake cache file.'''
330
331 @staticmethod
332 def from_file(cache_file):
333 return CMakeCache(cache_file)
334
335 def __init__(self, cache_file):
336 self.cache_file = cache_file
337 self.load(cache_file)
338
339 def load(self, cache_file):
340 entries = []
341 with open(cache_file, 'r') as cache:
342 for line_no, line in enumerate(cache):
343 entry = CMakeCacheEntry.from_line(line, line_no)
344 if entry:
345 entries.append(entry)
346 self._entries = OrderedDict((e.name, e) for e in entries)
347
348 def get(self, name, default=None):
349 entry = self._entries.get(name)
350 if entry is not None:
351 return entry.value
352 else:
353 return default
354
355 def get_list(self, name, default=None):
356 if default is None:
357 default = []
358 entry = self._entries.get(name)
359 if entry is not None:
360 value = entry.value
361 if isinstance(value, list):
362 return value
363 elif isinstance(value, str):
364 return [value] if value else []
365 else:
366 msg = 'invalid value {} type {}'
367 raise RuntimeError(msg.format(value, type(value)))
368 else:
369 return default
370
371 def __contains__(self, name):
372 return name in self._entries
373
374 def __getitem__(self, name):
375 return self._entries[name].value
376
377 def __setitem__(self, name, entry):
378 if not isinstance(entry, CMakeCacheEntry):
379 msg = 'improper type {} for value {}, expecting CMakeCacheEntry'
380 raise TypeError(msg.format(type(entry), entry))
381 self._entries[name] = entry
382
383 def __delitem__(self, name):
384 del self._entries[name]
385
386 def __iter__(self):
387 return iter(self._entries.values())
388
Andrew Boie6acbe632015-07-17 12:03:52 -0700389class SanityCheckException(Exception):
390 pass
391
Anas Nashif3ba1d432017-12-05 15:28:44 -0500392
Andrew Boie6acbe632015-07-17 12:03:52 -0700393class SanityRuntimeError(SanityCheckException):
394 pass
395
Anas Nashif3ba1d432017-12-05 15:28:44 -0500396
Andrew Boie6acbe632015-07-17 12:03:52 -0700397class ConfigurationError(SanityCheckException):
398 def __init__(self, cfile, message):
399 self.cfile = cfile
400 self.message = message
401
402 def __str__(self):
403 return repr(self.cfile + ": " + self.message)
404
Anas Nashif3ba1d432017-12-05 15:28:44 -0500405
Andrew Boie6acbe632015-07-17 12:03:52 -0700406class MakeError(SanityCheckException):
407 pass
408
Anas Nashif3ba1d432017-12-05 15:28:44 -0500409
Andrew Boie6acbe632015-07-17 12:03:52 -0700410class BuildError(MakeError):
411 pass
412
Anas Nashif3ba1d432017-12-05 15:28:44 -0500413
Andrew Boie6acbe632015-07-17 12:03:52 -0700414class ExecutionError(MakeError):
415 pass
416
Anas Nashif3ba1d432017-12-05 15:28:44 -0500417
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800418log_file = None
419
Anas Nashif3ba1d432017-12-05 15:28:44 -0500420
Andrew Boie6acbe632015-07-17 12:03:52 -0700421# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800422def info(what):
423 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300424 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800425 if log_file:
426 log_file.write(what + "\n")
427 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700428
Anas Nashif3ba1d432017-12-05 15:28:44 -0500429
Andrew Boie6acbe632015-07-17 12:03:52 -0700430def error(what):
431 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800432 if log_file:
433 log_file(what + "\n")
434 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700435
Anas Nashif3ba1d432017-12-05 15:28:44 -0500436
Andrew Boie08ce5a52016-02-22 13:28:10 -0800437def debug(what):
438 if VERBOSE >= 1:
439 info(what)
440
Anas Nashif3ba1d432017-12-05 15:28:44 -0500441
Andrew Boie6acbe632015-07-17 12:03:52 -0700442def verbose(what):
443 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800444 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700445
Anas Nashif576be982017-12-23 20:20:27 -0500446class HarnessImporter:
447
448 def __init__(self, name):
449 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
450 module = __import__("harness")
451 if name:
452 my_class = getattr(module, name)
453 else:
454 my_class = getattr(module, "Test")
455
456 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500457
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300458class Handler:
Anas Nashif576be982017-12-23 20:20:27 -0500459 def __init__(self, instance):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300460 """Constructor
461
462 @param name Arbitrary name of the created thread
Anas Nashif66bdb322017-11-25 17:20:07 -0500463 @param outdir Working directory, should be where handler pid file (qemu.pid for example)
464 gets created by the build system
465 @param log_fn Absolute path to write out handler's log data
466 @param timeout Kill the handler process if it doesn't finish up within
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300467 the given number of seconds
468 """
469 self.lock = threading.Lock()
470 self.state = "waiting"
Anas Nashif13773752018-07-06 18:20:23 -0500471 self.run = False
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300472 self.metrics = {}
Anas Nashifc8390f12017-11-25 17:14:12 -0500473 self.metrics["handler_time"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300474 self.metrics["ram_size"] = 0
475 self.metrics["rom_size"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300476
Anas Nashifdf7ee612018-07-07 06:09:01 -0500477 self.binary = None
Jan Kowalewski265895b2019-01-07 16:40:24 +0100478 self.pid_fn = None
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500479 self.call_make_run = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500480
Anas Nashifd3384fb2018-02-22 06:44:16 -0600481 self.name = instance.name
482 self.instance = instance
483 self.timeout = instance.test.timeout
Anas Nashiff18ad9d2018-11-20 09:03:17 -0500484 self.sourcedir = instance.test.test_path
Anas Nashifd3384fb2018-02-22 06:44:16 -0600485 self.outdir = instance.outdir
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500486 self.log = os.path.join(self.outdir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600487 self.returncode = 0
488 self.set_state("running", {})
489
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300490 def set_state(self, state, metrics):
491 self.lock.acquire()
492 self.state = state
493 self.metrics.update(metrics)
494 self.lock.release()
495
496 def get_state(self):
497 self.lock.acquire()
498 ret = (self.state, self.metrics)
499 self.lock.release()
500 return ret
501
Anas Nashifdf7ee612018-07-07 06:09:01 -0500502class BinaryHandler(Handler):
503 def __init__(self, instance):
504 """Constructor
505
506 @param instance Test Instance
507 """
508 super().__init__(instance)
509
510 self.valgrind = False
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100511 self.terminated = False
Anas Nashifdf7ee612018-07-07 06:09:01 -0500512
Jan Kowalewski265895b2019-01-07 16:40:24 +0100513 def try_kill_process_by_pid(self):
514 if self.pid_fn != None:
515 pid = int(open(self.pid_fn).read())
516 os.unlink(self.pid_fn)
517 self.pid_fn = None # clear so we don't try to kill the binary twice
518 try:
519 os.kill(pid, signal.SIGTERM)
520 except ProcessLookupError:
521 pass
522
Anas Nashifdf7ee612018-07-07 06:09:01 -0500523 def _output_reader(self, proc, harness):
524 log_out_fp = open(self.log, "wt")
525 for line in iter(proc.stdout.readline, b''):
526 verbose("OUTPUT: {0}".format(line.decode('utf-8').rstrip()))
527 log_out_fp.write(line.decode('utf-8'))
528 log_out_fp.flush()
529 harness.handle(line.decode('utf-8').rstrip())
530 if harness.state:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100531 try:
532 #POSIX arch based ztests end on their own,
533 #so let's give it up to 100ms to do so
534 proc.wait(0.1)
535 except subprocess.TimeoutExpired:
536 proc.terminate()
537 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500538 break
539
540 log_out_fp.close()
541
542 def handle(self):
Anas Nashifdf7ee612018-07-07 06:09:01 -0500543
544 harness_name = self.instance.test.harness.capitalize()
545 harness_import = HarnessImporter(harness_name)
546 harness = harness_import.instance
547 harness.configure(self.instance)
548
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500549 if self.call_make_run:
550 if options.ninja:
551 generator_cmd = "ninja"
552 else:
553 generator_cmd = "make"
554 command = [generator_cmd, "-C", self.outdir, "run"]
555 else:
556 command = [self.binary]
Anas Nashifdf7ee612018-07-07 06:09:01 -0500557
558 if shutil.which("valgrind") and self.valgrind:
559 command = ["valgrind", "--error-exitcode=2",
Alberto Escolar Piedras3980e0c2018-12-12 17:41:04 +0100560 "--leak-check=full",
561 "--suppressions="+ZEPHYR_BASE+"/scripts/valgrind.supp",
562 "--log-file="+self.outdir+"/valgrind.log"
563 ] + command
Anas Nashifdf7ee612018-07-07 06:09:01 -0500564
565 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
566 t = threading.Thread(target=self._output_reader, args=(proc, harness, ))
567 t.start()
568 t.join(self.timeout)
569 if t.is_alive():
Jan Kowalewski265895b2019-01-07 16:40:24 +0100570 self.try_kill_process_by_pid()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500571 proc.terminate()
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100572 self.terminated = True
Anas Nashifdf7ee612018-07-07 06:09:01 -0500573 t.join()
Anas Nashifdf7ee612018-07-07 06:09:01 -0500574 proc.wait()
575 self.returncode = proc.returncode
Anas Nashifdf7ee612018-07-07 06:09:01 -0500576
577 if options.enable_coverage:
578 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir,
579 "gcov", self.sourcedir, "-b", "-s", self.outdir], shell=True)
580
Jan Kowalewski265895b2019-01-07 16:40:24 +0100581 self.try_kill_process_by_pid()
582
Anas Nashif99f5a6c2018-07-07 08:45:53 -0500583 # FIME: This is needed when killing the simulator, the console is
584 # garbled and needs to be reset. Did not find a better way to do that.
585
586 subprocess.call(["stty", "sane"])
Anas Nashifdf7ee612018-07-07 06:09:01 -0500587 self.instance.results = harness.tests
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100588 if self.terminated==False and self.returncode != 0:
589 #When a process is killed, the default handler returns 128 + SIGTERM
590 #so in that case the return code itself is not meaningful
591 self.set_state("error", {})
592 elif harness.state:
Anas Nashifdf7ee612018-07-07 06:09:01 -0500593 self.set_state(harness.state, {})
594 else:
Alberto Escolar Piedras661bc822018-12-11 15:13:05 +0100595 self.set_state("timeout", {})
Anas Nashif73440ea2018-02-19 10:57:03 -0600596
597class DeviceHandler(Handler):
598
599 def __init__(self, instance):
600 """Constructor
601
602 @param instance Test Instance
603 """
604 super().__init__(instance)
605
Marti Bolivar5591ca22019-02-07 15:53:39 -0700606 def monitor_serial(self, ser, halt_fileno, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500607 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600608
Marti Bolivar5591ca22019-02-07 15:53:39 -0700609 ser_fileno = ser.fileno()
610 readlist = [halt_fileno, ser_fileno]
611
Anas Nashif73440ea2018-02-19 10:57:03 -0600612 while ser.isOpen():
Marti Bolivar5591ca22019-02-07 15:53:39 -0700613 readable, _, _ = select.select(readlist, [], [], self.timeout)
614
615 if halt_fileno in readable:
616 verbose('halted')
617 ser.close()
618 break
619 if ser_fileno not in readable:
620 continue # Timeout.
621
622 serial_line = None
Anas Nashif61e21632018-04-08 13:30:16 -0500623 try:
624 serial_line = ser.readline()
625 except TypeError:
626 pass
Anas Nashif59237602019-03-03 10:36:35 -0500627 except serial.serialutil.SerialException:
628 ser.close()
629 break
Anas Nashif61e21632018-04-08 13:30:16 -0500630
Marti Bolivar5591ca22019-02-07 15:53:39 -0700631 # Just because ser_fileno has data doesn't mean an entire line
632 # is available yet.
Anas Nashif73440ea2018-02-19 10:57:03 -0600633 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600634 sl = serial_line.decode('utf-8', 'ignore')
635 verbose("DEVICE: {0}".format(sl.rstrip()))
636
637 log_out_fp.write(sl)
638 log_out_fp.flush()
639 harness.handle(sl.rstrip())
Marti Bolivar5591ca22019-02-07 15:53:39 -0700640
Anas Nashif73440ea2018-02-19 10:57:03 -0600641 if harness.state:
642 ser.close()
643 break
644
645 log_out_fp.close()
646
647 def handle(self):
648 out_state = "failed"
649
Anas Nashifd3384fb2018-02-22 06:44:16 -0600650 if options.ninja:
651 generator_cmd = "ninja"
652 else:
653 generator_cmd = "make"
654
655 command = [generator_cmd, "-C", self.outdir, "flash"]
656
Anas Nashif73440ea2018-02-19 10:57:03 -0600657 device = options.device_serial
658 ser = serial.Serial(
659 device,
660 baudrate=115200,
661 parity=serial.PARITY_NONE,
662 stopbits=serial.STOPBITS_ONE,
663 bytesize=serial.EIGHTBITS,
664 timeout=self.timeout
665 )
666
667 ser.flush()
668
669 harness_name = self.instance.test.harness.capitalize()
670 harness_import = HarnessImporter(harness_name)
671 harness = harness_import.instance
672 harness.configure(self.instance)
Marti Bolivar5591ca22019-02-07 15:53:39 -0700673 rpipe, wpipe = os.pipe()
Anas Nashif73440ea2018-02-19 10:57:03 -0600674
Marti Bolivar5591ca22019-02-07 15:53:39 -0700675 t = threading.Thread(target=self.monitor_serial, daemon=True,
676 args=(ser, rpipe, harness))
Anas Nashif73440ea2018-02-19 10:57:03 -0600677 t.start()
678
Anas Nashif61e21632018-04-08 13:30:16 -0500679 try:
Marti Bolivar303b5222019-02-07 15:50:55 -0700680 if VERBOSE:
681 subprocess.check_call(command)
682 else:
683 subprocess.check_output(command, stderr=subprocess.PIPE)
Anas Nashif61e21632018-04-08 13:30:16 -0500684 except subprocess.CalledProcessError:
Marti Bolivar5591ca22019-02-07 15:53:39 -0700685 os.write(wpipe, b'x') # halt the thread
Anas Nashif73440ea2018-02-19 10:57:03 -0600686
687 t.join(self.timeout)
688 if t.is_alive():
689 out_state = "timeout"
Anas Nashif73440ea2018-02-19 10:57:03 -0600690
691 if ser.isOpen():
692 ser.close()
693
Anas Nashifd3384fb2018-02-22 06:44:16 -0600694 if out_state == "timeout":
695 for c in self.instance.test.cases:
696 if c not in harness.tests:
697 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500698
699 self.instance.results = harness.tests
Anas Nashif73440ea2018-02-19 10:57:03 -0600700 if harness.state:
701 self.set_state(harness.state, {})
702 else:
703 self.set_state(out_state, {})
704
Anas Nashif3ba1d432017-12-05 15:28:44 -0500705
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300706class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700707 """Spawns a thread to monitor QEMU output from pipes
708
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400709 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700710 We need to do this as once qemu starts, it runs forever until killed.
711 Test cases emit special messages to the console as they run, we check
712 for these to collect whether the test passed or failed.
713 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700714
715 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500716 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700717 fifo_in = fifo_fn + ".in"
718 fifo_out = fifo_fn + ".out"
719
720 # These in/out nodes are named from QEMU's perspective, not ours
721 if os.path.exists(fifo_in):
722 os.unlink(fifo_in)
723 os.mkfifo(fifo_in)
724 if os.path.exists(fifo_out):
725 os.unlink(fifo_out)
726 os.mkfifo(fifo_out)
727
728 # We don't do anything with out_fp but we need to open it for
729 # writing so that QEMU doesn't block, due to the way pipes work
730 out_fp = open(fifo_in, "wb")
731 # Disable internal buffering, we don't
732 # want read() or poll() to ever block if there is data in there
733 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800734 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700735
736 start_time = time.time()
737 timeout_time = start_time + timeout
738 p = select.poll()
739 p.register(in_fp, select.POLLIN)
Anas Nashif39ae72b2018-08-29 01:45:38 -0400740 out_state = None
Andrew Boie6acbe632015-07-17 12:03:52 -0700741
742 metrics = {}
743 line = ""
Anas Nashif74dbe332019-01-17 17:11:37 -0500744 timeout_extended = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700745 while True:
746 this_timeout = int((timeout_time - time.time()) * 1000)
747 if this_timeout < 0 or not p.poll(this_timeout):
Anas Nashif39ae72b2018-08-29 01:45:38 -0400748 if not out_state:
749 out_state = "timeout"
Andrew Boie6acbe632015-07-17 12:03:52 -0700750 break
751
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500752 try:
753 c = in_fp.read(1).decode("utf-8")
754 except UnicodeDecodeError:
755 # Test is writing something weird, fail
756 out_state = "unexpected byte"
757 break
758
Andrew Boie6acbe632015-07-17 12:03:52 -0700759 if c == "":
760 # EOF, this shouldn't happen unless QEMU crashes
761 out_state = "unexpected eof"
762 break
763 line = line + c
764 if c != "\n":
765 continue
766
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300767 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700768 log_out_fp.write(line)
769 log_out_fp.flush()
770 line = line.strip()
771 verbose("QEMU: %s" % line)
772
Anas Nashif576be982017-12-23 20:20:27 -0500773 harness.handle(line)
774 if harness.state:
Anas Nashif39ae72b2018-08-29 01:45:38 -0400775 # if we have registered a fail make sure the state is not
776 # overridden by a false success message coming from the
777 # testsuite
778 if out_state != 'failed':
779 out_state = harness.state
780
781 # if we get some state, that means test is doing well, we reset
Anas Nashif74dbe332019-01-17 17:11:37 -0500782 # the timeout and wait for 2 more seconds just in case we have
Anas Nashif39ae72b2018-08-29 01:45:38 -0400783 # crashed after test has completed
Anas Nashiff29087e2019-01-25 09:37:38 -0500784 if not timeout_extended or harness.capture_coverage:
785 timeout_extended= True
786 if harness.capture_coverage:
787 timeout_time = time.time() + 10
788 else:
Anas Nashif74dbe332019-01-17 17:11:37 -0500789 timeout_time = time.time() + 2
Andrew Boie6acbe632015-07-17 12:03:52 -0700790
791 # TODO: Add support for getting numerical performance data
792 # from test cases. Will involve extending test case reporting
793 # APIs. Add whatever gets reported to the metrics dictionary
794 line = ""
795
Anas Nashifc8390f12017-11-25 17:14:12 -0500796 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700797 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500798 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700799 handler.set_state(out_state, metrics)
800
801 log_out_fp.close()
802 out_fp.close()
803 in_fp.close()
804
805 pid = int(open(pid_fn).read())
806 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800807 try:
808 os.kill(pid, signal.SIGTERM)
809 except ProcessLookupError:
810 # Oh well, as long as it's dead! User probably sent Ctrl-C
811 pass
812
Andrew Boie6acbe632015-07-17 12:03:52 -0700813 os.unlink(fifo_in)
814 os.unlink(fifo_out)
815
Anas Nashif576be982017-12-23 20:20:27 -0500816 def __init__(self, instance):
Andrew Boie6acbe632015-07-17 12:03:52 -0700817 """Constructor
818
Anas Nashifd3384fb2018-02-22 06:44:16 -0600819 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700820 """
Anas Nashif576be982017-12-23 20:20:27 -0500821
Anas Nashif576be982017-12-23 20:20:27 -0500822 super().__init__(instance)
Anas Nashif576be982017-12-23 20:20:27 -0500823
Andrew Boie6acbe632015-07-17 12:03:52 -0700824 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500825 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700826
827 # We pass this to QEMU which looks for fifos with .in and .out
828 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500829 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700830
Anas Nashif576be982017-12-23 20:20:27 -0500831 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700832 if os.path.exists(self.pid_fn):
833 os.unlink(self.pid_fn)
834
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500835 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500836
837 harness_import = HarnessImporter(instance.test.harness.capitalize())
838 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600839 harness.configure(self.instance)
840 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
841 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300842 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500843 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600844
845 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700846 self.thread.daemon = True
Anas Nashifd3384fb2018-02-22 06:44:16 -0600847 verbose("Spawning QEMU process for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700848 self.thread.start()
849
Andrew Boie6acbe632015-07-17 12:03:52 -0700850 def get_fifo(self):
851 return self.fifo_fn
852
Anas Nashif3ba1d432017-12-05 15:28:44 -0500853
Andrew Boie6acbe632015-07-17 12:03:52 -0700854class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700855
Erwin Rolcb3d1272018-02-10 11:40:40 +0100856 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
857 "ccm_noinit"]
Daniel Leungc8066c52019-03-09 00:35:40 -0800858 rw_sections = ["datas", "initlevel", "exceptions", "initshell",
Andrew Boiec2e01df2018-11-12 15:16:54 -0800859 "_static_thread_area", "_k_timer_area",
Andrew Boie506f15c2018-11-08 14:44:31 -0800860 "_k_mem_slab_area", "_k_mem_pool_area", "sw_isr_table",
Andrew Boiec253a682019-01-29 12:56:02 -0800861 "_k_sem_area", "_k_mutex_area", "app_shmem_regions",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700862 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
863 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200864 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700865 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100866 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500867 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
868 'log_backends_sections', 'log_dynamic_sections',
Johann Fischerfcffebf2018-09-13 00:53:15 +0200869 'log_const_sections',"app_smem", 'shell_root_cmds_sections',
Adithya Baglody1fa8cf92018-10-19 10:15:19 -0700870 'log_const_sections',"app_smem", "font_entry_sections",
Anas Nashifdb9592a2018-10-08 10:19:41 -0400871 "priv_stacks_noinit", "_TEXT_SECTION_NAME_2",
Kumar Galae6393dd2019-02-16 10:08:33 -0600872 '_GCOV_BSS_SECTION_NAME', 'gcov', 'nocache']
Anas Nashifdb9592a2018-10-08 10:19:41 -0400873
Andrew Boie73b4ee62015-10-07 11:33:22 -0700874 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700875 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andrew Boie506f15c2018-11-08 14:44:31 -0800876 "rodata", "devconfig", "net_l2", "vector", "sw_isr_table",
877 "_bt_settings_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700878
Andrew Boie52fef672016-11-29 12:21:59 -0800879 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700880 """Constructor
881
Andrew Boiebbd670c2015-08-17 13:16:11 -0700882 @param filename Path to the output binary
883 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700884 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700885 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700886 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700887 magic = f.read(4)
888
Anas Nashifb4bdd662018-08-15 17:12:28 -0500889 try:
890 if (magic != b'\x7fELF'):
891 raise SanityRuntimeError("%s is not an ELF binary" % filename)
892 except Exception as e:
893 print(str(e))
894 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -0700895
896 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500897 # GREP can not be used as it returns an error if the symbol is not
898 # found.
899 is_xip_command = "nm " + filename + \
900 " | awk '/CONFIG_XIP/ { print $3 }'"
901 is_xip_output = subprocess.check_output(
902 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
903 "utf-8").strip()
Anas Nashifb4bdd662018-08-15 17:12:28 -0500904 try:
905 if is_xip_output.endswith("no symbols"):
906 raise SanityRuntimeError("%s has no symbol information" % filename)
907 except Exception as e:
908 print(str(e))
909 sys.exit(2)
910
Andrew Boie6acbe632015-07-17 12:03:52 -0700911 self.is_xip = (len(is_xip_output) != 0)
912
Andrew Boiebbd670c2015-08-17 13:16:11 -0700913 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700914 self.sections = []
915 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700916 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800917 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700918
919 self._calculate_sizes()
920
921 def get_ram_size(self):
922 """Get the amount of RAM the application will use up on the device
923
924 @return amount of RAM, in bytes
925 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700926 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700927
928 def get_rom_size(self):
929 """Get the size of the data that this application uses on device's flash
930
931 @return amount of ROM, in bytes
932 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700933 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700934
935 def unrecognized_sections(self):
936 """Get a list of sections inside the binary that weren't recognized
937
David B. Kinder29963c32017-06-16 12:32:42 -0700938 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700939 """
940 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700941 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700942 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700943 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700944 return slist
945
946 def _calculate_sizes(self):
947 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700948 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500949 objdump_output = subprocess.check_output(
950 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700951
952 for line in objdump_output:
953 words = line.split()
954
955 if (len(words) == 0): # Skip lines that are too short
956 continue
957
958 index = words[0]
959 if (not index[0].isdigit()): # Skip lines that do not start
960 continue # with a digit
961
962 name = words[1] # Skip lines with section names
963 if (name[0] == '.'): # starting with '.'
964 continue
965
Andrew Boie73b4ee62015-10-07 11:33:22 -0700966 # TODO this doesn't actually reflect the size in flash or RAM as
967 # it doesn't include linker-imposed padding between sections.
968 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700969 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700970 if size == 0:
971 continue
972
Andrew Boie73b4ee62015-10-07 11:33:22 -0700973 load_addr = int(words[4], 16)
974 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700975
976 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700977 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700978 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700979 if name in SizeCalculator.alloc_sections:
980 self.ram_size += size
981 stype = "alloc"
982 elif name in SizeCalculator.rw_sections:
983 self.ram_size += size
984 self.rom_size += size
985 stype = "rw"
986 elif name in SizeCalculator.ro_sections:
987 self.rom_size += size
988 if not self.is_xip:
989 self.ram_size += size
990 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700991 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700992 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800993 if name not in self.extra_sections:
994 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700995
Anas Nashif3ba1d432017-12-05 15:28:44 -0500996 self.sections.append({"name": name, "load_addr": load_addr,
997 "size": size, "virt_addr": virt_addr,
998 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700999
1000
1001class MakeGoal:
1002 """Metadata class representing one of the sub-makes called by MakeGenerator
1003
David B. Kinder29963c32017-06-16 12:32:42 -07001004 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -07001005 with TestInstances to get a complete picture of what happened during a test.
1006 MakeGenerator is used for tasks outside of building tests (such as
1007 defconfigs) which is why MakeGoal is a separate class from TestInstance.
1008 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001009
Anas Nashif4d25b502017-11-25 17:37:17 -05001010 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -07001011 self.name = name
1012 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -05001013 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001014 self.make_log = make_log
1015 self.build_log = build_log
1016 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -05001017 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001018 self.make_state = "waiting"
1019 self.failed = False
1020 self.finished = False
1021 self.reason = None
1022 self.metrics = {}
1023
1024 def get_error_log(self):
1025 if self.make_state == "waiting":
1026 # Shouldn't ever see this; breakage in the main Makefile itself.
1027 return self.make_log
1028 elif self.make_state == "building":
1029 # Failure when calling the sub-make to build the code
1030 return self.build_log
1031 elif self.make_state == "running":
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001032 # Failure in sub-make for "make run", qemu probably failed to start
Andrew Boie6acbe632015-07-17 12:03:52 -07001033 return self.run_log
1034 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -05001035 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -05001036 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -07001037
1038 def fail(self, reason):
1039 self.failed = True
1040 self.finished = True
1041 self.reason = reason
1042
1043 def success(self):
1044 self.finished = True
1045
1046 def __str__(self):
1047 if self.finished:
1048 if self.failed:
1049 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
1050 self.get_error_log())
1051 else:
1052 return "[%s] passed" % self.name
1053 else:
1054 return "[%s] in progress (%s)" % (self.name, self.make_state)
1055
1056
1057class MakeGenerator:
1058 """Generates a Makefile which just calls a bunch of sub-make sessions
1059
1060 In any given test suite we may need to build dozens if not hundreds of
1061 test cases. The cleanest way to parallelize this is to just let Make
1062 do the parallelization, sharing the jobserver among all the different
1063 sub-make targets.
1064 """
1065
1066 GOAL_HEADER_TMPL = """.PHONY: {goal}
1067{goal}:
1068"""
1069
1070 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -04001071\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -05001072\t\t-G"{generator}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001073\t\t-H{directory}\\
1074\t\t-B{outdir}\\
1075\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
1076\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -05001077\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001078\t\t{args}\\
1079\t\t>{logfile} 2>&1
Anas Nashifa8a13882017-12-30 13:01:06 -05001080\t{generator_cmd} -C {outdir}\\
1081\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -04001082\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -07001083"""
Anas Nashif20f553f2018-03-23 11:26:41 -05001084 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
1085\t{generator_cmd} -C {outdir}\\
1086\t\t{verb} {make_args}\\
1087\t\t>>{logfile} 2>&1
1088"""
Andrew Boie6acbe632015-07-17 12:03:52 -07001089
1090 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
1091
Anas Nashif3ba1d432017-12-05 15:28:44 -05001092 re_make = re.compile(
1093 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -07001094
Anas Nashif37f9dc52018-02-23 08:53:46 -06001095 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001096 """MakeGenerator constructor
1097
1098 @param base_outdir Intended to be the base out directory. A make.log
1099 file will be created here which contains the output of the
1100 top-level Make session, as well as the dynamic control Makefile
1101 @param verbose If true, pass V=1 to all the sub-makes which greatly
1102 increases their verbosity
1103 """
1104 self.goals = {}
1105 if not os.path.exists(base_outdir):
1106 os.makedirs(base_outdir)
1107 self.logfile = os.path.join(base_outdir, "make.log")
1108 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -06001109 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -07001110
1111 def _get_rule_header(self, name):
1112 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
1113
Anas Nashif3ba1d432017-12-05 15:28:44 -05001114 def _get_sub_make(self, name, phase, workdir, outdir,
1115 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -04001116 """
1117 @param args Arguments given to CMake
1118 @param make_args Arguments given to the Makefile generated by CMake
1119 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001120 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -05001121 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -07001122 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -05001123
1124 if self.deprecations:
1125 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -08001126
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +02001127 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -05001128
Anas Nashif25f6ab62018-03-06 07:15:11 -06001129 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001130 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -07001131 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +01001132 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -06001133 else:
1134 generator = "Unix Makefiles"
1135 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -07001136 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -05001137
Anas Nashif20f553f2018-03-23 11:26:41 -05001138 if phase == 'running':
1139 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
1140 generator_cmd=generator_cmd,
1141 phase=phase,
1142 goal=name,
1143 outdir=outdir,
1144 verb=verb,
1145 logfile=logfile,
1146 make_args=make_args
1147 )
1148 else:
1149 return MakeGenerator.MAKE_RULE_TMPL.format(
1150 generator=generator,
1151 generator_cmd=generator_cmd,
1152 phase=phase,
1153 goal=name,
1154 outdir=outdir,
1155 cflags=cflags,
1156 ldflags=ldflags,
1157 directory=workdir,
1158 verb=verb,
1159 args=args,
1160 logfile=logfile,
1161 make_args=make_args
1162 )
Andrew Boie6acbe632015-07-17 12:03:52 -07001163
1164 def _get_rule_footer(self, name):
1165 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
1166
Anas Nashif3ba1d432017-12-05 15:28:44 -05001167 def add_build_goal(self, name, directory, outdir,
1168 args, buildlog, make_args=""):
Anas Nashif13773752018-07-06 18:20:23 -05001169 """Add a goal to invoke a build session
Andrew Boie6acbe632015-07-17 12:03:52 -07001170
1171 @param name A unique string name for this build goal. The results
1172 dictionary returned by execute() will be keyed by this name.
1173 @param directory Absolute path to working directory, will be passed
1174 to make -C
1175 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001176 cmake via -B=<path>
1177 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -07001178 environment variables or specific Make goals
1179 """
Andrew Boie6acbe632015-07-17 12:03:52 -07001180
Anas Nashif13773752018-07-06 18:20:23 -05001181 if not os.path.exists(outdir):
1182 os.makedirs(outdir)
1183
1184 build_logfile = os.path.join(outdir, buildlog)
1185 text = self._get_rule_header(name)
1186 text += self._get_sub_make(name, "building", directory, outdir, build_logfile,
1187 args, make_args=make_args)
1188 text += self._get_rule_footer(name)
1189
1190 self.goals[name] = MakeGoal( name, text, None, self.logfile, build_logfile, None, None)
1191
1192 def add_goal(self, instance, type, args, make_args=""):
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001193
Anas Nashif13773752018-07-06 18:20:23 -05001194 """Add a goal to build a Zephyr project and then run it using a handler
Andrew Boie6acbe632015-07-17 12:03:52 -07001195
1196 The generated make goal invokes Make twice, the first time it will
1197 build the default goal, and the second will invoke the 'qemu' goal.
Anas Nashif13773752018-07-06 18:20:23 -05001198 The output of the handler session will be monitored, and terminated
Andrew Boie6acbe632015-07-17 12:03:52 -07001199 either upon pass/fail result of the test program, or the timeout
1200 is reached.
1201
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001202 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001203 """
1204
Anas Nashif576be982017-12-23 20:20:27 -05001205 name = instance.name
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001206 directory = instance.test.test_path
Anas Nashif576be982017-12-23 20:20:27 -05001207 outdir = instance.outdir
1208
Andrew Boie6acbe632015-07-17 12:03:52 -07001209 build_logfile = os.path.join(outdir, "build.log")
1210 run_logfile = os.path.join(outdir, "run.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001211
Anas Nashif13773752018-07-06 18:20:23 -05001212 if not os.path.exists(outdir):
1213 os.makedirs(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001214
Anas Nashif13773752018-07-06 18:20:23 -05001215 handler = None
1216 if type == "qemu":
1217 handler = QEMUHandler(instance)
1218 elif type == "native":
Anas Nashifdf7ee612018-07-07 06:09:01 -05001219 handler = BinaryHandler(instance)
1220 handler.binary = os.path.join(outdir, "zephyr", "zephyr.exe")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001221 if options.enable_coverage:
1222 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001223 elif type == "nsim":
1224 handler = BinaryHandler(instance)
1225 handler.call_make_run = True
Anas Nashif13773752018-07-06 18:20:23 -05001226 elif type == "unit":
Anas Nashifdf7ee612018-07-07 06:09:01 -05001227 handler = BinaryHandler(instance)
1228 handler.binary = os.path.join(outdir, "testbinary")
Anas Nashifa4b5a732019-01-24 18:15:44 -05001229 if options.enable_coverage:
1230 args += ["EXTRA_LDFLAGS=--coverage"]
Anas Nashif13773752018-07-06 18:20:23 -05001231 elif type == "device":
1232 handler = DeviceHandler(instance)
Jan Kowalewski265895b2019-01-07 16:40:24 +01001233 elif type == "renode":
1234 handler = BinaryHandler(instance)
1235 handler.pid_fn = os.path.join(instance.outdir, "renode.pid")
1236 handler.call_make_run = True
Anas Nashif576be982017-12-23 20:20:27 -05001237
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001238
Anas Nashif13773752018-07-06 18:20:23 -05001239 if type == 'qemu':
1240 args.append("QEMU_PIPE=%s" % handler.get_fifo())
1241
Anas Nashifcc164222017-12-26 11:02:46 -05001242 text = (self._get_rule_header(name) +
1243 self._get_sub_make(name, "building", directory,
Anas Nashif13773752018-07-06 18:20:23 -05001244 outdir, build_logfile, args, make_args=make_args))
1245 if handler and handler.run:
1246 text += self._get_sub_make(name, "running", directory,
1247 outdir, run_logfile,
1248 args, make_args="run")
Anas Nashif4d25b502017-11-25 17:37:17 -05001249
Anas Nashif13773752018-07-06 18:20:23 -05001250 text += self._get_rule_footer(name)
Anas Nashif73440ea2018-02-19 10:57:03 -06001251
Anas Nashif73440ea2018-02-19 10:57:03 -06001252 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001253 run_logfile, handler.log if handler else None)
Anas Nashif73440ea2018-02-19 10:57:03 -06001254
Anas Nashif13773752018-07-06 18:20:23 -05001255
Anas Nashif37f9dc52018-02-23 08:53:46 -06001256 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001257 """Add a goal to build/test a TestInstance object
1258
1259 @param ti TestInstance object to build. The status dictionary returned
1260 by execute() will be keyed by its .name field.
1261 """
1262 args = ti.test.extra_args[:]
Anas Nashiff9e73c92019-02-12 13:59:08 -05001263
1264 # merge overlay files into one variable
1265 overlays = ""
1266 idx = 0
1267 for a in args:
1268 m = re.search('OVERLAY_CONFIG="(.*)"', a)
1269 if m:
1270 overlays += m.group(1)
1271 del args[idx]
1272 idx += 1
1273
Anas Nashif3cbffef2018-11-07 23:50:54 -05001274 if len(ti.test.extra_configs) > 0 or options.coverage:
Anas Nashiff9e73c92019-02-12 13:59:08 -05001275 args.append("OVERLAY_CONFIG=\"%s %s\"" %(overlays,
1276 os.path.join(ti.outdir, "overlay.conf")))
Anas Nashiffa695d22017-10-04 16:14:27 -04001277
Anas Nashif3cbffef2018-11-07 23:50:54 -05001278 if ti.test.type == "unit" and options.enable_coverage:
1279 args.append("COVERAGE=1")
1280
Anas Nashiffb91ad62017-10-31 08:33:17 -04001281 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001282 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001283
Anas Nashif37f9dc52018-02-23 08:53:46 -06001284 do_build_only = ti.build_only or options.build_only
Andy Rossdc4151f2019-01-03 14:17:43 -08001285 do_run = not do_build_only
1286 skip_slow = ti.test.slow and not options.enable_slow
Anas Nashif5df8cff2018-02-23 08:37:14 -06001287
Anas Nashif13773752018-07-06 18:20:23 -05001288 # FIXME: Need refactoring and cleanup
1289 type = None
Anas Nashif5df8cff2018-02-23 08:37:14 -06001290 if ti.platform.qemu_support and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001291 type = "qemu"
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001292 elif ti.test.type == "unit":
Anas Nashif13773752018-07-06 18:20:23 -05001293 type = "unit"
Anas Nashif5df8cff2018-02-23 08:37:14 -06001294 elif ti.platform.type == "native" and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001295 type = "native"
Anas Nashif99f5a6c2018-07-07 08:45:53 -05001296 elif ti.platform.simulation == "nsim" and do_run:
Anas Nashife24350c2018-07-11 15:09:22 -05001297 if find_executable("nsimdrv"):
1298 type = "nsim"
Jan Kowalewski265895b2019-01-07 16:40:24 +01001299 elif ti.platform.simulation == "renode" and do_run:
1300 if find_executable("renode"):
1301 type = "renode"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001302 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif13773752018-07-06 18:20:23 -05001303 type = "device"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001304
Andy Rossdc4151f2019-01-03 14:17:43 -08001305 if not skip_slow:
1306 self.add_goal(ti, type, args)
1307 else:
1308 verbose("Skipping slow test: " + ti.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001309
1310 def execute(self, callback_fn=None, context=None):
1311 """Execute all the registered build goals
1312
1313 @param callback_fn If not None, a callback function will be called
1314 as individual goals transition between states. This function
1315 should accept two parameters: a string state and an arbitrary
1316 context object, supplied here
1317 @param context Context object to pass to the callback function.
1318 Type and semantics are specific to that callback function.
1319 @return A dictionary mapping goal names to final status.
1320 """
1321
Andrew Boie08ce5a52016-02-22 13:28:10 -08001322 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001323 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001324 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001325 # Create our dynamic Makefile and execute it.
1326 # Watch stderr output which is where we will keep
1327 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -08001328 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001329 tf.write(goal.text)
1330 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
1331 tf.flush()
1332
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03001333 cmd = ["make", "-k", "-j", str(JOBS), "-f", tf.name, "all"]
Andy Rossdec163f2018-05-21 10:12:59 -07001334
Bobby Noelte9cf8b3c2018-06-13 06:10:20 +02001335 # assure language neutral environemnt
1336 make_env = os.environ.copy()
1337 make_env['LC_MESSAGES'] = 'C.UTF-8'
1338 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1339 stdout=devnull, env=make_env)
Andrew Boie6acbe632015-07-17 12:03:52 -07001340
1341 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001342 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001343 make_log.write(line)
1344 verbose("MAKE: " + repr(line.strip()))
1345 m = MakeGenerator.re_make.match(line)
1346 if not m:
1347 continue
1348
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001349 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001350 if error:
1351 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001352 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001353 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001354 # nonzero status.
1355 # Need to distinguish this case from a compilation failure.
Anas Nashif4d25b502017-11-25 17:37:17 -05001356 if goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001357 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001358 else:
1359 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -07001360 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001361 goal = self.goals[name]
1362 goal.make_state = state
1363
Andrew Boie6acbe632015-07-17 12:03:52 -07001364 if state == "finished":
Anas Nashif4d25b502017-11-25 17:37:17 -05001365 if goal.handler:
Anas Nashif576be982017-12-23 20:20:27 -05001366 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001367 goal.handler.handle()
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001368 goal.handler_log = goal.handler.log
Anas Nashifcc164222017-12-26 11:02:46 -05001369
Anas Nashif4d25b502017-11-25 17:37:17 -05001370 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001371 goal.metrics.update(metrics)
1372 if thread_status == "passed":
1373 goal.success()
1374 else:
1375 goal.fail(thread_status)
1376 else:
1377 goal.success()
1378
1379 if callback_fn:
1380 callback_fn(context, self.goals, goal)
1381
1382 p.wait()
1383 return self.goals
1384
1385
1386# "list" - List of strings
1387# "list:<type>" - List of <type>
1388# "set" - Set of unordered, unique strings
1389# "set:<type>" - Set of <type>
1390# "float" - Floating point
1391# "int" - Integer
1392# "bool" - Boolean
1393# "str" - String
1394
1395# XXX Be sure to update __doc__ if you change any of this!!
1396
Anas Nashif3ba1d432017-12-05 15:28:44 -05001397platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
Anas Nashif924a4e72018-10-18 12:25:55 -04001398 "supported_toolchains": {"type": "list", "default": []},
1399 "env": {"type": "list", "default": []}
1400 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001401
Anas Nashif3ba1d432017-12-05 15:28:44 -05001402testcase_valid_keys = {"tags": {"type": "set", "required": False},
1403 "type": {"type": "str", "default": "integration"},
1404 "extra_args": {"type": "list"},
1405 "extra_configs": {"type": "list"},
1406 "build_only": {"type": "bool", "default": False},
1407 "build_on_all": {"type": "bool", "default": False},
1408 "skip": {"type": "bool", "default": False},
1409 "slow": {"type": "bool", "default": False},
1410 "timeout": {"type": "int", "default": 60},
1411 "min_ram": {"type": "int", "default": 8},
1412 "depends_on": {"type": "set"},
1413 "min_flash": {"type": "int", "default": 32},
1414 "arch_whitelist": {"type": "set"},
1415 "arch_exclude": {"type": "set"},
1416 "extra_sections": {"type": "list", "default": []},
1417 "platform_exclude": {"type": "set"},
1418 "platform_whitelist": {"type": "set"},
1419 "toolchain_exclude": {"type": "set"},
1420 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001421 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001422 "harness": {"type": "str"},
Praful Swarnakarcf89e282018-09-13 02:58:28 +05301423 "harness_config": {"type": "map", "default": {}}
Anas Nashifab940162017-12-08 10:17:57 -05001424 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001425
1426
1427class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001428 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001429 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001430
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001431 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001432 """Instantiate a new SanityConfigParser object
1433
Anas Nashifa792a3d2017-04-04 18:47:49 -04001434 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001435 """
Anas Nashif255625b2017-12-05 15:08:26 -05001436 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001437 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001438 self.tests = {}
1439 self.common = {}
1440 if 'tests' in self.data:
1441 self.tests = self.data['tests']
1442 if 'common' in self.data:
1443 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001444
1445 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001446 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001447 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001448 if typestr == "str":
1449 return v
1450
1451 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001452 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001453
1454 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001455 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001456
1457 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001458 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001459
Anas Nashif3ba1d432017-12-05 15:28:44 -05001460 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001461 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001462 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001463 vs = v.split()
1464 if len(typestr) > 4 and typestr[4] == ":":
1465 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1466 else:
1467 return vs
1468
1469 elif typestr.startswith("set"):
1470 vs = v.split()
1471 if len(typestr) > 3 and typestr[3] == ":":
1472 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1473 else:
1474 return set(vs)
1475
Anas Nashif576be982017-12-23 20:20:27 -05001476 elif typestr.startswith("map"):
1477 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001478 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001479 raise ConfigurationError(
1480 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001481
Anas Nashifb4754ed2017-12-05 17:27:58 -05001482 def get_test(self, name, valid_keys):
1483 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001484
Anas Nashifb4754ed2017-12-05 17:27:58 -05001485 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001486 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001487 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001488 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001489 here, it will generate an error. Each value in this dictionary
1490 is another dictionary containing metadata:
1491
1492 "default" - Default value if not given
1493 "type" - Data type to convert the text value to. Simple types
1494 supported are "str", "float", "int", "bool" which will get
1495 converted to respective Python data types. "set" and "list"
1496 may also be specified which will split the value by
1497 whitespace (but keep the elements as strings). finally,
1498 "list:<type>" and "set:<type>" may be given which will
1499 perform a type conversion after splitting the value up.
1500 "required" - If true, raise an error if not defined. If false
1501 and "default" isn't specified, a type conversion will be
1502 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001503 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001504 type conversion and default values filled in per valid_keys
1505 """
1506
1507 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001508 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001509 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001510
Anas Nashifb4754ed2017-12-05 17:27:58 -05001511 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001512 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001513 raise ConfigurationError(
1514 self.filename,
1515 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001516 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001517
Anas Nashiffa695d22017-10-04 16:14:27 -04001518 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001519 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001520 d[k] += " " + v
1521 else:
1522 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001523
Andrew Boie08ce5a52016-02-22 13:28:10 -08001524 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001525 if k not in d:
1526 if "required" in kinfo:
1527 required = kinfo["required"]
1528 else:
1529 required = False
1530
1531 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001532 raise ConfigurationError(
1533 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001534 "missing required value for '%s' in test '%s'" %
1535 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001536 else:
1537 if "default" in kinfo:
1538 default = kinfo["default"]
1539 else:
1540 default = self._cast_value("", kinfo["type"])
1541 d[k] = default
1542 else:
1543 try:
1544 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001545 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001546 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001547 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1548 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001549
1550 return d
1551
1552
1553class Platform:
1554 """Class representing metadata for a particular platform
1555
Anas Nashifc7406082015-12-13 15:00:31 -05001556 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001557
1558 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001559 os.path.join(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001560 ZEPHYR_BASE,
Anas Nashif3ba1d432017-12-05 15:28:44 -05001561 "scripts",
1562 "sanity_chk",
1563 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001564
Anas Nashifa792a3d2017-04-04 18:47:49 -04001565 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001566 """Constructor.
1567
Anas Nashif877d3ca2017-12-05 17:39:29 -05001568 @param cfile Path to platform configuration file, which gives
1569 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001570 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001571 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001572 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001573
Anas Nashif255625b2017-12-05 15:08:26 -05001574 self.name = data['identifier']
Anas Nashiff3d48e12018-07-24 08:14:42 -05001575 self.sanitycheck = data.get("sanitycheck", True)
Anas Nashifa792a3d2017-04-04 18:47:49 -04001576 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001577 self.ram = data.get("ram", 128)
1578 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001579 self.ignore_tags = testing.get("ignore_tags", [])
1580 self.default = testing.get("default", False)
1581 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001582 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001583 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001584 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001585 for item in supp_feature.split(":"):
1586 self.supported.add(item)
1587
Anas Nashif8acdbd72018-01-04 14:15:22 -05001588 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001589 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001590 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001591 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001592 self.supported_toolchains = data.get("toolchain", [])
Anas Nashif924a4e72018-10-18 12:25:55 -04001593 self.env = data.get("env", [])
1594 self.env_satisfied = True
1595 for env in self.env:
1596 if os.environ.get(env, None) == None:
1597 self.env_satisfied = False
Andrew Boie41878222016-11-03 11:58:53 -07001598 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001599 pass
1600
Andrew Boie6acbe632015-07-17 12:03:52 -07001601 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001602 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001603
1604
1605class Architecture:
1606 """Class representing metadata for a particular architecture
1607 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001608
Anas Nashifa792a3d2017-04-04 18:47:49 -04001609 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001610 """Architecture constructor
1611
Anas Nashif877d3ca2017-12-05 17:39:29 -05001612 @param name String name for this architecture
1613 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001614 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001615 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001616
Anas Nashifa792a3d2017-04-04 18:47:49 -04001617 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001618
1619 def __repr__(self):
1620 return "<arch %s>" % self.name
1621
1622
1623class TestCase:
1624 """Class representing a test application
1625 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001626
Anas Nashif7fae29c2017-10-09 13:19:12 -04001627 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001628 """TestCase constructor.
1629
Anas Nashif877d3ca2017-12-05 17:39:29 -05001630 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001631 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001632 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001633
Andrew Boie6acbe632015-07-17 12:03:52 -07001634 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001635 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001636 the test case is <workdir>/<name>.
1637
1638 @param testcase_root Absolute path to the root directory where
1639 all the test cases live
1640 @param workdir Relative path to the project directory for this
1641 test application from the test_case root.
Anas Nashif877d3ca2017-12-05 17:39:29 -05001642 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001643 in the test case configuration file. For many test cases that just
1644 define one test, can be anything and is usually "test". This is
1645 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001646 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001647 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001648 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001649 """
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001650 self.test_path = os.path.join(testcase_root, workdir)
1651
Anas Nashifaae71d72018-04-21 22:26:48 -05001652 self.id = name
1653 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001654 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001655 self.tags = tc_dict["tags"]
1656 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001657 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001658 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001659 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001660 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001661 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001662 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001663 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1664 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001665 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001666 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001667 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001668 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001669 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001670 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001671 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001672 self.min_ram = tc_dict["min_ram"]
1673 self.depends_on = tc_dict["depends_on"]
1674 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001675 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001676
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001677 self.name = self.get_unique(testcase_root, workdir, name)
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001678
Anas Nashif2cf0df02015-10-10 09:29:43 -04001679 self.defconfig = {}
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001680 self.dt_config = {}
Anas Nashif45a97862019-01-09 08:46:42 -05001681 self.cmake_cache = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001682 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001683
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001684
1685 def get_unique(self, testcase_root, workdir, name):
1686
1687 if ZEPHYR_BASE in testcase_root:
1688 # This is a Zephyr Test, so include path in name for uniqueness
1689 # FIXME: We should not depend on path of test for unique names.
1690
1691 zephyr_base = os.path.join(os.path.realpath(ZEPHYR_BASE))
1692 short_path = os.path.normpath(testcase_root.replace(zephyr_base + "/", ""))
1693 else:
1694 short_path = ""
1695
1696 unique = os.path.normpath(os.path.join(short_path, workdir, name))
1697 return unique
1698
Anas Nashifaae71d72018-04-21 22:26:48 -05001699 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001700 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001701 # do not match until end-of-line, otherwise we won't allow
1702 # stc_regex below to catch the ones that are declared in the same
1703 # line--as we only search starting the end of this match
1704 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001705 re.MULTILINE)
1706 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001707 br"^\s*" # empy space at the beginning is ok
1708 # catch the case where it is declared in the same sentence, e.g:
1709 #
1710 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1711 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1712 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1713 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1714 # Consume the argument that becomes the extra testcse
1715 br"\(\s*"
1716 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1717 # _setup_teardown() variant has two extra arguments that we ignore
1718 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1719 br"\s*\)",
1720 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001721 re.MULTILINE)
1722 suite_run_regex = re.compile(
1723 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1724 re.MULTILINE)
1725 achtung_regex = re.compile(
1726 br"(#ifdef|#endif)",
1727 re.MULTILINE)
1728 warnings = None
1729
1730 with open(inf_name) as inf:
1731 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1732 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001733 suite_regex_match = suite_regex.search(main_c)
1734 if not suite_regex_match:
1735 # can't find ztest_test_suite, maybe a client, because
1736 # it includes ztest.h
1737 return None, None
1738
1739 suite_run_match = suite_run_regex.search(main_c)
1740 if not suite_run_match:
1741 raise ValueError("can't find ztest_run_test_suite")
1742
1743 achtung_matches = re.findall(
1744 achtung_regex,
1745 main_c[suite_regex_match.end():suite_run_match.start()])
1746 if achtung_matches:
1747 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001748 % ", ".join(set([
1749 match.decode() for match in achtung_matches
1750 ]))
1751 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001752 stc_regex,
1753 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001754 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001755 return matches, warnings
1756
1757 def scan_path(self, path):
1758 subcases = []
1759 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1760 try:
1761 _subcases, warnings = self.scan_file(filename)
1762 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001763 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001764 if _subcases:
1765 subcases += _subcases
1766 except ValueError as e:
1767 error("%s: can't find: %s", filename, e)
1768 return subcases
1769
1770
1771 def parse_subcases(self):
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001772 results = self.scan_path(self.test_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001773 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001774 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001775 self.cases.append(name)
1776
1777
Anas Nashif75547e22018-02-24 08:32:14 -06001778 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001779 return self.name
1780
1781
Andrew Boie6acbe632015-07-17 12:03:52 -07001782class TestInstance:
1783 """Class representing the execution of a particular TestCase on a platform
1784
1785 @param test The TestCase object we want to build/execute
1786 @param platform Platform object that we want to build and run against
1787 @param base_outdir Base directory for all test results. The actual
1788 out directory used is <outdir>/<platform>/<test case name>
1789 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001790
Anas Nashif37f9dc52018-02-23 08:53:46 -06001791 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001792 self.test = test
1793 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001794 self.name = os.path.join(platform.name, test.name)
Anas Nashiff18ad9d2018-11-20 09:03:17 -05001795 self.outdir = os.path.join(base_outdir, platform.name, test.name)
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001796
1797 self.build_only = options.build_only or test.build_only \
1798 or self.check_dependency()
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001799 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001800
Anas Nashif7dd19ea2018-11-14 08:46:49 -05001801 def check_dependency(self):
1802 build_only = False
1803 if self.test.harness == 'console':
1804 if "fixture" in self.test.harness_config:
1805 fixture = self.test.harness_config['fixture']
1806 if fixture not in options.fixture:
1807 build_only = True
1808 elif self.test.harness:
1809 build_only = True
1810
1811 return build_only
1812
Anas Nashifdbd76492018-11-23 20:24:19 -05001813 def create_overlay(self, platform):
Anas Nashif3cbffef2018-11-07 23:50:54 -05001814 file = os.path.join(self.outdir, "overlay.conf")
1815 os.makedirs(self.outdir, exist_ok=True)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001816 with open(file, "w") as f:
1817 content = ""
Anas Nashif3cbffef2018-11-07 23:50:54 -05001818
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001819 if len(self.test.extra_configs) > 0:
1820 content = "\n".join(self.test.extra_configs)
Anas Nashif3cbffef2018-11-07 23:50:54 -05001821
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001822 if options.enable_coverage:
1823 if platform in options.coverage_platform:
1824 content = content + "\nCONFIG_COVERAGE=y"
1825
1826 f.write(content)
Anas Nashiffa695d22017-10-04 16:14:27 -04001827
Andrew Boie6acbe632015-07-17 12:03:52 -07001828 def calculate_sizes(self):
1829 """Get the RAM/ROM sizes of a test case.
1830
1831 This can only be run after the instance has been executed by
1832 MakeGenerator, otherwise there won't be any binaries to measure.
1833
1834 @return A SizeCalculator object
1835 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001836 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001837 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001838 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001839 if (len(fns) != 1):
1840 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001841 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001842
1843 def __repr__(self):
1844 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1845
1846
Andrew Boie4ef16c52015-08-28 12:36:03 -07001847def defconfig_cb(context, goals, goal):
1848 if not goal.failed:
1849 return
1850
1851 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001852 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001853 if INLINE_LOGS:
1854 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001855 data = fp.read()
1856 sys.stdout.write(data)
1857 if log_file:
1858 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001859 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001860 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001861
Andrew Boie6acbe632015-07-17 12:03:52 -07001862
1863class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001864 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Anas Nashif1c65b6b2018-12-02 19:12:21 -05001865 dt_re = re.compile('([A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001866
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001867 yaml_tc_schema = scl.yaml_load(
Oleg Zhurakivskyy42822082018-08-17 14:54:41 +03001868 os.path.join(ZEPHYR_BASE,
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001869 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001870
Anas Nashif37f9dc52018-02-23 08:53:46 -06001871 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001872 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001873 self.arches = {}
1874 self.testcases = {}
1875 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001876 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001877 self.instances = {}
1878 self.goals = None
1879 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001880 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001881
Andrew Boie3d348712016-04-08 11:52:13 -07001882 for testcase_root in testcase_roots:
1883 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001884
Andrew Boie3d348712016-04-08 11:52:13 -07001885 debug("Reading test case configuration files under %s..." %
1886 testcase_root)
1887 for dirpath, dirnames, filenames in os.walk(testcase_root,
1888 topdown=True):
1889 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001890 if 'sample.yaml' in filenames:
1891 filename = 'sample.yaml'
1892 elif 'testcase.yaml' in filenames:
1893 filename = 'testcase.yaml'
1894 else:
1895 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001896
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001897 verbose("Found possible test case in " + dirpath)
1898 dirnames[:] = []
1899 yaml_path = os.path.join(dirpath, filename)
1900 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001901 parsed_data = SanityConfigParser(
1902 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001903
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001904 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001905
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001906 for name in parsed_data.tests.keys():
1907 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1908 tc = TestCase(testcase_root, workdir, name, tc_dict,
1909 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001910 tc.parse_subcases()
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001911 self.testcases[tc.name] = tc
1912
1913 except Exception as e:
1914 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001915 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001916
Andrew Boie6acbe632015-07-17 12:03:52 -07001917
Anas Nashif86c8e232017-10-09 13:42:28 -04001918 for board_root in board_root_list:
1919 board_root = os.path.abspath(board_root)
1920
Anas Nashif3ba1d432017-12-05 15:28:44 -05001921 debug(
1922 "Reading platform configuration files under %s..." %
1923 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001924 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1925 verbose("Found plaform configuration " + fn)
1926 try:
1927 platform = Platform(fn)
Anas Nashiff3d48e12018-07-24 08:14:42 -05001928 if platform.sanitycheck:
1929 self.platforms.append(platform)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001930 except RuntimeError as e:
1931 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001932 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001933
Anas Nashifa792a3d2017-04-04 18:47:49 -04001934 arches = []
1935 for p in self.platforms:
1936 arches.append(p.arch)
1937 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001938 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001939 arch = Architecture(a, aplatforms)
1940 self.arches[a] = arch
1941
Andrew Boie6acbe632015-07-17 12:03:52 -07001942 self.instances = {}
1943
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001944 def get_platform(self, name):
1945 selected_platform = None
1946 for platform in self.platforms:
1947 if platform.name == name:
1948 selected_platform = platform
1949 break
1950 return selected_platform
Anas Nashifb4bdd662018-08-15 17:12:28 -05001951
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001952 def get_last_failed(self):
Anas Nashifb4bdd662018-08-15 17:12:28 -05001953 try:
1954 if not os.path.exists(LAST_SANITY):
1955 raise SanityRuntimeError("Couldn't find last sanity run.")
1956 except Exception as e:
1957 print(str(e))
1958 sys.exit(2)
1959
Andrew Boie6acbe632015-07-17 12:03:52 -07001960 result = []
1961 with open(LAST_SANITY, "r") as fp:
1962 cr = csv.DictReader(fp)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001963 instance_list = []
Andrew Boie6acbe632015-07-17 12:03:52 -07001964 for row in cr:
1965 if row["passed"] == "True":
1966 continue
1967 test = row["test"]
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001968 platform = self.get_platform(row["platform"])
1969 instance = TestInstance(self.testcases[test], platform, self.outdir)
1970 instance.create_overlay(platform.name)
1971 instance_list.append(instance)
1972 self.add_instances(instance_list)
Andrew Boie6acbe632015-07-17 12:03:52 -07001973
Anas Nashifbd166f42017-09-02 12:32:08 -04001974 def load_from_file(self, file):
Anas Nashifb4bdd662018-08-15 17:12:28 -05001975 try:
1976 if not os.path.exists(file):
1977 raise SanityRuntimeError(
1978 "Couldn't find input file with list of tests.")
1979 except Exception as e:
1980 print(str(e))
1981 sys.exit(2)
1982
Anas Nashifbd166f42017-09-02 12:32:08 -04001983 with open(file, "r") as fp:
1984 cr = csv.reader(fp)
1985 instance_list = []
1986 for row in cr:
1987 name = os.path.join(row[0], row[1])
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05001988 platform = self.get_platform(row[2])
1989 instance = TestInstance(self.testcases[name], platform, self.outdir)
1990 instance.create_overlay(platform.name)
Anas Nashifbd166f42017-09-02 12:32:08 -04001991 instance_list.append(instance)
1992 self.add_instances(instance_list)
1993
Anas Nashif4f028882017-12-30 11:48:43 -05001994 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04001995
Anas Nashif7fe35cf2018-02-15 07:20:18 -06001996 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
1997 os.environ.get("ZEPHYR_GCC_VARIANT", None)
Anas Nashifb4bdd662018-08-15 17:12:28 -05001998
Sebastian Bøe5681f872018-10-12 16:03:49 +02001999 if toolchain == "gccarmemb":
2000 # Remove this translation when gccarmemb is no longer supported.
2001 toolchain = "gnuarmemb"
Anas Nashifb4bdd662018-08-15 17:12:28 -05002002
2003 try:
2004 if not toolchain:
2005 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
2006 except Exception as e:
2007 print(str(e))
2008 sys.exit(2)
Anas Nashif7fe35cf2018-02-15 07:20:18 -06002009
2010
Andrew Boie6acbe632015-07-17 12:03:52 -07002011 instances = []
2012 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05002013 platform_filter = options.platform
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002014 testcase_filter = run_individual_tests
Anas Nashif4f028882017-12-30 11:48:43 -05002015 arch_filter = options.arch
2016 tag_filter = options.tag
2017 exclude_tag = options.exclude_tag
2018 config_filter = options.config
2019 extra_args = options.extra_args
2020 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04002021
Andrew Boie6acbe632015-07-17 12:03:52 -07002022 verbose("platform filter: " + str(platform_filter))
2023 verbose(" arch_filter: " + str(arch_filter))
2024 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04002025 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07002026 verbose(" config_filter: " + str(config_filter))
2027
Andrew Boie821d8322016-03-22 10:08:35 -07002028 default_platforms = False
2029
2030 if all_plats:
2031 info("Selecting all possible platforms per test case")
2032 # When --all used, any --platform arguments ignored
2033 platform_filter = []
2034 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07002035 info("Selecting default platforms per test case")
2036 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07002037
Sebastian Bøe781e3982017-11-09 11:43:33 +01002038 mg = MakeGenerator(self.outdir)
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002039 defconfig_list = {}
2040 dt_list = {}
Anas Nashif45a97862019-01-09 08:46:42 -05002041 cmake_list = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08002042 for tc_name, tc in self.testcases.items():
2043 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04002044 for plat in arch.platforms:
2045 instance = TestInstance(tc, plat, self.outdir)
2046
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002047 if (arch_name == "unit") != (tc.type == "unit"):
2048 continue
2049
Anas Nashifbfab06b2017-06-22 09:22:24 -04002050 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002051 platform_filter = []
2052
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002053 if tc.skip:
2054 continue
2055
Anas Nashif2cf0df02015-10-10 09:29:43 -04002056 if tag_filter and not tc.tags.intersection(tag_filter):
2057 continue
2058
Anas Nashifdfa86e22016-10-24 17:08:56 -04002059 if exclude_tag and tc.tags.intersection(exclude_tag):
2060 continue
2061
Anas Nashif2cf0df02015-10-10 09:29:43 -04002062 if testcase_filter and tc_name not in testcase_filter:
2063 continue
2064
Anas Nashif2cf0df02015-10-10 09:29:43 -04002065 if arch_filter and arch_name not in arch_filter:
2066 continue
2067
2068 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2069 continue
2070
2071 if tc.arch_exclude and arch.name in tc.arch_exclude:
2072 continue
2073
2074 if tc.platform_exclude and plat.name in tc.platform_exclude:
2075 continue
2076
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002077 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2078 continue
2079
Anas Nashif2cf0df02015-10-10 09:29:43 -04002080 if platform_filter and plat.name not in platform_filter:
2081 continue
2082
Anas Nashif62224182017-08-09 23:55:53 -04002083 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002084 continue
2085
2086 if set(plat.ignore_tags) & tc.tags:
2087 continue
2088
Kumar Gala5141d522017-07-07 08:05:48 -05002089 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002090 dep_intersection = tc.depends_on.intersection(
2091 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002092 if dep_intersection != set(tc.depends_on):
2093 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002094
2095 if plat.flash < tc.min_flash:
2096 continue
2097
Anas Nashif2cf0df02015-10-10 09:29:43 -04002098 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2099 continue
2100
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002101 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2102 continue
2103
Anas Nashif924a4e72018-10-18 12:25:55 -04002104 if (plat.env_satisfied and tc.tc_filter
2105 and (plat.default or all_plats or platform_filter)
Anas Nashif07d54c02018-07-21 19:29:08 -05002106 and (toolchain in plat.supported_toolchains or options.force_toolchain)):
Anas Nashif8ea9d022015-11-10 12:24:20 -05002107 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04002108 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07002109 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002110 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07002111 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07002112 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05002113 # each other since they all try to build them
2114 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04002115
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002116 o = os.path.join(self.outdir, plat.name, tc.name)
Anas Nashif45a97862019-01-09 08:46:42 -05002117 cmake_cache_path = os.path.join(o, "CMakeCache.txt")
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002118 generated_dt_confg = "include/generated/generated_dts_board.conf"
2119 dt_config_path = os.path.join(o, "zephyr", generated_dt_confg)
2120 dt_list[tc, plat, tc.name.split("/")[-1]] = dt_config_path
Anas Nashif45a97862019-01-09 08:46:42 -05002121 cmake_list[tc, plat, tc.name.split("/")[-1]] = cmake_cache_path
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002122 defconfig_list[tc, plat, tc.name.split("/")[-1]] = os.path.join(o, "zephyr", ".config")
Anas Nashif13773752018-07-06 18:20:23 -05002123 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
Anas Nashiff18ad9d2018-11-20 09:03:17 -05002124 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.test_path),
Anas Nashif13773752018-07-06 18:20:23 -05002125 o, args, "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04002126
2127 info("Building testcase defconfigs...")
2128 results = mg.execute(defconfig_cb)
2129
Andrew Boie08ce5a52016-02-22 13:28:10 -08002130 for name, goal in results.items():
Anas Nashifb4bdd662018-08-15 17:12:28 -05002131 try:
2132 if goal.failed:
2133 raise SanityRuntimeError("Couldn't build some defconfigs")
2134 except Exception as e:
2135 error(str(e))
2136 sys.exit(2)
2137
Anas Nashif2cf0df02015-10-10 09:29:43 -04002138
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002139 for k, out_config in defconfig_list.items():
Andrew Boie41878222016-11-03 11:58:53 -07002140 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04002141 defconfig = {}
2142 with open(out_config, "r") as fp:
2143 for line in fp.readlines():
2144 m = TestSuite.config_re.match(line)
2145 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07002146 if line.strip() and not line.startswith("#"):
2147 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002148 continue
2149 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07002150 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04002151
Anas Nashif45a97862019-01-09 08:46:42 -05002152 for k, cache_file in cmake_list.items():
2153 if not os.path.exists(out_config):
2154 continue
2155
2156 test, plat, name = k
2157 cmake_conf = {}
2158 try:
2159 cache = CMakeCache.from_file(cache_file)
2160 except FileNotFoundError:
2161 cache = {}
2162
2163 for k in iter(cache):
2164 cmake_conf[k.name] = k.value
2165
2166 test.cmake_cache[plat] = cmake_conf
2167
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002168 for k, out_config in dt_list.items():
2169 if not os.path.exists(out_config):
2170 continue
2171
2172 test, plat, name = k
2173 dt_conf = {}
2174 with open(out_config, "r") as fp:
2175 for line in fp.readlines():
2176 m = TestSuite.dt_re.match(line)
2177 if not m:
2178 if line.strip() and not line.startswith("#"):
2179 sys.stderr.write("Unrecognized line %s\n" % line)
2180 continue
2181 dt_conf[m.group(1)] = m.group(2).strip()
2182 test.dt_config[plat] = dt_conf
2183
Andrew Boie08ce5a52016-02-22 13:28:10 -08002184 for tc_name, tc in self.testcases.items():
2185 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002186 instance_list = []
2187 for plat in arch.platforms:
2188 instance = TestInstance(tc, plat, self.outdir)
2189
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002190 if (arch_name == "unit") != (tc.type == "unit"):
2191 # Discard silently
2192 continue
2193
Anas Nashif2bd99bc2015-10-12 13:10:57 -04002194 if tc.skip:
2195 discards[instance] = "Skip filter"
2196 continue
2197
Anas Nashifbfab06b2017-06-22 09:22:24 -04002198 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002199 platform_filter = []
2200
Andrew Boie6acbe632015-07-17 12:03:52 -07002201 if tag_filter and not tc.tags.intersection(tag_filter):
2202 discards[instance] = "Command line testcase tag filter"
2203 continue
2204
Anas Nashifdfa86e22016-10-24 17:08:56 -04002205 if exclude_tag and tc.tags.intersection(exclude_tag):
2206 discards[instance] = "Command line testcase exclude filter"
2207 continue
2208
Andrew Boie6acbe632015-07-17 12:03:52 -07002209 if testcase_filter and tc_name not in testcase_filter:
2210 discards[instance] = "Testcase name filter"
2211 continue
2212
Andrew Boie6acbe632015-07-17 12:03:52 -07002213 if arch_filter and arch_name not in arch_filter:
2214 discards[instance] = "Command line testcase arch filter"
2215 continue
2216
2217 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
2218 discards[instance] = "Not in test case arch whitelist"
2219 continue
2220
Anas Nashif30d13872015-10-05 10:02:45 -04002221 if tc.arch_exclude and arch.name in tc.arch_exclude:
2222 discards[instance] = "In test case arch exclude"
2223 continue
2224
2225 if tc.platform_exclude and plat.name in tc.platform_exclude:
2226 discards[instance] = "In test case platform exclude"
2227 continue
2228
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002229 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
2230 discards[instance] = "In test case toolchain exclude"
2231 continue
2232
Andrew Boie6acbe632015-07-17 12:03:52 -07002233 if platform_filter and plat.name not in platform_filter:
2234 discards[instance] = "Command line platform filter"
2235 continue
2236
2237 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
2238 discards[instance] = "Not in testcase platform whitelist"
2239 continue
2240
Anas Nashifb17e1ca2017-06-27 18:05:30 -04002241 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
2242 discards[instance] = "Not in testcase toolchain whitelist"
2243 continue
2244
Anas Nashif924a4e72018-10-18 12:25:55 -04002245 if not plat.env_satisfied:
2246 discards[instance] = "Environment ({}) not satisfied".format(", ".join(plat.env))
2247 continue
2248
2249 if not options.force_toolchain \
2250 and toolchain and (toolchain not in plat.supported_toolchains) \
2251 and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05002252 discards[instance] = "Not supported by the toolchain"
2253 continue
2254
Anas Nashif62224182017-08-09 23:55:53 -04002255 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002256 discards[instance] = "Not enough RAM"
2257 continue
2258
Kumar Gala5141d522017-07-07 08:05:48 -05002259 if tc.depends_on:
Anas Nashif07d54c02018-07-21 19:29:08 -05002260 dep_intersection = tc.depends_on.intersection(set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05002261 if dep_intersection != set(tc.depends_on):
2262 discards[instance] = "No hardware support"
2263 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04002264
Anas Nashif62224182017-08-09 23:55:53 -04002265 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04002266 discards[instance] = "Not enough FLASH"
2267 continue
2268
2269 if set(plat.ignore_tags) & tc.tags:
2270 discards[instance] = "Excluded tags per platform"
2271 continue
2272
Anas Nashif674bb282018-01-09 09:12:15 -05002273 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05002274 "ARCH": arch.name,
2275 "PLATFORM": plat.name
2276 }
Javier B Perez79414542016-08-08 12:24:59 -05002277 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002278 for p, tdefconfig in tc.defconfig.items():
2279 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002280 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002281 break
2282
Anas Nashif1c65b6b2018-12-02 19:12:21 -05002283 for p, tdefconfig in tc.dt_config.items():
2284 if p == plat:
2285 defconfig.update(tdefconfig)
2286 break
2287
Anas Nashif45a97862019-01-09 08:46:42 -05002288 for p, tdefconfig in tc.cmake_cache.items():
2289 if p == plat:
2290 defconfig.update(tdefconfig)
2291 break
2292
Andrew Boie3ea78922016-03-24 14:46:00 -07002293 if tc.tc_filter:
2294 try:
2295 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002296 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002297 sys.stderr.write(
2298 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002299 raise se
2300 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002301 discards[instance] = (
2302 "defconfig doesn't satisfy expression '%s'" %
2303 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002304 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002305
Andrew Boie6acbe632015-07-17 12:03:52 -07002306 instance_list.append(instance)
2307
2308 if not instance_list:
2309 # Every platform in this arch was rejected already
2310 continue
2311
Anas Nashifa792a3d2017-04-04 18:47:49 -04002312 if default_platforms and not tc.build_on_all:
2313 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002314 instances = list(
2315 filter(
2316 lambda tc: tc.platform.default,
2317 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002318 self.add_instances(instances)
2319 else:
Anas Nashifab747062017-12-05 17:59:01 -05002320 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002321
Anas Nashif3ba1d432017-12-05 15:28:44 -05002322 for instance in list(
2323 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002324 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002325 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002326 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002327
2328 for name, case in self.instances.items():
Anas Nashifdbd76492018-11-23 20:24:19 -05002329 case.create_overlay(case.platform.name)
Anas Nashifab351f42018-04-08 08:57:48 -05002330
Andrew Boie6acbe632015-07-17 12:03:52 -07002331 self.discards = discards
2332 return discards
2333
Andrew Boie821d8322016-03-22 10:08:35 -07002334 def add_instances(self, ti_list):
2335 for ti in ti_list:
2336 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002337
Anas Nashif37f9dc52018-02-23 08:53:46 -06002338 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002339
2340 def calc_one_elf_size(name, goal):
2341 if not goal.failed:
Alberto Escolar Piedrasb1045fe2018-07-14 13:11:02 +02002342 if self.instances[name].platform.type != "native":
2343 i = self.instances[name]
2344 sc = i.calculate_sizes()
2345 goal.metrics["ram_size"] = sc.get_ram_size()
2346 goal.metrics["rom_size"] = sc.get_rom_size()
2347 goal.metrics["unrecognized"] = sc.unrecognized_sections()
2348 else:
2349 goal.metrics["ram_size"] = 0
2350 goal.metrics["rom_size"] = 0
2351 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002352
Anas Nashif37f9dc52018-02-23 08:53:46 -06002353 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002354 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002355 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002356 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002357
Andy Ross9c9162d2019-01-03 10:50:53 -08002358 if not options.disable_size_report:
2359 # Parallelize size calculation
2360 executor = concurrent.futures.ThreadPoolExecutor(JOBS)
2361 futures = [executor.submit(calc_one_elf_size, name, goal)
2362 for name, goal in self.goals.items()]
2363 concurrent.futures.wait(futures)
2364 else:
2365 for goal in self.goals.values():
2366 goal.metrics["ram_size"] = 0
2367 goal.metrics["rom_size"] = 0
2368 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07002369
Andrew Boie6acbe632015-07-17 12:03:52 -07002370 return self.goals
2371
Anas Nashifbd166f42017-09-02 12:32:08 -04002372 def run_report(self, filename):
2373 with open(filename, "at") as csvfile:
2374 fieldnames = ['path', 'test', 'platform', 'arch']
2375 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2376 for instance in self.instances.values():
2377 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002378 "path": os.path.dirname(instance.test.name),
2379 "test": os.path.basename(instance.test.name),
2380 "platform": instance.platform.name,
2381 "arch": instance.platform.arch
2382 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002383 cw.writerow(rowdict)
2384
Andrew Boie6acbe632015-07-17 12:03:52 -07002385 def discard_report(self, filename):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002386
2387 try:
2388 if self.discards is None:
2389 raise SanityRuntimeError("apply_filters() hasn't been run!")
2390 except Exception as e:
2391 error(str(e))
2392 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002393
Anas Nashifbd166f42017-09-02 12:32:08 -04002394 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002395 fieldnames = ["test", "arch", "platform", "reason"]
2396 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2397 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002398 for instance, reason in self.discards.items():
Anas Nashif3ba1d432017-12-05 15:28:44 -05002399 rowdict = {"test": instance.test.name,
2400 "arch": instance.platform.arch,
2401 "platform": instance.platform.name,
2402 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002403 cw.writerow(rowdict)
2404
2405 def compare_metrics(self, filename):
2406 # name, datatype, lower results better
2407 interesting_metrics = [("ram_size", int, True),
2408 ("rom_size", int, True)]
2409
Anas Nashifb4bdd662018-08-15 17:12:28 -05002410 try:
2411 if self.goals is None:
2412 raise SanityRuntimeError("execute() hasn't been run!")
2413 except Exception as e:
2414 print(str(e))
2415 sys.exit(2)
Andrew Boie6acbe632015-07-17 12:03:52 -07002416
2417 if not os.path.exists(filename):
2418 info("Cannot compare metrics, %s not found" % filename)
2419 return []
2420
2421 results = []
2422 saved_metrics = {}
2423 with open(filename) as fp:
2424 cr = csv.DictReader(fp)
2425 for row in cr:
2426 d = {}
2427 for m, _, _ in interesting_metrics:
2428 d[m] = row[m]
2429 saved_metrics[(row["test"], row["platform"])] = d
2430
Andrew Boie08ce5a52016-02-22 13:28:10 -08002431 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002432 i = self.instances[name]
2433 mkey = (i.test.name, i.platform.name)
2434 if mkey not in saved_metrics:
2435 continue
2436 sm = saved_metrics[mkey]
2437 for metric, mtype, lower_better in interesting_metrics:
2438 if metric not in goal.metrics:
2439 continue
2440 if sm[metric] == "":
2441 continue
2442 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002443 if delta == 0:
2444 continue
2445 results.append((i, metric, goal.metrics[metric], delta,
2446 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002447 return results
2448
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002449
2450
2451 def encode_for_xml(self, unicode_data, encoding='ascii'):
2452 unicode_data = unicode_data.replace('\x00', '')
2453 return unicode_data
2454
2455 def testcase_target_report(self, report_file):
2456
2457 run = "Sanitycheck"
2458 eleTestsuite = None
2459 append = options.only_failed
2460
2461 errors = 0
2462 passes = 0
2463 fails = 0
2464 duration = 0
2465 skips = 0
2466
2467 for identifier, ti in self.instances.items():
2468 for k in ti.results.keys():
2469 if ti.results[k] == 'PASS':
2470 passes += 1
2471 elif ti.results[k] == 'BLOCK':
2472 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002473 elif ti.results[k] == 'SKIP':
2474 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002475 else:
2476 fails += 1
2477
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002478 eleTestsuites = ET.Element('testsuites')
2479 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2480 name=run, time="%d" % duration,
2481 tests="%d" % (errors + passes + fails),
2482 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002483 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002484
2485 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002486
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002487 # print out test results
2488 for identifier, ti in self.instances.items():
2489 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002490
2491 eleTestcase = ET.SubElement(
2492 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002493 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002494 if ti.results[k] in ['FAIL', 'BLOCK']:
2495 el = None
2496
2497 if ti.results[k] == 'FAIL':
2498 el = ET.SubElement(
2499 eleTestcase,
2500 'failure',
2501 type="failure",
2502 message="failed")
2503 elif ti.results[k] == 'BLOCK':
2504 el = ET.SubElement(
2505 eleTestcase,
2506 'error',
2507 type="failure",
2508 message="failed")
2509 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
2510 bl = os.path.join(p, "handler.log")
2511
2512 if os.path.exists(bl):
2513 with open(bl, "rb") as f:
2514 log = f.read().decode("utf-8")
2515 el.text = self.encode_for_xml(log)
2516
Anas Nashif61e21632018-04-08 13:30:16 -05002517 elif ti.results[k] == 'SKIP':
2518 el = ET.SubElement(
2519 eleTestcase,
Anas Nashif2c7636b2018-09-02 13:11:19 -04002520 'skipped',
2521 type="skipped",
2522 message="Skipped")
Anas Nashif61e21632018-04-08 13:30:16 -05002523
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002524 result = ET.tostring(eleTestsuites)
2525 f = open(report_file, 'wb')
2526 f.write(result)
2527 f.close()
2528
2529
Anas Nashif4f028882017-12-30 11:48:43 -05002530 def testcase_xunit_report(self, filename, duration):
Anas Nashifb4bdd662018-08-15 17:12:28 -05002531 try:
2532 if self.goals is None:
2533 raise SanityRuntimeError("execute() hasn't been run!")
2534 except Exception as e:
2535 print(str(e))
2536 sys.exit(2)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002537
2538 fails = 0
2539 passes = 0
2540 errors = 0
2541
2542 for name, goal in self.goals.items():
2543 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002544 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002545 errors += 1
2546 else:
2547 fails += 1
2548 else:
2549 passes += 1
2550
2551 run = "Sanitycheck"
2552 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002553 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002554
Anas Nashif0605fa32017-05-07 08:51:02 -04002555 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002556 tree = ET.parse(filename)
2557 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002558 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002559 else:
2560 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002561 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2562 name=run, time="%d" % duration,
2563 tests="%d" % (errors + passes + fails),
2564 failures="%d" % fails,
2565 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002566
Anas Nashifc8390f12017-11-25 17:14:12 -05002567 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002568 for name, goal in self.goals.items():
2569
2570 i = self.instances[name]
2571 if append:
2572 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002573 if tc.get('classname') == "%s:%s" % (
2574 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002575 eleTestsuite.remove(tc)
2576
Anas Nashif4d25b502017-11-25 17:37:17 -05002577 if not goal.failed and goal.handler:
2578 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002579
Anas Nashif3ba1d432017-12-05 15:28:44 -05002580 eleTestcase = ET.SubElement(
2581 eleTestsuite, 'testcase', classname="%s:%s" %
2582 (i.platform.name, i.test.name), name="%s" %
2583 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002584 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002585 failure = ET.SubElement(
2586 eleTestcase,
2587 'failure',
2588 type="failure",
2589 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002590 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002591 bl = os.path.join(p, "build.log")
Anas Nashifc96c90a2019-02-05 07:38:32 -05002592 hl = os.path.join(p, "handler.log")
2593 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002594 if goal.reason != 'build_error':
Anas Nashifc96c90a2019-02-05 07:38:32 -05002595 if os.path.exists(hl):
2596 log_file = hl
2597 else:
2598 log_file = bl
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002599
Anas Nashifc96c90a2019-02-05 07:38:32 -05002600 if os.path.exists(log_file):
2601 with open(log_file, "rb") as f:
Anas Nashif712d3452017-12-29 22:09:03 -05002602 log = f.read().decode("utf-8")
Anas Nashifa4c368e2018-10-15 09:45:59 -04002603 filtered_string = ''.join(filter(lambda x: x in string.printable, log))
2604 failure.text = filtered_string
Anas Nashifba4643b2018-09-23 09:41:59 -05002605 f.close()
Anas Nashifb3311ed2017-04-13 14:44:48 -04002606
2607 result = ET.tostring(eleTestsuites)
2608 f = open(filename, 'wb')
2609 f.write(result)
2610 f.close()
2611
Andrew Boie6acbe632015-07-17 12:03:52 -07002612 def testcase_report(self, filename):
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)
Andrew Boie6acbe632015-07-17 12:03:52 -07002619
Andrew Boie08ce5a52016-02-22 13:28:10 -08002620 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002621 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002622 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002623 "rom_size"]
2624 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2625 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002626 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002627 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002628 rowdict = {"test": i.test.name,
2629 "arch": i.platform.arch,
2630 "platform": i.platform.name,
2631 "extra_args": " ".join(i.test.extra_args),
2632 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002633 if goal.failed:
2634 rowdict["passed"] = False
2635 rowdict["status"] = goal.reason
2636 else:
2637 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002638 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002639 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002640 rowdict["ram_size"] = goal.metrics["ram_size"]
2641 rowdict["rom_size"] = goal.metrics["rom_size"]
2642 cw.writerow(rowdict)
2643
2644
2645def parse_arguments():
2646
Anas Nashif3ba1d432017-12-05 15:28:44 -05002647 parser = argparse.ArgumentParser(
2648 description=__doc__,
2649 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002650 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002651
Marc Herbertedf17592019-03-08 12:39:11 -08002652 case_select = parser.add_argument_group("Test case selection")
2653
Anas Nashif07d54c02018-07-21 19:29:08 -05002654 parser.add_argument("--force-toolchain", action="store_true",
2655 help="Do not filter based on toolchain, use the set "
2656 " toolchain unconditionally")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002657 parser.add_argument(
2658 "-p", "--platform", action="append",
2659 help="Platform filter for testing. This option may be used multiple "
2660 "times. Testcases will only be built/run on the platforms "
2661 "specified. If this option is not used, then platforms marked "
2662 "as default in the platform metadata file will be chosen "
2663 "to build and test. ")
2664 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002665 "-a", "--arch", action="append",
2666 help="Arch filter for testing. Takes precedence over --platform. "
2667 "If unspecified, test all arches. Multiple invocations "
2668 "are treated as a logical 'or' relationship")
2669 parser.add_argument(
2670 "-t", "--tag", action="append",
2671 help="Specify tags to restrict which tests to run by tag value. "
2672 "Default is to not do any tag filtering. Multiple invocations "
2673 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002674 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002675 help="Specify tags of tests that should not run. "
2676 "Default is to run all tests with all tags.")
Marc Herbertedf17592019-03-08 12:39:11 -08002677 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002678 "-f",
2679 "--only-failed",
2680 action="store_true",
2681 help="Run only those tests that failed the previous sanity check "
2682 "invocation.")
2683 parser.add_argument(
2684 "-c", "--config", action="append",
2685 help="Specify platform configuration values filtering. This can be "
2686 "specified two ways: <config>=<value> or just <config>. The "
2687 "defconfig for all platforms will be "
2688 "checked. For the <config>=<value> case, only match defconfig "
2689 "that have that value defined. For the <config> case, match "
2690 "defconfig that have that value assigned to any value. "
2691 "Prepend a '!' to invert the match.")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002692
Marc Herbert0c465bb2019-03-11 17:28:36 -07002693 test_xor_subtest = case_select.add_mutually_exclusive_group()
2694
2695 test_xor_subtest.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002696 "-s", "--test", action="append",
2697 help="Run only the specified test cases. These are named by "
2698 "<path to test project relative to "
2699 "--testcase-root>/<testcase.yaml section name>")
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002700
Marc Herbert0c465bb2019-03-11 17:28:36 -07002701 test_xor_subtest.add_argument(
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05002702 "--sub-test", action="append",
2703 help="Run only the specified sub-test cases and its parent. These are named by "
2704 "test case name appended by test function, i.e. kernel.mutex.mutex_lock_unlock."
2705 )
2706
Anas Nashif3ba1d432017-12-05 15:28:44 -05002707 parser.add_argument(
2708 "-l", "--all", action="store_true",
2709 help="Build/test on all platforms. Any --platform arguments "
2710 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002711
Anas Nashif3ba1d432017-12-05 15:28:44 -05002712 parser.add_argument(
2713 "-o", "--testcase-report",
2714 help="Output a CSV spreadsheet containing results of the test run")
2715 parser.add_argument(
2716 "-d", "--discard-report",
2717 help="Output a CSV spreadsheet showing tests that were skipped "
2718 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002719 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002720 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002721
Anas Nashif3ba1d432017-12-05 15:28:44 -05002722 parser.add_argument(
2723 "-B", "--subset",
2724 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2725 "3/5 means run the 3rd fifth of the total. "
2726 "This option is useful when running a large number of tests on "
2727 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002728
2729 parser.add_argument(
2730 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002731 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002732
Anas Nashif3ba1d432017-12-05 15:28:44 -05002733 parser.add_argument(
2734 "-y", "--dry-run", action="store_true",
2735 help="Create the filtered list of test cases, but don't actually "
2736 "run them. Useful if you're just interested in "
2737 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002738
Anas Nashif75547e22018-02-24 08:32:14 -06002739 parser.add_argument("--list-tags", action="store_true",
2740 help="list all tags in selected tests")
2741
Marc Herbertedf17592019-03-08 12:39:11 -08002742 case_select.add_argument("--list-tests", action="store_true",
Anas Nashifc0149cc2018-04-14 23:12:58 -05002743 help="list all tests.")
2744
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002745 parser.add_argument("--export-tests", action="store",
2746 metavar="FILENAME",
2747 help="Export tests case meta-data to a file in CSV format.")
2748
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002749 parser.add_argument("--detailed-report",
2750 action="store",
2751 metavar="FILENAME",
2752 help="Generate a junit report with detailed testcase results.")
2753
Anas Nashif3ba1d432017-12-05 15:28:44 -05002754 parser.add_argument(
2755 "-r", "--release", action="store_true",
2756 help="Update the benchmark database with the results of this test "
2757 "run. Intended to be run by CI when tagging an official "
2758 "release. This database is used as a basis for comparison "
2759 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002760 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002761 help="Treat warning conditions as errors")
2762 parser.add_argument(
2763 "-v",
2764 "--verbose",
2765 action="count",
2766 default=0,
2767 help="Emit debugging information, call multiple times to increase "
2768 "verbosity")
2769 parser.add_argument(
2770 "-i", "--inline-logs", action="store_true",
2771 help="Upon test failure, print relevant log data to stdout "
2772 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002773 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002774 help="log also to file")
2775 parser.add_argument(
2776 "-m", "--last-metrics", action="store_true",
2777 help="Instead of comparing metrics from the last --release, "
2778 "compare with the results of the previous sanity check "
2779 "invocation")
2780 parser.add_argument(
2781 "-u",
2782 "--no-update",
2783 action="store_true",
2784 help="do not update the results of the last run of the sanity "
2785 "checks")
Marc Herbertedf17592019-03-08 12:39:11 -08002786 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002787 "-F",
2788 "--load-tests",
2789 metavar="FILENAME",
2790 action="store",
2791 help="Load list of tests to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002792
Marc Herbertedf17592019-03-08 12:39:11 -08002793 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002794 "-E",
2795 "--save-tests",
2796 metavar="FILENAME",
2797 action="store",
2798 help="Save list of tests to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002799
Anas Nashif3ba1d432017-12-05 15:28:44 -05002800 parser.add_argument(
2801 "-b", "--build-only", action="store_true",
2802 help="Only build the code, do not execute any of it in QEMU")
2803 parser.add_argument(
2804 "-j", "--jobs", type=int,
Oleg Zhurakivskyyf3bc9672018-08-17 18:31:38 +03002805 help="Number of jobs for building, defaults to number of CPU threads "
2806 "overcommited by factor 2")
Anas Nashif73440ea2018-02-19 10:57:03 -06002807
2808 parser.add_argument(
2809 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002810 help="Test on device directly. Specify the serial device to "
2811 "use with the --device-serial option.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002812
2813 parser.add_argument(
2814 "-X", "--fixture", action="append", default=[],
2815 help="Specify a fixture that a board might support")
Anas Nashif73440ea2018-02-19 10:57:03 -06002816 parser.add_argument(
2817 "--device-serial",
Anas Nashif333a3152018-05-24 14:35:33 -05002818 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002819 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002820 "--show-footprint", action="store_true",
2821 help="Show footprint statistics and deltas since last release."
2822 )
2823 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002824 "-H", "--footprint-threshold", type=float, default=5,
2825 help="When checking test case footprint sizes, warn the user if "
2826 "the new app size is greater then the specified percentage "
2827 "from the last release. Default is 5. 0 to warn on any "
2828 "increase on app size")
2829 parser.add_argument(
2830 "-D", "--all-deltas", action="store_true",
2831 help="Show all footprint deltas, positive or negative. Implies "
2832 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002833 parser.add_argument(
2834 "-O", "--outdir",
Anas Nashiff114a132018-11-20 11:51:34 -05002835 default="%s/sanity-out" % os.getcwd(),
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002836 help="Output directory for logs and binaries. "
Anas Nashiff114a132018-11-20 11:51:34 -05002837 "Default is 'sanity-out' in the current directory. "
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002838 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002839 parser.add_argument(
2840 "-n", "--no-clean", action="store_true",
2841 help="Do not delete the outdir before building. Will result in "
2842 "faster compilation since builds will be incremental")
Marc Herbertedf17592019-03-08 12:39:11 -08002843 case_select.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002844 "-T", "--testcase-root", action="append", default=[],
2845 help="Base directory to recursively search for test cases. All "
2846 "testcase.yaml files under here will be processed. May be "
2847 "called multiple times. Defaults to the 'samples' and "
2848 "'tests' directories in the Zephyr tree.")
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002849
Anas Nashif3ba1d432017-12-05 15:28:44 -05002850 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2851 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
Anas Nashif7dd19ea2018-11-14 08:46:49 -05002852
Anas Nashif3ba1d432017-12-05 15:28:44 -05002853 parser.add_argument(
2854 "-A", "--board-root", action="append", default=board_root_list,
2855 help="Directory to search for board configuration files. All .yaml "
2856 "files in the directory will be processed.")
2857 parser.add_argument(
2858 "-z", "--size", action="append",
2859 help="Don't run sanity checks. Instead, produce a report to "
2860 "stdout detailing RAM/ROM sizes on the specified filenames. "
2861 "All other command line arguments ignored.")
2862 parser.add_argument(
2863 "-S", "--enable-slow", action="store_true",
2864 help="Execute time-consuming test cases that have been marked "
2865 "as 'slow' in testcase.yaml. Normally these are only built.")
Sebastian Bøe03ed0952018-11-13 13:36:19 +01002866 parser.add_argument(
2867 "--disable-unrecognized-section-test", action="store_true",
2868 default=False,
2869 help="Skip the 'unrecognized section' test.")
Andrew Boie55121052016-07-20 11:52:04 -07002870 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002871 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002872 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002873 parser.add_argument("--disable-asserts", action="store_false",
2874 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07002875 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05002876 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002877 help="Error on deprecation warnings.")
Andy Ross9c9162d2019-01-03 10:50:53 -08002878 parser.add_argument("--disable-size-report", action="store_true",
2879 help="Skip expensive computation of ram/rom segment sizes.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002880
2881 parser.add_argument(
2882 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002883 help="""Extra CMake cache entries to define when building test cases.
2884 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01002885 prefixed with -D before being passed to CMake.
2886
2887 E.g
2888 "sanitycheck -x=USE_CCACHE=0"
2889 will translate to
2890 "cmake -DUSE_CCACHE=0"
2891
2892 which will ultimately disable ccache.
2893 """
2894 )
Anas Nashif47cf4bf2019-01-24 21:50:59 -05002895 parser.add_argument("--gcov-tool", default="gcov",
2896 help="Path to the gcov tool. Default is gcov in the path.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002897
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002898 parser.add_argument("--enable-coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05002899 help="Enable code coverage using gcov.")
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002900
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002901 parser.add_argument("-C", "--coverage", action="store_true",
Anas Nashif8d72bb92018-11-07 23:05:42 -05002902 help="Generate coverage reports. Implies --enable_coverage")
Andrew Boie6acbe632015-07-17 12:03:52 -07002903
Anas Nashifdbd76492018-11-23 20:24:19 -05002904 coverage_platforms = ["native_posix", "nrf52_bsim"]
2905 parser.add_argument("--coverage-platform", action="append", default=coverage_platforms,
2906 help="Plarforms to run coverage reports on. "
2907 "This option may be used multiple times.")
2908
Andrew Boie6acbe632015-07-17 12:03:52 -07002909 return parser.parse_args()
2910
Anas Nashif3ba1d432017-12-05 15:28:44 -05002911
Andrew Boie6acbe632015-07-17 12:03:52 -07002912def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01002913 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002914 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002915 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08002916
2917 try:
2918 with open(filename) as fp:
2919 data = fp.read()
2920 except Exception as e:
2921 data = "Unable to read log data (%s)\n" % (str(e))
2922
2923 sys.stdout.write(data)
2924 if log_file:
2925 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002926 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002927 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002928 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07002929
Anas Nashif3ba1d432017-12-05 15:28:44 -05002930
Andrew Boie6acbe632015-07-17 12:03:52 -07002931def terse_test_cb(instances, goals, goal):
2932 total_tests = len(goals)
2933 total_done = 0
2934 total_failed = 0
2935
Andrew Boie08ce5a52016-02-22 13:28:10 -08002936 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002937 if g.finished:
2938 total_done += 1
2939 if g.failed:
2940 total_failed += 1
2941
2942 if goal.failed:
2943 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002944 info(
2945 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
2946 i.platform.name,
2947 i.test.name,
2948 COLOR_RED,
2949 COLOR_NORMAL,
2950 goal.reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002951 log_info(goal.get_error_log())
2952 info("")
2953
Anas Nashif3ba1d432017-12-05 15:28:44 -05002954 sys.stdout.write(
2955 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
2956 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
2957 int((float(total_done) / total_tests) * 100),
2958 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
2959 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07002960 sys.stdout.flush()
2961
Anas Nashif3ba1d432017-12-05 15:28:44 -05002962
Andrew Boie6acbe632015-07-17 12:03:52 -07002963def chatty_test_cb(instances, goals, goal):
2964 i = instances[goal.name]
2965
2966 if VERBOSE < 2 and not goal.finished:
2967 return
2968
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002969 total_tests = len(goals)
2970 total_tests_width = len(str(total_tests))
2971 total_done = 0
2972
2973 for k, g in goals.items():
2974 if g.finished:
2975 total_done += 1
2976
Andrew Boie6acbe632015-07-17 12:03:52 -07002977 if goal.failed:
2978 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
2979 elif goal.finished:
2980 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2981 else:
2982 status = goal.make_state
2983
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002984 info("{:>{}}/{} {:<25} {:<50} {}".format(
2985 total_done, total_tests_width, total_tests, i.platform.name,
2986 i.test.name, status))
Andrew Boie6acbe632015-07-17 12:03:52 -07002987 if goal.failed:
2988 log_info(goal.get_error_log())
2989
Andrew Boiebbd670c2015-08-17 13:16:11 -07002990
2991def size_report(sc):
2992 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07002993 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07002994 for i in range(len(sc.sections)):
2995 v = sc.sections[i]
2996
Andrew Boie73b4ee62015-10-07 11:33:22 -07002997 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
2998 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
2999 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07003000
Andrew Boie73b4ee62015-10-07 11:33:22 -07003001 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003002 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003003 info("")
3004
Anas Nashiff29087e2019-01-25 09:37:38 -05003005def retrieve_gcov_data(intput_file):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003006 if VERBOSE:
3007 print("Working on %s" %intput_file)
3008 extracted_coverage_info = {}
3009 capture_data = False
Anas Nashiff29087e2019-01-25 09:37:38 -05003010 capture_complete = False
Anas Nashifdb9592a2018-10-08 10:19:41 -04003011 with open(intput_file, 'r') as fp:
3012 for line in fp.readlines():
3013 if re.search("GCOV_COVERAGE_DUMP_START", line):
3014 capture_data = True
3015 continue
3016 if re.search("GCOV_COVERAGE_DUMP_END", line):
Anas Nashiff29087e2019-01-25 09:37:38 -05003017 capture_complete = True
Anas Nashifdb9592a2018-10-08 10:19:41 -04003018 break
3019 # Loop until the coverage data is found.
3020 if not capture_data:
3021 continue
3022 if line.startswith("*"):
3023 sp = line.split("<")
3024 if len(sp) > 1:
3025 # Remove the leading delimiter "*"
3026 file_name = sp[0][1:]
3027 # Remove the trailing new line char
3028 hex_dump = sp[1][:-1]
3029 else:
3030 continue
3031 else:
3032 continue
3033 extracted_coverage_info.update({file_name:hex_dump})
Anas Nashiff29087e2019-01-25 09:37:38 -05003034 if not capture_data:
3035 capture_complete = True
3036 return {'complete': capture_complete, 'data': extracted_coverage_info}
Anas Nashifdb9592a2018-10-08 10:19:41 -04003037
3038def create_gcda_files(extracted_coverage_info):
3039 if VERBOSE:
3040 print("Generating gcda files")
3041 for filename, hexdump_val in extracted_coverage_info.items():
3042 # if kobject_hash is given for coverage gcovr fails
3043 # hence skipping it problem only in gcovr v4.1
3044 if "kobject_hash" in filename:
3045 filename = (filename[:-4]) +"gcno"
3046 try:
3047 os.remove(filename)
3048 except:
3049 pass
3050 continue
3051
3052 with open(filename, 'wb') as fp:
3053 fp.write(bytes.fromhex(hexdump_val))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003054
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003055def generate_coverage(outdir, ignores):
Anas Nashifdb9592a2018-10-08 10:19:41 -04003056
3057 for filename in glob.glob("%s/**/handler.log" %outdir, recursive=True):
Anas Nashiff29087e2019-01-25 09:37:38 -05003058 gcov_data = retrieve_gcov_data(filename)
3059 capture_complete = gcov_data['complete']
3060 extracted_coverage_info = gcov_data['data']
3061 if capture_complete:
3062 create_gcda_files(extracted_coverage_info)
3063 verbose("Gcov data captured: {}".format(filename))
3064 else:
3065 error("Gcov data capture incomplete: {}".format(filename))
Anas Nashifdb9592a2018-10-08 10:19:41 -04003066
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003067 gcov_tool = options.gcov_tool
3068
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003069 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
3070 coveragefile = os.path.join(outdir, "coverage.info")
3071 ztestfile = os.path.join(outdir, "ztest.info")
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003072 subprocess.call(["lcov", "--gcov-tool", gcov_tool,
3073 "--capture", "--directory", outdir,
3074 "--rc", "lcov_branch_coverage=1",
3075 "--output-file", coveragefile], stdout=coveragelog)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003076 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003077 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05003078 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003079 "--output-file", ztestfile,
3080 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
3081
Anas Nashif3cbffef2018-11-07 23:50:54 -05003082 if os.path.exists(ztestfile) and os.path.getsize(ztestfile) > 0:
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003083 subprocess.call(["lcov", "--gcov-tool", gcov_tool, "--remove", ztestfile,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003084 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
3085 "--output-file", ztestfile,
3086 "--rc", "lcov_branch_coverage=1"],
3087 stdout=coveragelog)
3088 files = [coveragefile, ztestfile];
3089 else:
3090 files = [coveragefile];
3091
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003092 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003093 subprocess.call(
Anas Nashif47cf4bf2019-01-24 21:50:59 -05003094 ["lcov", "--gcov-tool", gcov_tool, "--remove",
3095 coveragefile, i, "--output-file",
3096 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05003097 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003098
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003099 #The --ignore-errors source option is added to avoid it exiting due to
3100 #samples/application_development/external_lib/
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003101 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
Alberto Escolar Piedras834b86f2019-02-03 15:48:07 +01003102 "--ignore-errors", "source",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01003103 "-output-directory",
3104 os.path.join(outdir, "coverage")] + files,
3105 stdout=coveragelog)
3106 if ret==0:
3107 info("HTML report generated: %s"%
3108 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05003109
Andrew Boiebbd670c2015-08-17 13:16:11 -07003110
Andrew Boie6acbe632015-07-17 12:03:52 -07003111def main():
Andrew Boie4b182472015-07-31 12:25:22 -07003112 start_time = time.time()
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003113 global VERBOSE, INLINE_LOGS, JOBS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05003114 global options
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003115 global run_individual_tests
Anas Nashife10b6512017-12-30 13:01:45 -05003116 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07003117
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02003118 if options.coverage:
3119 options.enable_coverage = True
3120
Anas Nashife10b6512017-12-30 13:01:45 -05003121 if options.size:
3122 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08003123 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07003124 sys.exit(0)
3125
Anas Nashif73440ea2018-02-19 10:57:03 -06003126
3127 if options.device_testing:
3128 if options.device_serial is None or len(options.platform) != 1:
3129 sys.exit(1)
3130
Anas Nashife10b6512017-12-30 13:01:45 -05003131 VERBOSE += options.verbose
3132 INLINE_LOGS = options.inline_logs
3133 if options.log_file:
3134 log_file = open(options.log_file, "w")
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003135
Anas Nashife10b6512017-12-30 13:01:45 -05003136 if options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003137 JOBS = options.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07003138
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003139 # Decrease JOBS for Ninja, if jobs weren't explicitly set
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003140 if options.ninja and not options.jobs:
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003141 JOBS = int(JOBS * 0.75)
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003142
Oleg Zhurakivskyy99aacd92018-08-17 15:02:28 +03003143 info("JOBS: %d" % JOBS);
Oleg Zhurakivskyyc97054c2018-08-17 14:56:05 +03003144
Anas Nashife10b6512017-12-30 13:01:45 -05003145 if options.subset:
3146 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04003147 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05003148 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04003149 else:
Anas Nashife10b6512017-12-30 13:01:45 -05003150 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04003151 return
3152
Anas Nashife10b6512017-12-30 13:01:45 -05003153 if os.path.exists(options.outdir) and not options.no_clean:
3154 info("Cleaning output directory " + options.outdir)
3155 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07003156
Anas Nashife10b6512017-12-30 13:01:45 -05003157 if not options.testcase_root:
3158 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07003159 os.path.join(ZEPHYR_BASE, "samples")]
3160
Anas Nashif37f9dc52018-02-23 08:53:46 -06003161 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04003162
Kumar Galac84235e2018-04-10 13:32:51 -05003163 if ts.load_errors:
3164 sys.exit(1)
3165
Anas Nashif75547e22018-02-24 08:32:14 -06003166 if options.list_tags:
3167 tags = set()
3168 for n,tc in ts.testcases.items():
3169 tags = tags.union(tc.tags)
3170
3171 for t in tags:
3172 print("- {}".format(t))
3173
3174 return
3175
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003176
3177 def export_tests(filename, tests):
3178 with open(filename, "wt") as csvfile:
3179 fieldnames = ['section', 'subsection', 'title', 'reference']
3180 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
3181 for test in tests:
3182 data = test.split(".")
3183 subsec = " ".join(data[1].split("_")).title()
3184 rowdict = {
3185 "section": data[0].capitalize(),
3186 "subsection": subsec,
3187 "title": test,
3188 "reference": test
3189 }
3190 cw.writerow(rowdict)
3191
3192 if options.export_tests:
3193 cnt = 0
3194 unq = []
3195 for n,tc in ts.testcases.items():
3196 for c in tc.cases:
3197 unq.append(c)
3198
3199 tests = sorted(set(unq))
3200 export_tests(options.export_tests, tests)
3201 return
3202
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003203 run_individual_tests = []
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05003204
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003205 if options.test:
3206 run_individual_tests = options.test
3207
3208 if options.list_tests or options.sub_test:
Anas Nashifc0149cc2018-04-14 23:12:58 -05003209 cnt = 0
Anas Nashifa3abe962018-05-05 19:10:22 -05003210 unq = []
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003211 run_individual_tests = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05003212 for n,tc in ts.testcases.items():
3213 for c in tc.cases:
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003214 if options.sub_test and c in options.sub_test:
3215 if tc.name not in run_individual_tests:
3216 run_individual_tests.append(tc.name)
Anas Nashifa3abe962018-05-05 19:10:22 -05003217 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05003218
Anas Nashif1ea5d7b2018-07-12 09:25:22 -05003219 if options.sub_test:
3220 if run_individual_tests:
3221 info("Running the following tests:")
3222 for t in run_individual_tests:
3223 print(" - {}".format(t))
3224 else:
3225 info("Tests not found")
3226 return
3227
3228 elif options.list_tests:
3229 for u in sorted(set(unq)):
3230 cnt = cnt + 1
3231 print(" - {}".format(u))
3232 print("{} total.".format(cnt))
3233 return
Anas Nashifc0149cc2018-04-14 23:12:58 -05003234
Anas Nashifbd166f42017-09-02 12:32:08 -04003235 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05003236 if options.load_tests:
3237 ts.load_from_file(options.load_tests)
Anas Nashiff4d8ecc2019-03-02 15:43:23 -05003238 elif options.only_failed:
3239 ts.get_last_failed()
Anas Nashifbd166f42017-09-02 12:32:08 -04003240 else:
Anas Nashif4f028882017-12-30 11:48:43 -05003241 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07003242
Anas Nashife10b6512017-12-30 13:01:45 -05003243 if options.discard_report:
3244 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07003245
Anas Nashif30551f42018-01-12 21:56:59 -05003246 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003247 # if we are using command line platform filter, no need to list every
3248 # other platform as excluded, we know that already.
3249 # Show only the discards that apply to the selected platforms on the
3250 # command line
3251
Andrew Boie08ce5a52016-02-22 13:28:10 -08003252 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05003253 if options.platform and i.platform.name not in options.platform:
3254 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05003255 debug(
3256 "{:<25} {:<50} {}SKIPPED{}: {}".format(
3257 i.platform.name,
3258 i.test.name,
3259 COLOR_YELLOW,
3260 COLOR_NORMAL,
3261 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07003262
Anas Nashif1a5bba72018-01-05 08:07:45 -05003263
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003264 def native_and_unit_first(a, b):
3265 if a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003266 return -1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003267 if b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05003268 return 1
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003269 if a[0].startswith('native_posix'):
3270 return -1
3271 if b[0].startswith('native_posix'):
3272 return 1
3273 if a[0].split("/",1)[0].endswith("_bsim"):
3274 return -1
3275 if b[0].split("/",1)[0].endswith("_bsim"):
3276 return 1
3277
Anas Nashif1a5bba72018-01-05 08:07:45 -05003278 return (a > b) - (a < b)
3279
3280 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedras3777b442018-10-08 08:57:13 +02003281 key=cmp_to_key(native_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04003282
Anas Nashife10b6512017-12-30 13:01:45 -05003283 if options.save_tests:
3284 ts.run_report(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04003285 return
3286
Anas Nashife10b6512017-12-30 13:01:45 -05003287 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05003288
Anas Nashife10b6512017-12-30 13:01:45 -05003289 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04003290 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04003291 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05003292 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04003293 if subset == sets:
3294 end = total
3295 else:
3296 end = start + per_set
3297
Anas Nashif3ba1d432017-12-05 15:28:44 -05003298 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04003299 ts.instances = OrderedDict(sliced_instances)
3300
Andrew Boie6acbe632015-07-17 12:03:52 -07003301 info("%d tests selected, %d tests discarded due to filters" %
3302 (len(ts.instances), len(discards)))
3303
Anas Nashife10b6512017-12-30 13:01:45 -05003304 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07003305 return
3306
3307 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003308 goals = ts.execute(
3309 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003310 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07003311 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05003312 goals = ts.execute(
3313 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06003314 ts.instances)
Andrew Boie08ce5a52016-02-22 13:28:10 -08003315 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07003316
Anas Nashife0a6a0b2018-02-15 20:07:24 -06003317 if options.detailed_report:
3318 ts.testcase_target_report(options.detailed_report)
3319
Daniel Leung7f850102016-04-08 11:07:32 -07003320 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05003321 if options.compare_report:
3322 report_to_use = options.compare_report
3323 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07003324 report_to_use = LAST_SANITY
3325 else:
3326 report_to_use = RELEASE_DATA
3327
3328 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07003329 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06003330 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07003331 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05003332 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07003333 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07003334 continue
3335
Andrew Boieea7928f2015-08-14 14:27:38 -07003336 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05003337 if not options.all_deltas and (percentage <
3338 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07003339 continue
3340
Daniel Leung00525c22016-04-11 10:27:56 -07003341 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07003342 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05003343 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07003344 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07003345 warnings += 1
3346
3347 if warnings:
3348 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05003349 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07003350
3351 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08003352 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07003353 if goal.failed:
3354 failed += 1
Sebastian Bøe03ed0952018-11-13 13:36:19 +01003355 elif goal.metrics.get("unrecognized") and not options.disable_unrecognized_section_test:
Andrew Boie73b4ee62015-10-07 11:33:22 -07003356 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
3357 (COLOR_RED, COLOR_NORMAL, goal.name,
3358 str(goal.metrics["unrecognized"])))
3359 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07003360
Anas Nashife10b6512017-12-30 13:01:45 -05003361 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003362 info("Generating coverage files...")
Anas Nashif4df6c562018-11-07 19:29:04 -05003363 generate_coverage(options.outdir, ["*generated*", "tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03003364
Anas Nashif0605fa32017-05-07 08:51:02 -04003365 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07003366 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05003367 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
3368 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
3369 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07003370
Anas Nashife10b6512017-12-30 13:01:45 -05003371 if options.testcase_report:
3372 ts.testcase_report(options.testcase_report)
3373 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05003374 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07003375 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05003376 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07003377 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08003378 if log_file:
3379 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05003380 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07003381 sys.exit(1)
3382
Anas Nashif3ba1d432017-12-05 15:28:44 -05003383
Andrew Boie6acbe632015-07-17 12:03:52 -07003384if __name__ == "__main__":
3385 main()