blob: cbd0a710b7598b35854bd49a8aae5d8c9196fcd1 [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)
Andrew Boie6bb087c2016-02-10 13:39:00 -080030 Don't run this test case unless --enable-slow was passed in on the
31 command line. Intended for time-consuming test cases that are only
32 run under certain circumstances, like daily builds. These test cases
33 are still compiled.
34
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>,
90 *<env>: any environment variable available
Andrew Boie3ea78922016-03-24 14:46:00 -070091 }
92
93 The grammar for the expression language is as follows:
94
95 expression ::= expression "and" expression
96 | expression "or" expression
97 | "not" expression
98 | "(" expression ")"
99 | symbol "==" constant
100 | symbol "!=" constant
101 | symbol "<" number
102 | symbol ">" number
103 | symbol ">=" number
104 | symbol "<=" number
105 | symbol "in" list
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700106 | symbol ":" string
Andrew Boie3ea78922016-03-24 14:46:00 -0700107 | symbol
108
109 list ::= "[" list_contents "]"
110
111 list_contents ::= constant
112 | list_contents "," constant
113
114 constant ::= number
115 | string
116
117
118 For the case where expression ::= symbol, it evaluates to true
119 if the symbol is defined to a non-empty string.
120
121 Operator precedence, starting from lowest to highest:
122
123 or (left associative)
124 and (left associative)
125 not (right associative)
126 all comparison operators (non-associative)
127
128 arch_whitelist, arch_exclude, platform_whitelist, platform_exclude
129 are all syntactic sugar for these expressions. For instance
130
131 arch_exclude = x86 arc
132
133 Is the same as:
134
135 filter = not ARCH in ["x86", "arc"]
Andrew Boie6acbe632015-07-17 12:03:52 -0700136
Andrew Boie7a87f9f2016-06-02 12:27:54 -0700137 The ':' operator compiles the string argument as a regular expression,
138 and then returns a true value only if the symbol's value in the environment
139 matches. For example, if CONFIG_SOC="quark_se" then
140
141 filter = CONFIG_SOC : "quark.*"
142
143 Would match it.
144
Anas Nashifa792a3d2017-04-04 18:47:49 -0400145The set of test cases that actually run depends on directives in the testcase
146filed and options passed in on the command line. If there is any confusion,
147running with -v or --discard-report can help show why particular test cases
148were skipped.
Andrew Boie6acbe632015-07-17 12:03:52 -0700149
150Metrics (such as pass/fail state and binary size) for the last code
151release are stored in scripts/sanity_chk/sanity_last_release.csv.
152To update this, pass the --all --release options.
153
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -0500154To load arguments from a file, write '+' before the file name, e.g.,
155+file_name. File content must be one or more valid arguments separated by
156line break instead of white spaces.
157
Andrew Boie6acbe632015-07-17 12:03:52 -0700158Most everyday users will run with no arguments.
159"""
160
Anas Nashifaae71d72018-04-21 22:26:48 -0500161import contextlib
162import mmap
Andrew Boie6acbe632015-07-17 12:03:52 -0700163import argparse
164import os
165import sys
Andrew Boie6acbe632015-07-17 12:03:52 -0700166import re
Andrew Boie6acbe632015-07-17 12:03:52 -0700167import subprocess
168import multiprocessing
169import select
170import shutil
171import signal
172import threading
173import time
174import csv
Andrew Boie5d4eb782015-10-02 10:04:56 -0700175import glob
Anas Nashif73440ea2018-02-19 10:57:03 -0600176import serial
Daniel Leung6b170072016-04-07 12:10:25 -0700177import concurrent
178import concurrent.futures
Anas Nashifb3311ed2017-04-13 14:44:48 -0400179import xml.etree.ElementTree as ET
Anas Nashife6fcc012017-05-17 09:29:09 -0400180from xml.sax.saxutils import escape
Anas Nashif035799f2017-05-13 21:31:53 -0400181from collections import OrderedDict
182from itertools import islice
Anas Nashif1a5bba72018-01-05 08:07:45 -0500183from functools import cmp_to_key
Andrew Boie6acbe632015-07-17 12:03:52 -0700184
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700185import logging
Anas Nashif3ba1d432017-12-05 15:28:44 -0500186from sanity_chk import scl
187from sanity_chk import expr_parser
188
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700189log_format = "%(levelname)s %(name)s::%(module)s.%(funcName)s():%(lineno)d: %(message)s"
Anas Nashif3ba1d432017-12-05 15:28:44 -0500190logging.basicConfig(format=log_format, level=30)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700191
Andrew Boie6acbe632015-07-17 12:03:52 -0700192if "ZEPHYR_BASE" not in os.environ:
Anas Nashif427cdd32015-08-06 07:25:42 -0400193 sys.stderr.write("$ZEPHYR_BASE environment variable undefined.\n")
Andrew Boie6acbe632015-07-17 12:03:52 -0700194 exit(1)
195ZEPHYR_BASE = os.environ["ZEPHYR_BASE"]
Andrew Boie3ea78922016-03-24 14:46:00 -0700196
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -0700197
Andrew Boie3ea78922016-03-24 14:46:00 -0700198sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/"))
199
Andrew Boie3ea78922016-03-24 14:46:00 -0700200
Andrew Boie6acbe632015-07-17 12:03:52 -0700201VERBOSE = 0
202LAST_SANITY = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
203 "last_sanity.csv")
Anas Nashifb3311ed2017-04-13 14:44:48 -0400204LAST_SANITY_XUNIT = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
Anas Nashif3ba1d432017-12-05 15:28:44 -0500205 "last_sanity.xml")
Andrew Boie6acbe632015-07-17 12:03:52 -0700206RELEASE_DATA = os.path.join(ZEPHYR_BASE, "scripts", "sanity_chk",
207 "sanity_last_release.csv")
Daniel Leung6b170072016-04-07 12:10:25 -0700208CPU_COUNTS = multiprocessing.cpu_count()
Andrew Boie6acbe632015-07-17 12:03:52 -0700209
210if os.isatty(sys.stdout.fileno()):
211 TERMINAL = True
212 COLOR_NORMAL = '\033[0m'
213 COLOR_RED = '\033[91m'
214 COLOR_GREEN = '\033[92m'
215 COLOR_YELLOW = '\033[93m'
216else:
217 TERMINAL = False
218 COLOR_NORMAL = ""
219 COLOR_RED = ""
220 COLOR_GREEN = ""
221 COLOR_YELLOW = ""
222
223class SanityCheckException(Exception):
224 pass
225
Anas Nashif3ba1d432017-12-05 15:28:44 -0500226
Andrew Boie6acbe632015-07-17 12:03:52 -0700227class SanityRuntimeError(SanityCheckException):
228 pass
229
Anas Nashif3ba1d432017-12-05 15:28:44 -0500230
Andrew Boie6acbe632015-07-17 12:03:52 -0700231class ConfigurationError(SanityCheckException):
232 def __init__(self, cfile, message):
233 self.cfile = cfile
234 self.message = message
235
236 def __str__(self):
237 return repr(self.cfile + ": " + self.message)
238
Anas Nashif3ba1d432017-12-05 15:28:44 -0500239
Andrew Boie6acbe632015-07-17 12:03:52 -0700240class MakeError(SanityCheckException):
241 pass
242
Anas Nashif3ba1d432017-12-05 15:28:44 -0500243
Andrew Boie6acbe632015-07-17 12:03:52 -0700244class BuildError(MakeError):
245 pass
246
Anas Nashif3ba1d432017-12-05 15:28:44 -0500247
Andrew Boie6acbe632015-07-17 12:03:52 -0700248class ExecutionError(MakeError):
249 pass
250
Anas Nashif3ba1d432017-12-05 15:28:44 -0500251
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800252log_file = None
253
Anas Nashif3ba1d432017-12-05 15:28:44 -0500254
Andrew Boie6acbe632015-07-17 12:03:52 -0700255# Debug Functions
Andrew Boie08ce5a52016-02-22 13:28:10 -0800256def info(what):
257 sys.stdout.write(what + "\n")
Paul Sokolovsky3ba08762017-10-27 14:53:24 +0300258 sys.stdout.flush()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800259 if log_file:
260 log_file.write(what + "\n")
261 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700262
Anas Nashif3ba1d432017-12-05 15:28:44 -0500263
Andrew Boie6acbe632015-07-17 12:03:52 -0700264def error(what):
265 sys.stderr.write(COLOR_RED + what + COLOR_NORMAL + "\n")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -0800266 if log_file:
267 log_file(what + "\n")
268 log_file.flush()
Andrew Boie6acbe632015-07-17 12:03:52 -0700269
Anas Nashif3ba1d432017-12-05 15:28:44 -0500270
Andrew Boie08ce5a52016-02-22 13:28:10 -0800271def debug(what):
272 if VERBOSE >= 1:
273 info(what)
274
Anas Nashif3ba1d432017-12-05 15:28:44 -0500275
Andrew Boie6acbe632015-07-17 12:03:52 -0700276def verbose(what):
277 if VERBOSE >= 2:
Andrew Boie08ce5a52016-02-22 13:28:10 -0800278 info(what)
Andrew Boie6acbe632015-07-17 12:03:52 -0700279
Anas Nashif576be982017-12-23 20:20:27 -0500280class HarnessImporter:
281
282 def __init__(self, name):
283 sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/sanity_chk"))
284 module = __import__("harness")
285 if name:
286 my_class = getattr(module, name)
287 else:
288 my_class = getattr(module, "Test")
289
290 self.instance = my_class()
Anas Nashif3ba1d432017-12-05 15:28:44 -0500291
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300292class Handler:
Anas Nashif576be982017-12-23 20:20:27 -0500293 def __init__(self, instance):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300294 """Constructor
295
296 @param name Arbitrary name of the created thread
Anas Nashif66bdb322017-11-25 17:20:07 -0500297 @param outdir Working directory, should be where handler pid file (qemu.pid for example)
298 gets created by the build system
299 @param log_fn Absolute path to write out handler's log data
300 @param timeout Kill the handler process if it doesn't finish up within
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300301 the given number of seconds
302 """
303 self.lock = threading.Lock()
304 self.state = "waiting"
305 self.metrics = {}
Anas Nashifc8390f12017-11-25 17:14:12 -0500306 self.metrics["handler_time"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300307 self.metrics["ram_size"] = 0
308 self.metrics["rom_size"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300309
Anas Nashifd3384fb2018-02-22 06:44:16 -0600310 self.name = instance.name
311 self.instance = instance
312 self.timeout = instance.test.timeout
313 self.sourcedir = instance.test.code_location
314 self.outdir = instance.outdir
315 self.handler_log = os.path.join(self.outdir, "handler.log")
316 self.returncode = 0
317 self.set_state("running", {})
318
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300319 def set_state(self, state, metrics):
320 self.lock.acquire()
321 self.state = state
322 self.metrics.update(metrics)
323 self.lock.release()
324
325 def get_state(self):
326 self.lock.acquire()
327 ret = (self.state, self.metrics)
328 self.lock.release()
329 return ret
330
Anas Nashif73440ea2018-02-19 10:57:03 -0600331
332class DeviceHandler(Handler):
333
334 def __init__(self, instance):
335 """Constructor
336
337 @param instance Test Instance
338 """
339 super().__init__(instance)
340
Anas Nashif73440ea2018-02-19 10:57:03 -0600341 def monitor_serial(self, ser, harness):
342 log_out_fp = open(self.handler_log, "wt")
343
344 while ser.isOpen():
Anas Nashif61e21632018-04-08 13:30:16 -0500345 try:
346 serial_line = ser.readline()
347 except TypeError:
348 pass
349
Anas Nashif73440ea2018-02-19 10:57:03 -0600350 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600351 sl = serial_line.decode('utf-8', 'ignore')
352 verbose("DEVICE: {0}".format(sl.rstrip()))
353
354 log_out_fp.write(sl)
355 log_out_fp.flush()
356 harness.handle(sl.rstrip())
Anas Nashif73440ea2018-02-19 10:57:03 -0600357 if harness.state:
358 ser.close()
359 break
360
361 log_out_fp.close()
362
363 def handle(self):
364 out_state = "failed"
365
Anas Nashifd3384fb2018-02-22 06:44:16 -0600366 if options.ninja:
367 generator_cmd = "ninja"
368 else:
369 generator_cmd = "make"
370
371 command = [generator_cmd, "-C", self.outdir, "flash"]
372
Anas Nashif73440ea2018-02-19 10:57:03 -0600373 device = options.device_serial
374 ser = serial.Serial(
375 device,
376 baudrate=115200,
377 parity=serial.PARITY_NONE,
378 stopbits=serial.STOPBITS_ONE,
379 bytesize=serial.EIGHTBITS,
380 timeout=self.timeout
381 )
382
383 ser.flush()
384
385 harness_name = self.instance.test.harness.capitalize()
386 harness_import = HarnessImporter(harness_name)
387 harness = harness_import.instance
388 harness.configure(self.instance)
389
390 t = threading.Thread(target=self.monitor_serial, args=(ser, harness))
391 t.start()
392
Anas Nashif61e21632018-04-08 13:30:16 -0500393 try:
394 subprocess.check_output(command, stderr=subprocess.PIPE)
395 except subprocess.CalledProcessError:
396 pass
Anas Nashif73440ea2018-02-19 10:57:03 -0600397
398 t.join(self.timeout)
399 if t.is_alive():
400 out_state = "timeout"
401 ser.close()
402
403 if ser.isOpen():
404 ser.close()
405
Anas Nashifd3384fb2018-02-22 06:44:16 -0600406 if out_state == "timeout":
407 for c in self.instance.test.cases:
408 if c not in harness.tests:
409 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500410
411 self.instance.results = harness.tests
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600412
Anas Nashif73440ea2018-02-19 10:57:03 -0600413 if harness.state:
414 self.set_state(harness.state, {})
415 else:
416 self.set_state(out_state, {})
417
Anas Nashifcc164222017-12-26 11:02:46 -0500418class NativeHandler(Handler):
Anas Nashif576be982017-12-23 20:20:27 -0500419 def __init__(self, instance):
Anas Nashifcc164222017-12-26 11:02:46 -0500420 """Constructor
421
Anas Nashifb630ca62018-01-29 08:38:57 -0500422 @param instance Test Instance
Anas Nashifcc164222017-12-26 11:02:46 -0500423 """
Anas Nashif576be982017-12-23 20:20:27 -0500424 super().__init__(instance)
Anas Nashifcc164222017-12-26 11:02:46 -0500425
Anas Nashifcc164222017-12-26 11:02:46 -0500426 self.valgrind = False
Anas Nashifcc164222017-12-26 11:02:46 -0500427
Anas Nashiffd6eb762017-12-31 11:16:49 -0500428 def _output_reader(self, proc, harness):
Anas Nashifa49048b2018-01-29 08:41:19 -0500429 log_out_fp = open(self.handler_log, "wt")
Anas Nashif576be982017-12-23 20:20:27 -0500430 for line in iter(proc.stdout.readline, b''):
431 verbose("NATIVE: {0}".format(line.decode('utf-8').rstrip()))
Anas Nashiffd6eb762017-12-31 11:16:49 -0500432 log_out_fp.write(line.decode('utf-8'))
433 log_out_fp.flush()
Anas Nashif576be982017-12-23 20:20:27 -0500434 harness.handle(line.decode('utf-8').rstrip())
435 if harness.state:
436 proc.terminate()
437 break
438
Anas Nashiffd6eb762017-12-31 11:16:49 -0500439 log_out_fp.close()
440
Anas Nashifcc164222017-12-26 11:02:46 -0500441 def handle(self):
442 out_state = "failed"
443
Anas Nashif576be982017-12-23 20:20:27 -0500444 harness_name = self.instance.test.harness.capitalize()
445 harness_import = HarnessImporter(harness_name)
446 harness = harness_import.instance
447 harness.configure(self.instance)
Anas Nashifcc164222017-12-26 11:02:46 -0500448
Anas Nashif576be982017-12-23 20:20:27 -0500449 binary = os.path.join(self.outdir, "zephyr", "zephyr.exe")
450 command = [binary]
451 if shutil.which("valgrind") and self.valgrind:
452 command = ["valgrind", "--error-exitcode=2",
453 "--leak-check=full"] + command
454
455 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
Anas Nashiffd6eb762017-12-31 11:16:49 -0500456 t = threading.Thread(target=self._output_reader, args=(proc, harness, ))
Anas Nashif576be982017-12-23 20:20:27 -0500457 t.start()
458 t.join(self.timeout)
459 if t.is_alive():
460 proc.terminate()
Anas Nashifcc164222017-12-26 11:02:46 -0500461 out_state = "timeout"
Anas Nashif576be982017-12-23 20:20:27 -0500462 t.join()
Anas Nashifcc164222017-12-26 11:02:46 -0500463
Anas Nashif576be982017-12-23 20:20:27 -0500464 proc.wait()
465 self.returncode = proc.returncode
466 if proc.returncode != 0:
Anas Nashifa49048b2018-01-29 08:41:19 -0500467 out_state = "failed"
Anas Nashifcc164222017-12-26 11:02:46 -0500468
Anas Nashif576be982017-12-23 20:20:27 -0500469 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-b", "-s", self.outdir], shell=True)
Anas Nashifcc164222017-12-26 11:02:46 -0500470
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600471
472 self.instance.results = harness.tests
Anas Nashif576be982017-12-23 20:20:27 -0500473 if harness.state:
474 self.set_state(harness.state, {})
475 else:
476 self.set_state(out_state, {})
477
Anas Nashif3ba1d432017-12-05 15:28:44 -0500478
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300479class UnitHandler(Handler):
Anas Nashif576be982017-12-23 20:20:27 -0500480 def __init__(self, instance):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300481 """Constructor
482
Anas Nashifb630ca62018-01-29 08:38:57 -0500483 @param instance Test instance
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300484 """
Anas Nashif576be982017-12-23 20:20:27 -0500485 super().__init__(instance)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300486
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300487
488 def handle(self):
489 out_state = "failed"
490
Anas Nashifd3384fb2018-02-22 06:44:16 -0600491 with open(self.handler_log, "wt") as hl:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300492 try:
493 binary = os.path.join(self.outdir, "testbinary")
494 command = [binary]
495 if shutil.which("valgrind"):
496 command = ["valgrind", "--error-exitcode=2",
497 "--leak-check=full"] + command
498 returncode = subprocess.call(command, timeout=self.timeout,
Anas Nashifd3384fb2018-02-22 06:44:16 -0600499 stdout=hl, stderr=hl)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300500 self.returncode = returncode
501 if returncode != 0:
502 if self.returncode == 1:
503 out_state = "failed"
504 else:
505 out_state = "failed valgrind"
506 else:
507 out_state = "passed"
508 except subprocess.TimeoutExpired:
509 out_state = "timeout"
510 self.returncode = 1
511
Anas Nashif3ba1d432017-12-05 15:28:44 -0500512 returncode = subprocess.call(
513 ["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300514
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300515 self.set_state(out_state, {})
516
Anas Nashif3ba1d432017-12-05 15:28:44 -0500517
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300518class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700519 """Spawns a thread to monitor QEMU output from pipes
520
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400521 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700522 We need to do this as once qemu starts, it runs forever until killed.
523 Test cases emit special messages to the console as they run, we check
524 for these to collect whether the test passed or failed.
525 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700526
527 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500528 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700529 fifo_in = fifo_fn + ".in"
530 fifo_out = fifo_fn + ".out"
531
532 # These in/out nodes are named from QEMU's perspective, not ours
533 if os.path.exists(fifo_in):
534 os.unlink(fifo_in)
535 os.mkfifo(fifo_in)
536 if os.path.exists(fifo_out):
537 os.unlink(fifo_out)
538 os.mkfifo(fifo_out)
539
540 # We don't do anything with out_fp but we need to open it for
541 # writing so that QEMU doesn't block, due to the way pipes work
542 out_fp = open(fifo_in, "wb")
543 # Disable internal buffering, we don't
544 # want read() or poll() to ever block if there is data in there
545 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800546 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700547
548 start_time = time.time()
549 timeout_time = start_time + timeout
550 p = select.poll()
551 p.register(in_fp, select.POLLIN)
552
553 metrics = {}
554 line = ""
555 while True:
556 this_timeout = int((timeout_time - time.time()) * 1000)
557 if this_timeout < 0 or not p.poll(this_timeout):
558 out_state = "timeout"
559 break
560
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500561 try:
562 c = in_fp.read(1).decode("utf-8")
563 except UnicodeDecodeError:
564 # Test is writing something weird, fail
565 out_state = "unexpected byte"
566 break
567
Andrew Boie6acbe632015-07-17 12:03:52 -0700568 if c == "":
569 # EOF, this shouldn't happen unless QEMU crashes
570 out_state = "unexpected eof"
571 break
572 line = line + c
573 if c != "\n":
574 continue
575
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300576 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700577 log_out_fp.write(line)
578 log_out_fp.flush()
579 line = line.strip()
580 verbose("QEMU: %s" % line)
581
Anas Nashif576be982017-12-23 20:20:27 -0500582 harness.handle(line)
583 if harness.state:
584 out_state = harness.state
Andrew Boie6acbe632015-07-17 12:03:52 -0700585 break
586
587 # TODO: Add support for getting numerical performance data
588 # from test cases. Will involve extending test case reporting
589 # APIs. Add whatever gets reported to the metrics dictionary
590 line = ""
591
Anas Nashifc8390f12017-11-25 17:14:12 -0500592 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700593 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500594 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700595 handler.set_state(out_state, metrics)
596
597 log_out_fp.close()
598 out_fp.close()
599 in_fp.close()
600
601 pid = int(open(pid_fn).read())
602 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800603 try:
604 os.kill(pid, signal.SIGTERM)
605 except ProcessLookupError:
606 # Oh well, as long as it's dead! User probably sent Ctrl-C
607 pass
608
Andrew Boie6acbe632015-07-17 12:03:52 -0700609 os.unlink(fifo_in)
610 os.unlink(fifo_out)
611
Anas Nashif576be982017-12-23 20:20:27 -0500612 def __init__(self, instance):
Andrew Boie6acbe632015-07-17 12:03:52 -0700613 """Constructor
614
Anas Nashifd3384fb2018-02-22 06:44:16 -0600615 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700616 """
Anas Nashif576be982017-12-23 20:20:27 -0500617
Anas Nashif576be982017-12-23 20:20:27 -0500618 super().__init__(instance)
Anas Nashif576be982017-12-23 20:20:27 -0500619
Andrew Boie6acbe632015-07-17 12:03:52 -0700620 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -0700621
622 # We pass this to QEMU which looks for fifos with .in and .out
623 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500624 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700625
Anas Nashif576be982017-12-23 20:20:27 -0500626 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700627 if os.path.exists(self.pid_fn):
628 os.unlink(self.pid_fn)
629
Anas Nashifd3384fb2018-02-22 06:44:16 -0600630 self.log_fn = self.handler_log
Anas Nashif576be982017-12-23 20:20:27 -0500631
632 harness_import = HarnessImporter(instance.test.harness.capitalize())
633 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600634 harness.configure(self.instance)
635 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
636 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300637 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500638 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600639
640 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700641 self.thread.daemon = True
Anas Nashifd3384fb2018-02-22 06:44:16 -0600642 verbose("Spawning QEMU process for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700643 self.thread.start()
644
Andrew Boie6acbe632015-07-17 12:03:52 -0700645 def get_fifo(self):
646 return self.fifo_fn
647
Anas Nashif3ba1d432017-12-05 15:28:44 -0500648
Andrew Boie6acbe632015-07-17 12:03:52 -0700649class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700650
Erwin Rolcb3d1272018-02-10 11:40:40 +0100651 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
652 "ccm_noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800653 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500654 "_k_memory_pool", "exceptions", "initshell",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700655 "_static_thread_area", "_k_timer_area", "_k_work_area",
656 "_k_mem_slab_area", "_k_mem_pool_area",
657 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
658 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
659 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200660 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700661 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100662 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500663 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
664 'log_backends_sections', 'log_dynamic_sections',
665 'log_const_sections']
Andrew Boie73b4ee62015-10-07 11:33:22 -0700666 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700667 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andy Rossa3a7e8e2018-05-23 16:39:16 -0700668 "rodata", "devconfig", "net_l2", "vector", "_bt_settings_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700669
Andrew Boie52fef672016-11-29 12:21:59 -0800670 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700671 """Constructor
672
Andrew Boiebbd670c2015-08-17 13:16:11 -0700673 @param filename Path to the output binary
674 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700675 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700676 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700677 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700678 magic = f.read(4)
679
Andrew Boie08ce5a52016-02-22 13:28:10 -0800680 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700681 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700682
683 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500684 # GREP can not be used as it returns an error if the symbol is not
685 # found.
686 is_xip_command = "nm " + filename + \
687 " | awk '/CONFIG_XIP/ { print $3 }'"
688 is_xip_output = subprocess.check_output(
689 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
690 "utf-8").strip()
Andrew Boie8f0211d2016-03-02 20:40:29 -0800691 if is_xip_output.endswith("no symbols"):
692 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700693 self.is_xip = (len(is_xip_output) != 0)
694
Andrew Boiebbd670c2015-08-17 13:16:11 -0700695 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700696 self.sections = []
697 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700698 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800699 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700700
701 self._calculate_sizes()
702
703 def get_ram_size(self):
704 """Get the amount of RAM the application will use up on the device
705
706 @return amount of RAM, in bytes
707 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700708 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700709
710 def get_rom_size(self):
711 """Get the size of the data that this application uses on device's flash
712
713 @return amount of ROM, in bytes
714 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700715 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700716
717 def unrecognized_sections(self):
718 """Get a list of sections inside the binary that weren't recognized
719
David B. Kinder29963c32017-06-16 12:32:42 -0700720 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700721 """
722 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700723 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700724 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700725 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700726 return slist
727
728 def _calculate_sizes(self):
729 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700730 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500731 objdump_output = subprocess.check_output(
732 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700733
734 for line in objdump_output:
735 words = line.split()
736
737 if (len(words) == 0): # Skip lines that are too short
738 continue
739
740 index = words[0]
741 if (not index[0].isdigit()): # Skip lines that do not start
742 continue # with a digit
743
744 name = words[1] # Skip lines with section names
745 if (name[0] == '.'): # starting with '.'
746 continue
747
Andrew Boie73b4ee62015-10-07 11:33:22 -0700748 # TODO this doesn't actually reflect the size in flash or RAM as
749 # it doesn't include linker-imposed padding between sections.
750 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700751 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700752 if size == 0:
753 continue
754
Andrew Boie73b4ee62015-10-07 11:33:22 -0700755 load_addr = int(words[4], 16)
756 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700757
758 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700759 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700760 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700761 if name in SizeCalculator.alloc_sections:
762 self.ram_size += size
763 stype = "alloc"
764 elif name in SizeCalculator.rw_sections:
765 self.ram_size += size
766 self.rom_size += size
767 stype = "rw"
768 elif name in SizeCalculator.ro_sections:
769 self.rom_size += size
770 if not self.is_xip:
771 self.ram_size += size
772 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700773 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700774 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800775 if name not in self.extra_sections:
776 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700777
Anas Nashif3ba1d432017-12-05 15:28:44 -0500778 self.sections.append({"name": name, "load_addr": load_addr,
779 "size": size, "virt_addr": virt_addr,
780 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700781
782
783class MakeGoal:
784 """Metadata class representing one of the sub-makes called by MakeGenerator
785
David B. Kinder29963c32017-06-16 12:32:42 -0700786 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -0700787 with TestInstances to get a complete picture of what happened during a test.
788 MakeGenerator is used for tasks outside of building tests (such as
789 defconfigs) which is why MakeGoal is a separate class from TestInstance.
790 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500791
Anas Nashif4d25b502017-11-25 17:37:17 -0500792 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -0700793 self.name = name
794 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -0500795 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -0700796 self.make_log = make_log
797 self.build_log = build_log
798 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -0500799 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700800 self.make_state = "waiting"
801 self.failed = False
802 self.finished = False
803 self.reason = None
804 self.metrics = {}
805
806 def get_error_log(self):
807 if self.make_state == "waiting":
808 # Shouldn't ever see this; breakage in the main Makefile itself.
809 return self.make_log
810 elif self.make_state == "building":
811 # Failure when calling the sub-make to build the code
812 return self.build_log
813 elif self.make_state == "running":
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400814 # Failure in sub-make for "make run", qemu probably failed to start
Andrew Boie6acbe632015-07-17 12:03:52 -0700815 return self.run_log
816 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -0500817 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -0500818 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700819
820 def fail(self, reason):
821 self.failed = True
822 self.finished = True
823 self.reason = reason
824
825 def success(self):
826 self.finished = True
827
828 def __str__(self):
829 if self.finished:
830 if self.failed:
831 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
832 self.get_error_log())
833 else:
834 return "[%s] passed" % self.name
835 else:
836 return "[%s] in progress (%s)" % (self.name, self.make_state)
837
838
839class MakeGenerator:
840 """Generates a Makefile which just calls a bunch of sub-make sessions
841
842 In any given test suite we may need to build dozens if not hundreds of
843 test cases. The cleanest way to parallelize this is to just let Make
844 do the parallelization, sharing the jobserver among all the different
845 sub-make targets.
846 """
847
848 GOAL_HEADER_TMPL = """.PHONY: {goal}
849{goal}:
850"""
851
852 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -0400853\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -0500854\t\t-G"{generator}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400855\t\t-H{directory}\\
856\t\t-B{outdir}\\
857\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
858\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -0500859\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400860\t\t{args}\\
861\t\t>{logfile} 2>&1
Anas Nashifa8a13882017-12-30 13:01:06 -0500862\t{generator_cmd} -C {outdir}\\
863\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400864\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -0700865"""
Anas Nashif20f553f2018-03-23 11:26:41 -0500866 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
867\t{generator_cmd} -C {outdir}\\
868\t\t{verb} {make_args}\\
869\t\t>>{logfile} 2>&1
870"""
Andrew Boie6acbe632015-07-17 12:03:52 -0700871
872 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
873
Anas Nashif3ba1d432017-12-05 15:28:44 -0500874 re_make = re.compile(
875 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700876
Anas Nashif37f9dc52018-02-23 08:53:46 -0600877 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -0700878 """MakeGenerator constructor
879
880 @param base_outdir Intended to be the base out directory. A make.log
881 file will be created here which contains the output of the
882 top-level Make session, as well as the dynamic control Makefile
883 @param verbose If true, pass V=1 to all the sub-makes which greatly
884 increases their verbosity
885 """
886 self.goals = {}
887 if not os.path.exists(base_outdir):
888 os.makedirs(base_outdir)
889 self.logfile = os.path.join(base_outdir, "make.log")
890 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -0600891 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -0700892
893 def _get_rule_header(self, name):
894 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
895
Anas Nashif3ba1d432017-12-05 15:28:44 -0500896 def _get_sub_make(self, name, phase, workdir, outdir,
897 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -0400898 """
899 @param args Arguments given to CMake
900 @param make_args Arguments given to the Makefile generated by CMake
901 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500902 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -0500903 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -0700904 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -0500905
906 if self.deprecations:
907 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800908
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +0200909 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -0500910
Anas Nashif25f6ab62018-03-06 07:15:11 -0600911 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100912 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -0700913 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100914 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -0600915 else:
916 generator = "Unix Makefiles"
917 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -0700918 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -0500919
Anas Nashif20f553f2018-03-23 11:26:41 -0500920 if phase == 'running':
921 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
922 generator_cmd=generator_cmd,
923 phase=phase,
924 goal=name,
925 outdir=outdir,
926 verb=verb,
927 logfile=logfile,
928 make_args=make_args
929 )
930 else:
931 return MakeGenerator.MAKE_RULE_TMPL.format(
932 generator=generator,
933 generator_cmd=generator_cmd,
934 phase=phase,
935 goal=name,
936 outdir=outdir,
937 cflags=cflags,
938 ldflags=ldflags,
939 directory=workdir,
940 verb=verb,
941 args=args,
942 logfile=logfile,
943 make_args=make_args
944 )
Andrew Boie6acbe632015-07-17 12:03:52 -0700945
946 def _get_rule_footer(self, name):
947 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
948
949 def _add_goal(self, outdir):
950 if not os.path.exists(outdir):
951 os.makedirs(outdir)
952
Anas Nashif576be982017-12-23 20:20:27 -0500953 def add_instance_build_goal(self, instance, args, buildlog, make_args=""):
954
955 self.add_build_goal(instance.name, instance.test.code_location,
956 instance.outdir, args, buildlog, make_args)
957
Anas Nashif3ba1d432017-12-05 15:28:44 -0500958 def add_build_goal(self, name, directory, outdir,
959 args, buildlog, make_args=""):
Andrew Boie6acbe632015-07-17 12:03:52 -0700960 """Add a goal to invoke a Kbuild session
961
962 @param name A unique string name for this build goal. The results
963 dictionary returned by execute() will be keyed by this name.
964 @param directory Absolute path to working directory, will be passed
965 to make -C
966 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +0100967 cmake via -B=<path>
968 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -0700969 environment variables or specific Make goals
970 """
971 self._add_goal(outdir)
Kumar Gala914d92e2017-06-22 16:19:11 -0500972 build_logfile = os.path.join(outdir, buildlog)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500973 text = (
974 self._get_rule_header(name) +
975 self._get_sub_make(
976 name,
977 "building",
978 directory,
979 outdir,
980 build_logfile,
981 args,
982 make_args=make_args) +
983 self._get_rule_footer(name))
984 self.goals[name] = MakeGoal(
985 name,
986 text,
987 None,
988 self.logfile,
989 build_logfile,
990 None,
991 None)
Andrew Boie6acbe632015-07-17 12:03:52 -0700992
Anas Nashif576be982017-12-23 20:20:27 -0500993 def add_qemu_goal(self, instance, args):
Andrew Boie6acbe632015-07-17 12:03:52 -0700994 """Add a goal to build a Zephyr project and then run it under QEMU
995
996 The generated make goal invokes Make twice, the first time it will
997 build the default goal, and the second will invoke the 'qemu' goal.
998 The output of the QEMU session will be monitored, and terminated
999 either upon pass/fail result of the test program, or the timeout
1000 is reached.
1001
1002 @param name A unique string name for this build goal. The results
1003 dictionary returned by execute() will be keyed by this name.
1004 @param directory Absolute path to working directory, will be passed
1005 to make -C
1006 @param outdir Absolute path to output directory, will be passed to
1007 Kbuild via -O=<path>
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001008 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001009 @param timeout Maximum length of time QEMU session should be allowed
1010 to run before automatically killing it. Default is 30 seconds.
1011 """
1012
Anas Nashif576be982017-12-23 20:20:27 -05001013 name = instance.name
1014 directory = instance.test.code_location
1015 outdir = instance.outdir
1016
Andrew Boie6acbe632015-07-17 12:03:52 -07001017 build_logfile = os.path.join(outdir, "build.log")
1018 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001019 handler_logfile = os.path.join(outdir, "handler.log")
Anas Nashif576be982017-12-23 20:20:27 -05001020 self._add_goal(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001021
Anas Nashif576be982017-12-23 20:20:27 -05001022 qemu_handler = QEMUHandler(instance)
Anas Nashif4d25b502017-11-25 17:37:17 -05001023 args.append("QEMU_PIPE=%s" % qemu_handler.get_fifo())
Andrew Boie6acbe632015-07-17 12:03:52 -07001024 text = (self._get_rule_header(name) +
1025 self._get_sub_make(name, "building", directory,
1026 outdir, build_logfile, args) +
1027 self._get_sub_make(name, "running", directory,
1028 outdir, run_logfile,
Anas Nashiffb91ad62017-10-31 08:33:17 -04001029 args, make_args="run") +
Andrew Boie6acbe632015-07-17 12:03:52 -07001030 self._get_rule_footer(name))
Anas Nashif4d25b502017-11-25 17:37:17 -05001031 self.goals[name] = MakeGoal(name, text, qemu_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001032 run_logfile, handler_logfile)
Andrew Boie6acbe632015-07-17 12:03:52 -07001033
Anas Nashif37f9dc52018-02-23 08:53:46 -06001034 def add_unit_goal(self, instance, args, timeout=30):
Anas Nashif576be982017-12-23 20:20:27 -05001035 outdir = instance.outdir
1036 timeout = instance.test.timeout
1037 name = instance.name
1038 directory = instance.test.code_location
1039
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001040 self._add_goal(outdir)
1041 build_logfile = os.path.join(outdir, "build.log")
1042 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001043 handler_logfile = os.path.join(outdir, "handler.log")
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01001044
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001045 if options.enable_coverage:
1046 args += ["COVERAGE=1", "EXTRA_LDFLAGS=--coverage"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001047
1048 # we handle running in the UnitHandler class
1049 text = (self._get_rule_header(name) +
1050 self._get_sub_make(name, "building", directory,
1051 outdir, build_logfile, args) +
1052 self._get_rule_footer(name))
Anas Nashif576be982017-12-23 20:20:27 -05001053 unit_handler = UnitHandler(instance)
Anas Nashif4d25b502017-11-25 17:37:17 -05001054 self.goals[name] = MakeGoal(name, text, unit_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001055 run_logfile, handler_logfile)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001056
Anas Nashif37f9dc52018-02-23 08:53:46 -06001057 def add_native_goal(self, instance, args):
Anas Nashif576be982017-12-23 20:20:27 -05001058
1059 outdir = instance.outdir
1060 timeout = instance.test.timeout
1061 name = instance.name
1062 directory = instance.test.code_location
1063
Anas Nashifcc164222017-12-26 11:02:46 -05001064 self._add_goal(outdir)
1065 build_logfile = os.path.join(outdir, "build.log")
1066 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001067 handler_logfile = os.path.join(outdir, "handler.log")
Anas Nashifcc164222017-12-26 11:02:46 -05001068
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001069 if options.enable_coverage:
1070 args += ["CONFIG_COVERAGE=y"]
1071
Anas Nashifcc164222017-12-26 11:02:46 -05001072 # we handle running in the NativeHandler class
1073 text = (self._get_rule_header(name) +
1074 self._get_sub_make(name, "building", directory,
1075 outdir, build_logfile, args) +
1076 self._get_rule_footer(name))
Anas Nashif576be982017-12-23 20:20:27 -05001077 native_handler = NativeHandler(instance)
Anas Nashifcc164222017-12-26 11:02:46 -05001078 self.goals[name] = MakeGoal(name, text, native_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001079 run_logfile, handler_logfile)
Anas Nashif4d25b502017-11-25 17:37:17 -05001080
Anas Nashif73440ea2018-02-19 10:57:03 -06001081 def add_device_goal(self, instance, args):
1082
1083 outdir = instance.outdir
1084 timeout = instance.test.timeout
1085 name = instance.name
1086 directory = instance.test.code_location
1087
1088 self._add_goal(outdir)
1089 build_logfile = os.path.join(outdir, "build.log")
1090 run_logfile = os.path.join(outdir, "run.log")
1091 handler_logfile = os.path.join(outdir, "handler.log")
1092
1093 # we handle running in the NativeHandler class
1094 text = (self._get_rule_header(name) +
1095 self._get_sub_make(name, "building", directory,
1096 outdir, build_logfile, args) +
1097 self._get_rule_footer(name))
1098 handler = DeviceHandler(instance)
1099 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
1100 run_logfile, handler_logfile)
1101
Anas Nashif37f9dc52018-02-23 08:53:46 -06001102 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001103 """Add a goal to build/test a TestInstance object
1104
1105 @param ti TestInstance object to build. The status dictionary returned
1106 by execute() will be keyed by its .name field.
1107 """
1108 args = ti.test.extra_args[:]
Anas Nashiffa695d22017-10-04 16:14:27 -04001109 if len(ti.test.extra_configs) > 0:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001110 args.append("OVERLAY_CONFIG=%s" %
1111 os.path.join(ti.outdir, "overlay.conf"))
Anas Nashiffa695d22017-10-04 16:14:27 -04001112
Anas Nashiffb91ad62017-10-31 08:33:17 -04001113 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001114 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001115
Anas Nashif37f9dc52018-02-23 08:53:46 -06001116 do_run_slow = options.enable_slow or not ti.test.slow
1117 do_build_only = ti.build_only or options.build_only
Anas Nashif5df8cff2018-02-23 08:37:14 -06001118 do_run = (not do_build_only) and do_run_slow
1119
Anas Nashif5df8cff2018-02-23 08:37:14 -06001120 if ti.platform.qemu_support and do_run:
Anas Nashif576be982017-12-23 20:20:27 -05001121 self.add_qemu_goal(ti, args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001122
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001123 elif ti.test.type == "unit":
Anas Nashif37f9dc52018-02-23 08:53:46 -06001124 self.add_unit_goal(ti, args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001125
1126 elif ti.platform.type == "native" and do_run:
Anas Nashif37f9dc52018-02-23 08:53:46 -06001127 self.add_native_goal(ti, args)
1128
1129 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif73440ea2018-02-19 10:57:03 -06001130 self.add_device_goal(ti, args)
Anas Nashif37f9dc52018-02-23 08:53:46 -06001131
Andrew Boie6acbe632015-07-17 12:03:52 -07001132 else:
Anas Nashif576be982017-12-23 20:20:27 -05001133 self.add_instance_build_goal(ti, args, "build.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001134
1135 def execute(self, callback_fn=None, context=None):
1136 """Execute all the registered build goals
1137
1138 @param callback_fn If not None, a callback function will be called
1139 as individual goals transition between states. This function
1140 should accept two parameters: a string state and an arbitrary
1141 context object, supplied here
1142 @param context Context object to pass to the callback function.
1143 Type and semantics are specific to that callback function.
1144 @return A dictionary mapping goal names to final status.
1145 """
1146
Andrew Boie08ce5a52016-02-22 13:28:10 -08001147 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001148 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001149 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001150 # Create our dynamic Makefile and execute it.
1151 # Watch stderr output which is where we will keep
1152 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -08001153 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001154 tf.write(goal.text)
1155 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
1156 tf.flush()
1157
Andy Rossdec163f2018-05-21 10:12:59 -07001158 # Normally spawn 2x as many processes as CPUs. Ninja,
1159 # though, seems to have significant parallelism internally
1160 # and results in rather high loads, so use 1.5x there to
1161 # match what we see with GNU make.
1162 loadmul = 2
1163 if options.ninja:
1164 loadmul = 1.5
1165
Anas Nashif3ba1d432017-12-05 15:28:44 -05001166 cmd = ["make", "-k", "-j",
Andy Rossdec163f2018-05-21 10:12:59 -07001167 str(int(CPU_COUNTS * loadmul)), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001168 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1169 stdout=devnull)
1170
1171 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001172 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001173 make_log.write(line)
1174 verbose("MAKE: " + repr(line.strip()))
1175 m = MakeGenerator.re_make.match(line)
1176 if not m:
1177 continue
1178
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001179 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001180 if error:
1181 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001182 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001183 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001184 # nonzero status.
1185 # Need to distinguish this case from a compilation failure.
Anas Nashif4d25b502017-11-25 17:37:17 -05001186 if goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001187 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001188 else:
1189 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -07001190 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001191 goal = self.goals[name]
1192 goal.make_state = state
1193
Andrew Boie6acbe632015-07-17 12:03:52 -07001194 if state == "finished":
Anas Nashif4d25b502017-11-25 17:37:17 -05001195 if goal.handler:
Anas Nashif576be982017-12-23 20:20:27 -05001196 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001197 goal.handler.handle()
Anas Nashifa49048b2018-01-29 08:41:19 -05001198 goal.handler_log = goal.handler.handler_log
Anas Nashifcc164222017-12-26 11:02:46 -05001199
Anas Nashif4d25b502017-11-25 17:37:17 -05001200 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001201 goal.metrics.update(metrics)
1202 if thread_status == "passed":
1203 goal.success()
1204 else:
1205 goal.fail(thread_status)
1206 else:
1207 goal.success()
1208
1209 if callback_fn:
1210 callback_fn(context, self.goals, goal)
1211
1212 p.wait()
1213 return self.goals
1214
1215
1216# "list" - List of strings
1217# "list:<type>" - List of <type>
1218# "set" - Set of unordered, unique strings
1219# "set:<type>" - Set of <type>
1220# "float" - Floating point
1221# "int" - Integer
1222# "bool" - Boolean
1223# "str" - String
1224
1225# XXX Be sure to update __doc__ if you change any of this!!
1226
Anas Nashif3ba1d432017-12-05 15:28:44 -05001227platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
1228 "supported_toolchains": {"type": "list", "default": []}}
Andrew Boie6acbe632015-07-17 12:03:52 -07001229
Anas Nashif3ba1d432017-12-05 15:28:44 -05001230testcase_valid_keys = {"tags": {"type": "set", "required": False},
1231 "type": {"type": "str", "default": "integration"},
1232 "extra_args": {"type": "list"},
1233 "extra_configs": {"type": "list"},
1234 "build_only": {"type": "bool", "default": False},
1235 "build_on_all": {"type": "bool", "default": False},
1236 "skip": {"type": "bool", "default": False},
1237 "slow": {"type": "bool", "default": False},
1238 "timeout": {"type": "int", "default": 60},
1239 "min_ram": {"type": "int", "default": 8},
1240 "depends_on": {"type": "set"},
1241 "min_flash": {"type": "int", "default": 32},
1242 "arch_whitelist": {"type": "set"},
1243 "arch_exclude": {"type": "set"},
1244 "extra_sections": {"type": "list", "default": []},
1245 "platform_exclude": {"type": "set"},
1246 "platform_whitelist": {"type": "set"},
1247 "toolchain_exclude": {"type": "set"},
1248 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001249 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001250 "harness": {"type": "str"},
1251 "harness_config": {"type": "map"}
Anas Nashifab940162017-12-08 10:17:57 -05001252 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001253
1254
1255class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001256 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001257 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001258
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001259 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001260 """Instantiate a new SanityConfigParser object
1261
Anas Nashifa792a3d2017-04-04 18:47:49 -04001262 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001263 """
Anas Nashif255625b2017-12-05 15:08:26 -05001264 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001265 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001266 self.tests = {}
1267 self.common = {}
1268 if 'tests' in self.data:
1269 self.tests = self.data['tests']
1270 if 'common' in self.data:
1271 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001272
1273 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001274 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001275 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001276 if typestr == "str":
1277 return v
1278
1279 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001280 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001281
1282 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001283 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001284
1285 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001286 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001287
Anas Nashif3ba1d432017-12-05 15:28:44 -05001288 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001289 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001290 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001291 vs = v.split()
1292 if len(typestr) > 4 and typestr[4] == ":":
1293 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1294 else:
1295 return vs
1296
1297 elif typestr.startswith("set"):
1298 vs = v.split()
1299 if len(typestr) > 3 and typestr[3] == ":":
1300 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1301 else:
1302 return set(vs)
1303
Anas Nashif576be982017-12-23 20:20:27 -05001304 elif typestr.startswith("map"):
1305 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001306 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001307 raise ConfigurationError(
1308 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001309
Anas Nashifb4754ed2017-12-05 17:27:58 -05001310 def get_test(self, name, valid_keys):
1311 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001312
Anas Nashifb4754ed2017-12-05 17:27:58 -05001313 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001314 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001315 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001316 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001317 here, it will generate an error. Each value in this dictionary
1318 is another dictionary containing metadata:
1319
1320 "default" - Default value if not given
1321 "type" - Data type to convert the text value to. Simple types
1322 supported are "str", "float", "int", "bool" which will get
1323 converted to respective Python data types. "set" and "list"
1324 may also be specified which will split the value by
1325 whitespace (but keep the elements as strings). finally,
1326 "list:<type>" and "set:<type>" may be given which will
1327 perform a type conversion after splitting the value up.
1328 "required" - If true, raise an error if not defined. If false
1329 and "default" isn't specified, a type conversion will be
1330 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001331 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001332 type conversion and default values filled in per valid_keys
1333 """
1334
1335 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001336 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001337 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001338
Anas Nashifb4754ed2017-12-05 17:27:58 -05001339 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001340 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001341 raise ConfigurationError(
1342 self.filename,
1343 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001344 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001345
Anas Nashiffa695d22017-10-04 16:14:27 -04001346 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001347 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001348 d[k] += " " + v
1349 else:
1350 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001351
Andrew Boie08ce5a52016-02-22 13:28:10 -08001352 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001353 if k not in d:
1354 if "required" in kinfo:
1355 required = kinfo["required"]
1356 else:
1357 required = False
1358
1359 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001360 raise ConfigurationError(
1361 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001362 "missing required value for '%s' in test '%s'" %
1363 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001364 else:
1365 if "default" in kinfo:
1366 default = kinfo["default"]
1367 else:
1368 default = self._cast_value("", kinfo["type"])
1369 d[k] = default
1370 else:
1371 try:
1372 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001373 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001374 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001375 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1376 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001377
1378 return d
1379
1380
1381class Platform:
1382 """Class representing metadata for a particular platform
1383
Anas Nashifc7406082015-12-13 15:00:31 -05001384 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001385
1386 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001387 os.path.join(
1388 os.environ['ZEPHYR_BASE'],
1389 "scripts",
1390 "sanity_chk",
1391 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001392
Anas Nashifa792a3d2017-04-04 18:47:49 -04001393 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001394 """Constructor.
1395
Anas Nashif877d3ca2017-12-05 17:39:29 -05001396 @param cfile Path to platform configuration file, which gives
1397 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001398 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001399 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001400 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001401
Anas Nashif255625b2017-12-05 15:08:26 -05001402 self.name = data['identifier']
Anas Nashifa792a3d2017-04-04 18:47:49 -04001403 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001404 self.ram = data.get("ram", 128)
1405 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001406 self.ignore_tags = testing.get("ignore_tags", [])
1407 self.default = testing.get("default", False)
1408 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001409 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001410 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001411 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001412 for item in supp_feature.split(":"):
1413 self.supported.add(item)
1414
Anas Nashif8acdbd72018-01-04 14:15:22 -05001415 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001416 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001417 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001418 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001419 self.supported_toolchains = data.get("toolchain", [])
Andrew Boie41878222016-11-03 11:58:53 -07001420 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001421 pass
1422
Andrew Boie6acbe632015-07-17 12:03:52 -07001423 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001424 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001425
1426
1427class Architecture:
1428 """Class representing metadata for a particular architecture
1429 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001430
Anas Nashifa792a3d2017-04-04 18:47:49 -04001431 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001432 """Architecture constructor
1433
Anas Nashif877d3ca2017-12-05 17:39:29 -05001434 @param name String name for this architecture
1435 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001436 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001437 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001438
Anas Nashifa792a3d2017-04-04 18:47:49 -04001439 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001440
1441 def __repr__(self):
1442 return "<arch %s>" % self.name
1443
1444
1445class TestCase:
1446 """Class representing a test application
1447 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001448
Anas Nashif7fae29c2017-10-09 13:19:12 -04001449 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001450 """TestCase constructor.
1451
Anas Nashif877d3ca2017-12-05 17:39:29 -05001452 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001453 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001454 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001455
Andrew Boie6acbe632015-07-17 12:03:52 -07001456 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001457 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001458 the test case is <workdir>/<name>.
1459
1460 @param testcase_root Absolute path to the root directory where
1461 all the test cases live
1462 @param workdir Relative path to the project directory for this
1463 test application from the test_case root.
Anas Nashif877d3ca2017-12-05 17:39:29 -05001464 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001465 in the test case configuration file. For many test cases that just
1466 define one test, can be anything and is usually "test". This is
1467 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001468 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001469 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001470 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001471 """
1472 self.code_location = os.path.join(testcase_root, workdir)
Anas Nashifaae71d72018-04-21 22:26:48 -05001473 self.id = name
1474 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001475 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001476 self.tags = tc_dict["tags"]
1477 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001478 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001479 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001480 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001481 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001482 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001483 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001484 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1485 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001486 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001487 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001488 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001489 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001490 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001491 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001492 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001493 self.min_ram = tc_dict["min_ram"]
1494 self.depends_on = tc_dict["depends_on"]
1495 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001496 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001497
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001498 self.path = os.path.normpath(os.path.join(os.path.realpath(
1499 testcase_root).replace(os.path.realpath(ZEPHYR_BASE) + "/", ''),
1500 workdir, name))
1501
Anas Nashifaae71d72018-04-21 22:26:48 -05001502
Anas Nashifbd166f42017-09-02 12:32:08 -04001503 self.name = os.path.join(self.path)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001504 self.defconfig = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001505 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001506
Anas Nashifaae71d72018-04-21 22:26:48 -05001507 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001508 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001509 # do not match until end-of-line, otherwise we won't allow
1510 # stc_regex below to catch the ones that are declared in the same
1511 # line--as we only search starting the end of this match
1512 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001513 re.MULTILINE)
1514 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001515 br"^\s*" # empy space at the beginning is ok
1516 # catch the case where it is declared in the same sentence, e.g:
1517 #
1518 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1519 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1520 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1521 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1522 # Consume the argument that becomes the extra testcse
1523 br"\(\s*"
1524 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1525 # _setup_teardown() variant has two extra arguments that we ignore
1526 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1527 br"\s*\)",
1528 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001529 re.MULTILINE)
1530 suite_run_regex = re.compile(
1531 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1532 re.MULTILINE)
1533 achtung_regex = re.compile(
1534 br"(#ifdef|#endif)",
1535 re.MULTILINE)
1536 warnings = None
1537
1538 with open(inf_name) as inf:
1539 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1540 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001541 suite_regex_match = suite_regex.search(main_c)
1542 if not suite_regex_match:
1543 # can't find ztest_test_suite, maybe a client, because
1544 # it includes ztest.h
1545 return None, None
1546
1547 suite_run_match = suite_run_regex.search(main_c)
1548 if not suite_run_match:
1549 raise ValueError("can't find ztest_run_test_suite")
1550
1551 achtung_matches = re.findall(
1552 achtung_regex,
1553 main_c[suite_regex_match.end():suite_run_match.start()])
1554 if achtung_matches:
1555 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001556 % ", ".join(set([
1557 match.decode() for match in achtung_matches
1558 ]))
1559 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001560 stc_regex,
1561 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001562 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001563 return matches, warnings
1564
1565 def scan_path(self, path):
1566 subcases = []
1567 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1568 try:
1569 _subcases, warnings = self.scan_file(filename)
1570 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001571 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001572 if _subcases:
1573 subcases += _subcases
1574 except ValueError as e:
1575 error("%s: can't find: %s", filename, e)
1576 return subcases
1577
1578
1579 def parse_subcases(self):
1580 results = self.scan_path(self.code_location)
1581 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001582 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001583 self.cases.append(name)
1584
1585
Anas Nashif75547e22018-02-24 08:32:14 -06001586 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001587 return self.name
1588
1589
Andrew Boie6acbe632015-07-17 12:03:52 -07001590class TestInstance:
1591 """Class representing the execution of a particular TestCase on a platform
1592
1593 @param test The TestCase object we want to build/execute
1594 @param platform Platform object that we want to build and run against
1595 @param base_outdir Base directory for all test results. The actual
1596 out directory used is <outdir>/<platform>/<test case name>
1597 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001598
Anas Nashif37f9dc52018-02-23 08:53:46 -06001599 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001600 self.test = test
1601 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001602 self.name = os.path.join(platform.name, test.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001603 self.outdir = os.path.join(base_outdir, platform.name, test.path)
Anas Nashif37f9dc52018-02-23 08:53:46 -06001604 self.build_only = options.build_only or test.build_only or (test.harness and test.harness != 'console')
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001605 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001606
Anas Nashiffa695d22017-10-04 16:14:27 -04001607 def create_overlay(self):
1608 if len(self.test.extra_configs) > 0:
1609 file = os.path.join(self.outdir, "overlay.conf")
1610 os.makedirs(self.outdir, exist_ok=True)
1611 f = open(file, "w")
1612 content = ""
Anas Nashif981f77f2017-10-18 07:53:58 -04001613 content = "\n".join(self.test.extra_configs)
Anas Nashiffa695d22017-10-04 16:14:27 -04001614 f.write(content)
1615 f.close()
1616
Andrew Boie6acbe632015-07-17 12:03:52 -07001617 def calculate_sizes(self):
1618 """Get the RAM/ROM sizes of a test case.
1619
1620 This can only be run after the instance has been executed by
1621 MakeGenerator, otherwise there won't be any binaries to measure.
1622
1623 @return A SizeCalculator object
1624 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001625 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001626 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001627 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001628 if (len(fns) != 1):
1629 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001630 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001631
1632 def __repr__(self):
1633 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1634
1635
Andrew Boie4ef16c52015-08-28 12:36:03 -07001636def defconfig_cb(context, goals, goal):
1637 if not goal.failed:
1638 return
1639
1640 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001641 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001642 if INLINE_LOGS:
1643 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001644 data = fp.read()
1645 sys.stdout.write(data)
1646 if log_file:
1647 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001648 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001649 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001650
Andrew Boie6acbe632015-07-17 12:03:52 -07001651
1652class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001653 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001654
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001655 yaml_tc_schema = scl.yaml_load(
1656 os.path.join(os.environ['ZEPHYR_BASE'],
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001657 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001658
Anas Nashif37f9dc52018-02-23 08:53:46 -06001659 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001660 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001661 self.arches = {}
1662 self.testcases = {}
1663 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001664 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001665 self.instances = {}
1666 self.goals = None
1667 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001668 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001669
Andrew Boie3d348712016-04-08 11:52:13 -07001670 for testcase_root in testcase_roots:
1671 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001672
Andrew Boie3d348712016-04-08 11:52:13 -07001673 debug("Reading test case configuration files under %s..." %
1674 testcase_root)
1675 for dirpath, dirnames, filenames in os.walk(testcase_root,
1676 topdown=True):
1677 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001678 if 'sample.yaml' in filenames:
1679 filename = 'sample.yaml'
1680 elif 'testcase.yaml' in filenames:
1681 filename = 'testcase.yaml'
1682 else:
1683 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001684
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001685 verbose("Found possible test case in " + dirpath)
1686 dirnames[:] = []
1687 yaml_path = os.path.join(dirpath, filename)
1688 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001689 parsed_data = SanityConfigParser(
1690 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001691
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001692 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001693
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001694 for name in parsed_data.tests.keys():
1695 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1696 tc = TestCase(testcase_root, workdir, name, tc_dict,
1697 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001698 tc.parse_subcases()
Anas Nashif7fae29c2017-10-09 13:19:12 -04001699
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001700 self.testcases[tc.name] = tc
1701
1702 except Exception as e:
1703 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001704 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001705
Andrew Boie6acbe632015-07-17 12:03:52 -07001706
Anas Nashif86c8e232017-10-09 13:42:28 -04001707 for board_root in board_root_list:
1708 board_root = os.path.abspath(board_root)
1709
Anas Nashif3ba1d432017-12-05 15:28:44 -05001710 debug(
1711 "Reading platform configuration files under %s..." %
1712 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001713 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1714 verbose("Found plaform configuration " + fn)
1715 try:
1716 platform = Platform(fn)
1717 self.platforms.append(platform)
1718 except RuntimeError as e:
1719 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001720 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001721
Anas Nashifa792a3d2017-04-04 18:47:49 -04001722 arches = []
1723 for p in self.platforms:
1724 arches.append(p.arch)
1725 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001726 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001727 arch = Architecture(a, aplatforms)
1728 self.arches[a] = arch
1729
Andrew Boie6acbe632015-07-17 12:03:52 -07001730 self.instances = {}
1731
1732 def get_last_failed(self):
1733 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001734 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001735 result = []
1736 with open(LAST_SANITY, "r") as fp:
1737 cr = csv.DictReader(fp)
1738 for row in cr:
1739 if row["passed"] == "True":
1740 continue
1741 test = row["test"]
1742 platform = row["platform"]
1743 result.append((test, platform))
1744 return result
1745
Anas Nashifbd166f42017-09-02 12:32:08 -04001746 def load_from_file(self, file):
1747 if not os.path.exists(file):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001748 raise SanityRuntimeError(
1749 "Couldn't find input file with list of tests.")
Anas Nashifbd166f42017-09-02 12:32:08 -04001750 with open(file, "r") as fp:
1751 cr = csv.reader(fp)
1752 instance_list = []
1753 for row in cr:
1754 name = os.path.join(row[0], row[1])
1755 platforms = self.arches[row[3]].platforms
1756 myp = None
1757 for p in platforms:
1758 if p.name == row[2]:
1759 myp = p
1760 break
1761 instance = TestInstance(self.testcases[name], myp, self.outdir)
Anas Nashiffa695d22017-10-04 16:14:27 -04001762 instance.create_overlay()
Anas Nashifbd166f42017-09-02 12:32:08 -04001763 instance_list.append(instance)
1764 self.add_instances(instance_list)
1765
Anas Nashif4f028882017-12-30 11:48:43 -05001766 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04001767
Anas Nashif7fe35cf2018-02-15 07:20:18 -06001768 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
1769 os.environ.get("ZEPHYR_GCC_VARIANT", None)
1770 if not toolchain:
1771 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
1772
1773
Andrew Boie6acbe632015-07-17 12:03:52 -07001774 instances = []
1775 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05001776 platform_filter = options.platform
1777 last_failed = options.only_failed
1778 testcase_filter = options.test
1779 arch_filter = options.arch
1780 tag_filter = options.tag
1781 exclude_tag = options.exclude_tag
1782 config_filter = options.config
1783 extra_args = options.extra_args
1784 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04001785
Andrew Boie6acbe632015-07-17 12:03:52 -07001786 verbose("platform filter: " + str(platform_filter))
1787 verbose(" arch_filter: " + str(arch_filter))
1788 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001789 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001790 verbose(" config_filter: " + str(config_filter))
1791
1792 if last_failed:
1793 failed_tests = self.get_last_failed()
1794
Andrew Boie821d8322016-03-22 10:08:35 -07001795 default_platforms = False
1796
1797 if all_plats:
1798 info("Selecting all possible platforms per test case")
1799 # When --all used, any --platform arguments ignored
1800 platform_filter = []
1801 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001802 info("Selecting default platforms per test case")
1803 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001804
Sebastian Bøe781e3982017-11-09 11:43:33 +01001805 mg = MakeGenerator(self.outdir)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001806 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001807 for tc_name, tc in self.testcases.items():
1808 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001809 for plat in arch.platforms:
1810 instance = TestInstance(tc, plat, self.outdir)
1811
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001812 if (arch_name == "unit") != (tc.type == "unit"):
1813 continue
1814
Anas Nashifbfab06b2017-06-22 09:22:24 -04001815 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001816 platform_filter = []
1817
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001818 if tc.skip:
1819 continue
1820
Anas Nashif2cf0df02015-10-10 09:29:43 -04001821 if tag_filter and not tc.tags.intersection(tag_filter):
1822 continue
1823
Anas Nashifdfa86e22016-10-24 17:08:56 -04001824 if exclude_tag and tc.tags.intersection(exclude_tag):
1825 continue
1826
Anas Nashif2cf0df02015-10-10 09:29:43 -04001827 if testcase_filter and tc_name not in testcase_filter:
1828 continue
1829
Anas Nashif3ba1d432017-12-05 15:28:44 -05001830 if last_failed and (
1831 tc.name, plat.name) not in failed_tests:
Anas Nashif2cf0df02015-10-10 09:29:43 -04001832 continue
1833
1834 if arch_filter and arch_name not in arch_filter:
1835 continue
1836
1837 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1838 continue
1839
1840 if tc.arch_exclude and arch.name in tc.arch_exclude:
1841 continue
1842
1843 if tc.platform_exclude and plat.name in tc.platform_exclude:
1844 continue
1845
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001846 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1847 continue
1848
Anas Nashif2cf0df02015-10-10 09:29:43 -04001849 if platform_filter and plat.name not in platform_filter:
1850 continue
1851
Anas Nashif62224182017-08-09 23:55:53 -04001852 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001853 continue
1854
1855 if set(plat.ignore_tags) & tc.tags:
1856 continue
1857
Kumar Gala5141d522017-07-07 08:05:48 -05001858 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001859 dep_intersection = tc.depends_on.intersection(
1860 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001861 if dep_intersection != set(tc.depends_on):
1862 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001863
1864 if plat.flash < tc.min_flash:
1865 continue
1866
Anas Nashif2cf0df02015-10-10 09:29:43 -04001867 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1868 continue
1869
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001870 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1871 continue
1872
Anas Nashif3ba1d432017-12-05 15:28:44 -05001873 if (tc.tc_filter and (plat.default or all_plats or platform_filter)
1874 and toolchain in plat.supported_toolchains):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001875 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04001876 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07001877 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001878 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001879 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07001880 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05001881 # each other since they all try to build them
1882 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04001883
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001884 if plat.type == "native" and options.enable_coverage:
1885 args.append("CONFIG_COVERAGE=y")
1886
Anas Nashif2cf0df02015-10-10 09:29:43 -04001887 o = os.path.join(self.outdir, plat.name, tc.path)
Anas Nashif3ba1d432017-12-05 15:28:44 -05001888 dlist[tc, plat, tc.name.split(
1889 "/")[-1]] = os.path.join(o, "zephyr", ".config")
1890 goal = "_".join([plat.name, "_".join(
1891 tc.name.split("/")), "config-sanitycheck"])
Anas Nashif576be982017-12-23 20:20:27 -05001892 mg.add_build_goal(goal,
1893 os.path.join(ZEPHYR_BASE, tc.code_location),
1894 o, args,
1895 "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04001896
1897 info("Building testcase defconfigs...")
1898 results = mg.execute(defconfig_cb)
1899
Andrew Boie08ce5a52016-02-22 13:28:10 -08001900 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001901 if goal.failed:
1902 raise SanityRuntimeError("Couldn't build some defconfigs")
1903
Andrew Boie08ce5a52016-02-22 13:28:10 -08001904 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001905 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001906 defconfig = {}
1907 with open(out_config, "r") as fp:
1908 for line in fp.readlines():
1909 m = TestSuite.config_re.match(line)
1910 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001911 if line.strip() and not line.startswith("#"):
1912 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001913 continue
1914 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001915 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001916
Andrew Boie08ce5a52016-02-22 13:28:10 -08001917 for tc_name, tc in self.testcases.items():
1918 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001919 instance_list = []
1920 for plat in arch.platforms:
1921 instance = TestInstance(tc, plat, self.outdir)
1922
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001923 if (arch_name == "unit") != (tc.type == "unit"):
1924 # Discard silently
1925 continue
1926
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001927 if tc.skip:
1928 discards[instance] = "Skip filter"
1929 continue
1930
Anas Nashifbfab06b2017-06-22 09:22:24 -04001931 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001932 platform_filter = []
1933
Andrew Boie6acbe632015-07-17 12:03:52 -07001934 if tag_filter and not tc.tags.intersection(tag_filter):
1935 discards[instance] = "Command line testcase tag filter"
1936 continue
1937
Anas Nashifdfa86e22016-10-24 17:08:56 -04001938 if exclude_tag and tc.tags.intersection(exclude_tag):
1939 discards[instance] = "Command line testcase exclude filter"
1940 continue
1941
Andrew Boie6acbe632015-07-17 12:03:52 -07001942 if testcase_filter and tc_name not in testcase_filter:
1943 discards[instance] = "Testcase name filter"
1944 continue
1945
Anas Nashif3ba1d432017-12-05 15:28:44 -05001946 if last_failed and (
1947 tc.name, plat.name) not in failed_tests:
Andrew Boie6acbe632015-07-17 12:03:52 -07001948 discards[instance] = "Passed or skipped during last run"
1949 continue
1950
1951 if arch_filter and arch_name not in arch_filter:
1952 discards[instance] = "Command line testcase arch filter"
1953 continue
1954
1955 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1956 discards[instance] = "Not in test case arch whitelist"
1957 continue
1958
Anas Nashif30d13872015-10-05 10:02:45 -04001959 if tc.arch_exclude and arch.name in tc.arch_exclude:
1960 discards[instance] = "In test case arch exclude"
1961 continue
1962
1963 if tc.platform_exclude and plat.name in tc.platform_exclude:
1964 discards[instance] = "In test case platform exclude"
1965 continue
1966
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001967 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1968 discards[instance] = "In test case toolchain exclude"
1969 continue
1970
Andrew Boie6acbe632015-07-17 12:03:52 -07001971 if platform_filter and plat.name not in platform_filter:
1972 discards[instance] = "Command line platform filter"
1973 continue
1974
1975 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1976 discards[instance] = "Not in testcase platform whitelist"
1977 continue
1978
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001979 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1980 discards[instance] = "Not in testcase toolchain whitelist"
1981 continue
1982
Anas Nashif86c8e232017-10-09 13:42:28 -04001983 if toolchain and toolchain not in plat.supported_toolchains and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05001984 discards[instance] = "Not supported by the toolchain"
1985 continue
1986
Anas Nashif62224182017-08-09 23:55:53 -04001987 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001988 discards[instance] = "Not enough RAM"
1989 continue
1990
Kumar Gala5141d522017-07-07 08:05:48 -05001991 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001992 dep_intersection = tc.depends_on.intersection(
1993 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001994 if dep_intersection != set(tc.depends_on):
1995 discards[instance] = "No hardware support"
1996 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001997
Anas Nashif62224182017-08-09 23:55:53 -04001998 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001999 discards[instance] = "Not enough FLASH"
2000 continue
2001
2002 if set(plat.ignore_tags) & tc.tags:
2003 discards[instance] = "Excluded tags per platform"
2004 continue
2005
Anas Nashif674bb282018-01-09 09:12:15 -05002006 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05002007 "ARCH": arch.name,
2008 "PLATFORM": plat.name
2009 }
Javier B Perez79414542016-08-08 12:24:59 -05002010 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002011 for p, tdefconfig in tc.defconfig.items():
2012 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002013 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002014 break
2015
Andrew Boie3ea78922016-03-24 14:46:00 -07002016 if tc.tc_filter:
2017 try:
2018 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002019 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002020 sys.stderr.write(
2021 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002022 raise se
2023 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002024 discards[instance] = (
2025 "defconfig doesn't satisfy expression '%s'" %
2026 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002027 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002028
Andrew Boie6acbe632015-07-17 12:03:52 -07002029 instance_list.append(instance)
2030
2031 if not instance_list:
2032 # Every platform in this arch was rejected already
2033 continue
2034
Anas Nashifa792a3d2017-04-04 18:47:49 -04002035 if default_platforms and not tc.build_on_all:
2036 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002037 instances = list(
2038 filter(
2039 lambda tc: tc.platform.default,
2040 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002041 self.add_instances(instances)
2042 else:
Anas Nashifab747062017-12-05 17:59:01 -05002043 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002044
Anas Nashif3ba1d432017-12-05 15:28:44 -05002045 for instance in list(
2046 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002047 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002048 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002049 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002050
2051 for name, case in self.instances.items():
2052 case.create_overlay()
2053
Andrew Boie6acbe632015-07-17 12:03:52 -07002054 self.discards = discards
2055 return discards
2056
Andrew Boie821d8322016-03-22 10:08:35 -07002057 def add_instances(self, ti_list):
2058 for ti in ti_list:
2059 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002060
Anas Nashif37f9dc52018-02-23 08:53:46 -06002061 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002062
2063 def calc_one_elf_size(name, goal):
2064 if not goal.failed:
2065 i = self.instances[name]
2066 sc = i.calculate_sizes()
2067 goal.metrics["ram_size"] = sc.get_ram_size()
2068 goal.metrics["rom_size"] = sc.get_rom_size()
2069 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07002070
Anas Nashif37f9dc52018-02-23 08:53:46 -06002071 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002072 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002073 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002074 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002075
2076 # Parallelize size calculation
2077 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
Anas Nashif3ba1d432017-12-05 15:28:44 -05002078 futures = [executor.submit(calc_one_elf_size, name, goal)
2079 for name, goal in self.goals.items()]
Daniel Leung6b170072016-04-07 12:10:25 -07002080 concurrent.futures.wait(futures)
2081
Andrew Boie6acbe632015-07-17 12:03:52 -07002082 return self.goals
2083
Anas Nashifbd166f42017-09-02 12:32:08 -04002084 def run_report(self, filename):
2085 with open(filename, "at") as csvfile:
2086 fieldnames = ['path', 'test', 'platform', 'arch']
2087 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2088 for instance in self.instances.values():
2089 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002090 "path": os.path.dirname(instance.test.name),
2091 "test": os.path.basename(instance.test.name),
2092 "platform": instance.platform.name,
2093 "arch": instance.platform.arch
2094 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002095 cw.writerow(rowdict)
2096
Andrew Boie6acbe632015-07-17 12:03:52 -07002097 def discard_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002098 if self.discards is None:
2099 raise SanityRuntimeError("apply_filters() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002100
Anas Nashifbd166f42017-09-02 12:32:08 -04002101 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002102 fieldnames = ["test", "arch", "platform", "reason"]
2103 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2104 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002105 for instance, reason in self.discards.items():
Anas Nashif3ba1d432017-12-05 15:28:44 -05002106 rowdict = {"test": instance.test.name,
2107 "arch": instance.platform.arch,
2108 "platform": instance.platform.name,
2109 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002110 cw.writerow(rowdict)
2111
2112 def compare_metrics(self, filename):
2113 # name, datatype, lower results better
2114 interesting_metrics = [("ram_size", int, True),
2115 ("rom_size", int, True)]
2116
Anas Nashif3ba1d432017-12-05 15:28:44 -05002117 if self.goals is None:
2118 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002119
2120 if not os.path.exists(filename):
2121 info("Cannot compare metrics, %s not found" % filename)
2122 return []
2123
2124 results = []
2125 saved_metrics = {}
2126 with open(filename) as fp:
2127 cr = csv.DictReader(fp)
2128 for row in cr:
2129 d = {}
2130 for m, _, _ in interesting_metrics:
2131 d[m] = row[m]
2132 saved_metrics[(row["test"], row["platform"])] = d
2133
Andrew Boie08ce5a52016-02-22 13:28:10 -08002134 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002135 i = self.instances[name]
2136 mkey = (i.test.name, i.platform.name)
2137 if mkey not in saved_metrics:
2138 continue
2139 sm = saved_metrics[mkey]
2140 for metric, mtype, lower_better in interesting_metrics:
2141 if metric not in goal.metrics:
2142 continue
2143 if sm[metric] == "":
2144 continue
2145 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002146 if delta == 0:
2147 continue
2148 results.append((i, metric, goal.metrics[metric], delta,
2149 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002150 return results
2151
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002152
2153
2154 def encode_for_xml(self, unicode_data, encoding='ascii'):
2155 unicode_data = unicode_data.replace('\x00', '')
2156 return unicode_data
2157
2158 def testcase_target_report(self, report_file):
2159
2160 run = "Sanitycheck"
2161 eleTestsuite = None
2162 append = options.only_failed
2163
2164 errors = 0
2165 passes = 0
2166 fails = 0
2167 duration = 0
2168 skips = 0
2169
2170 for identifier, ti in self.instances.items():
2171 for k in ti.results.keys():
2172 if ti.results[k] == 'PASS':
2173 passes += 1
2174 elif ti.results[k] == 'BLOCK':
2175 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002176 elif ti.results[k] == 'SKIP':
2177 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002178 else:
2179 fails += 1
2180
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002181 eleTestsuites = ET.Element('testsuites')
2182 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2183 name=run, time="%d" % duration,
2184 tests="%d" % (errors + passes + fails),
2185 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002186 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002187
2188 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002189
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002190 # print out test results
2191 for identifier, ti in self.instances.items():
2192 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002193
2194 eleTestcase = ET.SubElement(
2195 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002196 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002197 if ti.results[k] in ['FAIL', 'BLOCK']:
2198 el = None
2199
2200 if ti.results[k] == 'FAIL':
2201 el = ET.SubElement(
2202 eleTestcase,
2203 'failure',
2204 type="failure",
2205 message="failed")
2206 elif ti.results[k] == 'BLOCK':
2207 el = ET.SubElement(
2208 eleTestcase,
2209 'error',
2210 type="failure",
2211 message="failed")
2212 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
2213 bl = os.path.join(p, "handler.log")
2214
2215 if os.path.exists(bl):
2216 with open(bl, "rb") as f:
2217 log = f.read().decode("utf-8")
2218 el.text = self.encode_for_xml(log)
2219
Anas Nashif61e21632018-04-08 13:30:16 -05002220 elif ti.results[k] == 'SKIP':
2221 el = ET.SubElement(
2222 eleTestcase,
2223 'skipped')
2224
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002225 result = ET.tostring(eleTestsuites)
2226 f = open(report_file, 'wb')
2227 f.write(result)
2228 f.close()
2229
2230
Anas Nashif4f028882017-12-30 11:48:43 -05002231 def testcase_xunit_report(self, filename, duration):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002232 if self.goals is None:
2233 raise SanityRuntimeError("execute() hasn't been run!")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002234
2235 fails = 0
2236 passes = 0
2237 errors = 0
2238
2239 for name, goal in self.goals.items():
2240 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002241 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002242 errors += 1
2243 else:
2244 fails += 1
2245 else:
2246 passes += 1
2247
2248 run = "Sanitycheck"
2249 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002250 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002251
Anas Nashif0605fa32017-05-07 08:51:02 -04002252 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002253 tree = ET.parse(filename)
2254 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002255 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002256 else:
2257 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002258 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2259 name=run, time="%d" % duration,
2260 tests="%d" % (errors + passes + fails),
2261 failures="%d" % fails,
2262 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002263
Anas Nashifc8390f12017-11-25 17:14:12 -05002264 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002265 for name, goal in self.goals.items():
2266
2267 i = self.instances[name]
2268 if append:
2269 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002270 if tc.get('classname') == "%s:%s" % (
2271 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002272 eleTestsuite.remove(tc)
2273
Anas Nashif4d25b502017-11-25 17:37:17 -05002274 if not goal.failed and goal.handler:
2275 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002276
Anas Nashif3ba1d432017-12-05 15:28:44 -05002277 eleTestcase = ET.SubElement(
2278 eleTestsuite, 'testcase', classname="%s:%s" %
2279 (i.platform.name, i.test.name), name="%s" %
2280 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002281 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002282 failure = ET.SubElement(
2283 eleTestcase,
2284 'failure',
2285 type="failure",
2286 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002287 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002288 bl = os.path.join(p, "build.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002289 if goal.reason != 'build_error':
Anas Nashifa49048b2018-01-29 08:41:19 -05002290 bl = os.path.join(p, "handler.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002291
Anas Nashifb3311ed2017-04-13 14:44:48 -04002292 if os.path.exists(bl):
Anas Nashif712d3452017-12-29 22:09:03 -05002293 with open(bl, "rb") as f:
2294 log = f.read().decode("utf-8")
2295 failure.text = log
Anas Nashifb3311ed2017-04-13 14:44:48 -04002296
2297 result = ET.tostring(eleTestsuites)
2298 f = open(filename, 'wb')
2299 f.write(result)
2300 f.close()
2301
Andrew Boie6acbe632015-07-17 12:03:52 -07002302 def testcase_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002303 if self.goals is None:
2304 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002305
Andrew Boie08ce5a52016-02-22 13:28:10 -08002306 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002307 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002308 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002309 "rom_size"]
2310 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2311 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002312 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002313 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002314 rowdict = {"test": i.test.name,
2315 "arch": i.platform.arch,
2316 "platform": i.platform.name,
2317 "extra_args": " ".join(i.test.extra_args),
2318 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002319 if goal.failed:
2320 rowdict["passed"] = False
2321 rowdict["status"] = goal.reason
2322 else:
2323 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002324 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002325 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002326 rowdict["ram_size"] = goal.metrics["ram_size"]
2327 rowdict["rom_size"] = goal.metrics["rom_size"]
2328 cw.writerow(rowdict)
2329
2330
2331def parse_arguments():
2332
Anas Nashif3ba1d432017-12-05 15:28:44 -05002333 parser = argparse.ArgumentParser(
2334 description=__doc__,
2335 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002336 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002337
Anas Nashif3ba1d432017-12-05 15:28:44 -05002338 parser.add_argument(
2339 "-p", "--platform", action="append",
2340 help="Platform filter for testing. This option may be used multiple "
2341 "times. Testcases will only be built/run on the platforms "
2342 "specified. If this option is not used, then platforms marked "
2343 "as default in the platform metadata file will be chosen "
2344 "to build and test. ")
2345 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002346 "-a", "--arch", action="append",
2347 help="Arch filter for testing. Takes precedence over --platform. "
2348 "If unspecified, test all arches. Multiple invocations "
2349 "are treated as a logical 'or' relationship")
2350 parser.add_argument(
2351 "-t", "--tag", action="append",
2352 help="Specify tags to restrict which tests to run by tag value. "
2353 "Default is to not do any tag filtering. Multiple invocations "
2354 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002355 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002356 help="Specify tags of tests that should not run. "
2357 "Default is to run all tests with all tags.")
2358 parser.add_argument(
2359 "-f",
2360 "--only-failed",
2361 action="store_true",
2362 help="Run only those tests that failed the previous sanity check "
2363 "invocation.")
2364 parser.add_argument(
2365 "-c", "--config", action="append",
2366 help="Specify platform configuration values filtering. This can be "
2367 "specified two ways: <config>=<value> or just <config>. The "
2368 "defconfig for all platforms will be "
2369 "checked. For the <config>=<value> case, only match defconfig "
2370 "that have that value defined. For the <config> case, match "
2371 "defconfig that have that value assigned to any value. "
2372 "Prepend a '!' to invert the match.")
2373 parser.add_argument(
2374 "-s", "--test", action="append",
2375 help="Run only the specified test cases. These are named by "
2376 "<path to test project relative to "
2377 "--testcase-root>/<testcase.yaml section name>")
2378 parser.add_argument(
2379 "-l", "--all", action="store_true",
2380 help="Build/test on all platforms. Any --platform arguments "
2381 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002382
Anas Nashif3ba1d432017-12-05 15:28:44 -05002383 parser.add_argument(
2384 "-o", "--testcase-report",
2385 help="Output a CSV spreadsheet containing results of the test run")
2386 parser.add_argument(
2387 "-d", "--discard-report",
2388 help="Output a CSV spreadsheet showing tests that were skipped "
2389 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002390 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002391 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002392
Anas Nashif3ba1d432017-12-05 15:28:44 -05002393 parser.add_argument(
2394 "-B", "--subset",
2395 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2396 "3/5 means run the 3rd fifth of the total. "
2397 "This option is useful when running a large number of tests on "
2398 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002399
2400 parser.add_argument(
2401 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002402 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002403
Anas Nashif3ba1d432017-12-05 15:28:44 -05002404 parser.add_argument(
2405 "-y", "--dry-run", action="store_true",
2406 help="Create the filtered list of test cases, but don't actually "
2407 "run them. Useful if you're just interested in "
2408 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002409
Anas Nashif75547e22018-02-24 08:32:14 -06002410 parser.add_argument("--list-tags", action="store_true",
2411 help="list all tags in selected tests")
2412
Anas Nashifc0149cc2018-04-14 23:12:58 -05002413 parser.add_argument("--list-tests", action="store_true",
2414 help="list all tests.")
2415
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002416 parser.add_argument("--export-tests", action="store",
2417 metavar="FILENAME",
2418 help="Export tests case meta-data to a file in CSV format.")
2419
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002420 parser.add_argument("--detailed-report",
2421 action="store",
2422 metavar="FILENAME",
2423 help="Generate a junit report with detailed testcase results.")
2424
Anas Nashif3ba1d432017-12-05 15:28:44 -05002425 parser.add_argument(
2426 "-r", "--release", action="store_true",
2427 help="Update the benchmark database with the results of this test "
2428 "run. Intended to be run by CI when tagging an official "
2429 "release. This database is used as a basis for comparison "
2430 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002431 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002432 help="Treat warning conditions as errors")
2433 parser.add_argument(
2434 "-v",
2435 "--verbose",
2436 action="count",
2437 default=0,
2438 help="Emit debugging information, call multiple times to increase "
2439 "verbosity")
2440 parser.add_argument(
2441 "-i", "--inline-logs", action="store_true",
2442 help="Upon test failure, print relevant log data to stdout "
2443 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002444 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002445 help="log also to file")
2446 parser.add_argument(
2447 "-m", "--last-metrics", action="store_true",
2448 help="Instead of comparing metrics from the last --release, "
2449 "compare with the results of the previous sanity check "
2450 "invocation")
2451 parser.add_argument(
2452 "-u",
2453 "--no-update",
2454 action="store_true",
2455 help="do not update the results of the last run of the sanity "
2456 "checks")
2457 parser.add_argument(
2458 "-F",
2459 "--load-tests",
2460 metavar="FILENAME",
2461 action="store",
2462 help="Load list of tests to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002463
Anas Nashif3ba1d432017-12-05 15:28:44 -05002464 parser.add_argument(
2465 "-E",
2466 "--save-tests",
2467 metavar="FILENAME",
2468 action="store",
2469 help="Save list of tests to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002470
Anas Nashif3ba1d432017-12-05 15:28:44 -05002471 parser.add_argument(
2472 "-b", "--build-only", action="store_true",
2473 help="Only build the code, do not execute any of it in QEMU")
2474 parser.add_argument(
2475 "-j", "--jobs", type=int,
2476 help="Number of cores to use when building, defaults to "
2477 "number of CPUs * 2")
Anas Nashif73440ea2018-02-19 10:57:03 -06002478
2479 parser.add_argument(
2480 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002481 help="Test on device directly. Specify the serial device to "
2482 "use with the --device-serial option.")
Anas Nashif73440ea2018-02-19 10:57:03 -06002483 parser.add_argument(
2484 "--device-serial",
Anas Nashif333a3152018-05-24 14:35:33 -05002485 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002486 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002487 "--show-footprint", action="store_true",
2488 help="Show footprint statistics and deltas since last release."
2489 )
2490 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002491 "-H", "--footprint-threshold", type=float, default=5,
2492 help="When checking test case footprint sizes, warn the user if "
2493 "the new app size is greater then the specified percentage "
2494 "from the last release. Default is 5. 0 to warn on any "
2495 "increase on app size")
2496 parser.add_argument(
2497 "-D", "--all-deltas", action="store_true",
2498 help="Show all footprint deltas, positive or negative. Implies "
2499 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002500 parser.add_argument(
2501 "-O", "--outdir",
2502 default="%s/sanity-out" % ZEPHYR_BASE,
2503 help="Output directory for logs and binaries. "
2504 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002505 parser.add_argument(
2506 "-n", "--no-clean", action="store_true",
2507 help="Do not delete the outdir before building. Will result in "
2508 "faster compilation since builds will be incremental")
2509 parser.add_argument(
2510 "-T", "--testcase-root", action="append", default=[],
2511 help="Base directory to recursively search for test cases. All "
2512 "testcase.yaml files under here will be processed. May be "
2513 "called multiple times. Defaults to the 'samples' and "
2514 "'tests' directories in the Zephyr tree.")
2515 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2516 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
2517 parser.add_argument(
2518 "-A", "--board-root", action="append", default=board_root_list,
2519 help="Directory to search for board configuration files. All .yaml "
2520 "files in the directory will be processed.")
2521 parser.add_argument(
2522 "-z", "--size", action="append",
2523 help="Don't run sanity checks. Instead, produce a report to "
2524 "stdout detailing RAM/ROM sizes on the specified filenames. "
2525 "All other command line arguments ignored.")
2526 parser.add_argument(
2527 "-S", "--enable-slow", action="store_true",
2528 help="Execute time-consuming test cases that have been marked "
2529 "as 'slow' in testcase.yaml. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07002530 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002531 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002532 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002533 parser.add_argument("--disable-asserts", action="store_false",
2534 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07002535 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05002536 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002537 help="Error on deprecation warnings.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002538
2539 parser.add_argument(
2540 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002541 help="""Extra CMake cache entries to define when building test cases.
2542 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01002543 prefixed with -D before being passed to CMake.
2544
2545 E.g
2546 "sanitycheck -x=USE_CCACHE=0"
2547 will translate to
2548 "cmake -DUSE_CCACHE=0"
2549
2550 which will ultimately disable ccache.
2551 """
2552 )
2553
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002554 parser.add_argument("--enable-coverage", action="store_true",
2555 help="Enable code coverage when building unit tests and"
2556 " when targeting the native_posix board")
2557
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002558 parser.add_argument("-C", "--coverage", action="store_true",
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002559 help="Generate coverage report for unit tests, and"
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002560 " tests and samples run in native_posix. Implies"
2561 " --enable_coverage")
Andrew Boie6acbe632015-07-17 12:03:52 -07002562
2563 return parser.parse_args()
2564
Anas Nashif3ba1d432017-12-05 15:28:44 -05002565
Andrew Boie6acbe632015-07-17 12:03:52 -07002566def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01002567 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002568 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002569 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08002570
2571 try:
2572 with open(filename) as fp:
2573 data = fp.read()
2574 except Exception as e:
2575 data = "Unable to read log data (%s)\n" % (str(e))
2576
2577 sys.stdout.write(data)
2578 if log_file:
2579 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002580 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002581 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002582 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07002583
Anas Nashif3ba1d432017-12-05 15:28:44 -05002584
Andrew Boie6acbe632015-07-17 12:03:52 -07002585def terse_test_cb(instances, goals, goal):
2586 total_tests = len(goals)
2587 total_done = 0
2588 total_failed = 0
2589
Andrew Boie08ce5a52016-02-22 13:28:10 -08002590 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002591 if g.finished:
2592 total_done += 1
2593 if g.failed:
2594 total_failed += 1
2595
2596 if goal.failed:
2597 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002598 info(
2599 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
2600 i.platform.name,
2601 i.test.name,
2602 COLOR_RED,
2603 COLOR_NORMAL,
2604 goal.reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002605 log_info(goal.get_error_log())
2606 info("")
2607
Anas Nashif3ba1d432017-12-05 15:28:44 -05002608 sys.stdout.write(
2609 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
2610 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
2611 int((float(total_done) / total_tests) * 100),
2612 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
2613 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07002614 sys.stdout.flush()
2615
Anas Nashif3ba1d432017-12-05 15:28:44 -05002616
Andrew Boie6acbe632015-07-17 12:03:52 -07002617def chatty_test_cb(instances, goals, goal):
2618 i = instances[goal.name]
2619
2620 if VERBOSE < 2 and not goal.finished:
2621 return
2622
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002623 total_tests = len(goals)
2624 total_tests_width = len(str(total_tests))
2625 total_done = 0
2626
2627 for k, g in goals.items():
2628 if g.finished:
2629 total_done += 1
2630
Andrew Boie6acbe632015-07-17 12:03:52 -07002631 if goal.failed:
2632 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
2633 elif goal.finished:
2634 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2635 else:
2636 status = goal.make_state
2637
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002638 info("{:>{}}/{} {:<25} {:<50} {}".format(
2639 total_done, total_tests_width, total_tests, i.platform.name,
2640 i.test.name, status))
Andrew Boie6acbe632015-07-17 12:03:52 -07002641 if goal.failed:
2642 log_info(goal.get_error_log())
2643
Andrew Boiebbd670c2015-08-17 13:16:11 -07002644
2645def size_report(sc):
2646 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07002647 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07002648 for i in range(len(sc.sections)):
2649 v = sc.sections[i]
2650
Andrew Boie73b4ee62015-10-07 11:33:22 -07002651 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
2652 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
2653 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07002654
Andrew Boie73b4ee62015-10-07 11:33:22 -07002655 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002656 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002657 info("")
2658
Anas Nashif3ba1d432017-12-05 15:28:44 -05002659
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002660def generate_coverage(outdir, ignores):
2661 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
2662 coveragefile = os.path.join(outdir, "coverage.info")
2663 ztestfile = os.path.join(outdir, "ztest.info")
2664 subprocess.call(["lcov", "--capture", "--directory", outdir,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002665 "--rc", "lcov_branch_coverage=1",
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002666 "--output-file", coveragefile], stdout=coveragelog)
2667 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
2668 subprocess.call(["lcov", "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05002669 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002670 "--output-file", ztestfile,
2671 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
2672
2673 if os.path.getsize(ztestfile) > 0:
2674 subprocess.call(["lcov", "--remove", ztestfile,
2675 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
2676 "--output-file", ztestfile,
2677 "--rc", "lcov_branch_coverage=1"],
2678 stdout=coveragelog)
2679 files = [coveragefile, ztestfile];
2680 else:
2681 files = [coveragefile];
2682
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002683 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002684 subprocess.call(
2685 ["lcov", "--remove", coveragefile, i, "--output-file",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002686 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05002687 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002688
2689 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
2690 "-output-directory",
2691 os.path.join(outdir, "coverage")] + files,
2692 stdout=coveragelog)
2693 if ret==0:
2694 info("HTML report generated: %s"%
2695 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05002696
Andrew Boiebbd670c2015-08-17 13:16:11 -07002697
Andrew Boie6acbe632015-07-17 12:03:52 -07002698def main():
Andrew Boie4b182472015-07-31 12:25:22 -07002699 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002700 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05002701 global options
2702 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07002703
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002704 if options.coverage:
2705 options.enable_coverage = True
2706
Anas Nashife10b6512017-12-30 13:01:45 -05002707 if options.size:
2708 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08002709 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002710 sys.exit(0)
2711
Anas Nashif73440ea2018-02-19 10:57:03 -06002712
2713 if options.device_testing:
2714 if options.device_serial is None or len(options.platform) != 1:
2715 sys.exit(1)
2716
Anas Nashife10b6512017-12-30 13:01:45 -05002717 VERBOSE += options.verbose
2718 INLINE_LOGS = options.inline_logs
2719 if options.log_file:
2720 log_file = open(options.log_file, "w")
2721 if options.jobs:
2722 CPU_COUNTS = options.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07002723
Anas Nashife10b6512017-12-30 13:01:45 -05002724 if options.subset:
2725 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04002726 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002727 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04002728 else:
Anas Nashife10b6512017-12-30 13:01:45 -05002729 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04002730 return
2731
Anas Nashife10b6512017-12-30 13:01:45 -05002732 if os.path.exists(options.outdir) and not options.no_clean:
2733 info("Cleaning output directory " + options.outdir)
2734 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002735
Anas Nashife10b6512017-12-30 13:01:45 -05002736 if not options.testcase_root:
2737 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07002738 os.path.join(ZEPHYR_BASE, "samples")]
2739
Anas Nashif37f9dc52018-02-23 08:53:46 -06002740 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04002741
Kumar Galac84235e2018-04-10 13:32:51 -05002742 if ts.load_errors:
2743 sys.exit(1)
2744
Anas Nashif75547e22018-02-24 08:32:14 -06002745 if options.list_tags:
2746 tags = set()
2747 for n,tc in ts.testcases.items():
2748 tags = tags.union(tc.tags)
2749
2750 for t in tags:
2751 print("- {}".format(t))
2752
2753 return
2754
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002755
2756 def export_tests(filename, tests):
2757 with open(filename, "wt") as csvfile:
2758 fieldnames = ['section', 'subsection', 'title', 'reference']
2759 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2760 for test in tests:
2761 data = test.split(".")
2762 subsec = " ".join(data[1].split("_")).title()
2763 rowdict = {
2764 "section": data[0].capitalize(),
2765 "subsection": subsec,
2766 "title": test,
2767 "reference": test
2768 }
2769 cw.writerow(rowdict)
2770
2771 if options.export_tests:
2772 cnt = 0
2773 unq = []
2774 for n,tc in ts.testcases.items():
2775 for c in tc.cases:
2776 unq.append(c)
2777
2778 tests = sorted(set(unq))
2779 export_tests(options.export_tests, tests)
2780 return
2781
2782
Anas Nashifc0149cc2018-04-14 23:12:58 -05002783 if options.list_tests:
2784 cnt = 0
Anas Nashifa3abe962018-05-05 19:10:22 -05002785 unq = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05002786 for n,tc in ts.testcases.items():
2787 for c in tc.cases:
Anas Nashifa3abe962018-05-05 19:10:22 -05002788 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002789
Anas Nashifa3abe962018-05-05 19:10:22 -05002790 for u in sorted(set(unq)):
2791 cnt = cnt + 1
2792 print(" - {}".format(u))
Anas Nashifc0149cc2018-04-14 23:12:58 -05002793 print("{} total.".format(cnt))
2794 return
2795
Anas Nashifbd166f42017-09-02 12:32:08 -04002796 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05002797 if options.load_tests:
2798 ts.load_from_file(options.load_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002799 else:
Anas Nashif4f028882017-12-30 11:48:43 -05002800 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07002801
Anas Nashife10b6512017-12-30 13:01:45 -05002802 if options.discard_report:
2803 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07002804
Anas Nashif30551f42018-01-12 21:56:59 -05002805 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002806 # if we are using command line platform filter, no need to list every
2807 # other platform as excluded, we know that already.
2808 # Show only the discards that apply to the selected platforms on the
2809 # command line
2810
Andrew Boie08ce5a52016-02-22 13:28:10 -08002811 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002812 if options.platform and i.platform.name not in options.platform:
2813 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05002814 debug(
2815 "{:<25} {:<50} {}SKIPPED{}: {}".format(
2816 i.platform.name,
2817 i.test.name,
2818 COLOR_YELLOW,
2819 COLOR_NORMAL,
2820 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002821
Anas Nashif1a5bba72018-01-05 08:07:45 -05002822
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002823 def native_posix_and_unit_first(a, b):
2824 if a[0].startswith('native_posix') or a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002825 return -1
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002826 if b[0].startswith('native_posix') or b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002827 return 1
2828 return (a > b) - (a < b)
2829
2830 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002831 key=cmp_to_key(native_posix_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04002832
Anas Nashife10b6512017-12-30 13:01:45 -05002833 if options.save_tests:
2834 ts.run_report(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002835 return
2836
Anas Nashife10b6512017-12-30 13:01:45 -05002837 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05002838
Anas Nashife10b6512017-12-30 13:01:45 -05002839 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04002840 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04002841 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05002842 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04002843 if subset == sets:
2844 end = total
2845 else:
2846 end = start + per_set
2847
Anas Nashif3ba1d432017-12-05 15:28:44 -05002848 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04002849 ts.instances = OrderedDict(sliced_instances)
2850
Andrew Boie6acbe632015-07-17 12:03:52 -07002851 info("%d tests selected, %d tests discarded due to filters" %
2852 (len(ts.instances), len(discards)))
2853
Anas Nashife10b6512017-12-30 13:01:45 -05002854 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07002855 return
2856
2857 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002858 goals = ts.execute(
2859 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002860 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07002861 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002862 goals = ts.execute(
2863 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002864 ts.instances)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002865 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07002866
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002867 if options.detailed_report:
2868 ts.testcase_target_report(options.detailed_report)
2869
Daniel Leung7f850102016-04-08 11:07:32 -07002870 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05002871 if options.compare_report:
2872 report_to_use = options.compare_report
2873 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07002874 report_to_use = LAST_SANITY
2875 else:
2876 report_to_use = RELEASE_DATA
2877
2878 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07002879 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06002880 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07002881 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05002882 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07002883 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07002884 continue
2885
Andrew Boieea7928f2015-08-14 14:27:38 -07002886 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05002887 if not options.all_deltas and (percentage <
2888 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07002889 continue
2890
Daniel Leung00525c22016-04-11 10:27:56 -07002891 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07002892 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05002893 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07002894 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07002895 warnings += 1
2896
2897 if warnings:
2898 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05002899 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07002900
2901 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08002902 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002903 if goal.failed:
2904 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002905 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07002906 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2907 (COLOR_RED, COLOR_NORMAL, goal.name,
2908 str(goal.metrics["unrecognized"])))
2909 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002910
Anas Nashife10b6512017-12-30 13:01:45 -05002911 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002912 info("Generating coverage files...")
Anas Nashife10b6512017-12-30 13:01:45 -05002913 generate_coverage(options.outdir, ["tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002914
Anas Nashif0605fa32017-05-07 08:51:02 -04002915 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07002916 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002917 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
2918 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
2919 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07002920
Anas Nashife10b6512017-12-30 13:01:45 -05002921 if options.testcase_report:
2922 ts.testcase_report(options.testcase_report)
2923 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05002924 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07002925 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05002926 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07002927 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002928 if log_file:
2929 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05002930 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07002931 sys.exit(1)
2932
Anas Nashif3ba1d432017-12-05 15:28:44 -05002933
Andrew Boie6acbe632015-07-17 12:03:52 -07002934if __name__ == "__main__":
2935 main()