blob: 77d4fbefcc3f4d47f9fe08614514bb298a949e51 [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",
663 "ccm_data"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700664 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700665 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andy Rossa3a7e8e2018-05-23 16:39:16 -0700666 "rodata", "devconfig", "net_l2", "vector", "_bt_settings_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700667
Andrew Boie52fef672016-11-29 12:21:59 -0800668 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700669 """Constructor
670
Andrew Boiebbd670c2015-08-17 13:16:11 -0700671 @param filename Path to the output binary
672 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700673 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700674 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700675 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700676 magic = f.read(4)
677
Andrew Boie08ce5a52016-02-22 13:28:10 -0800678 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700679 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700680
681 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500682 # GREP can not be used as it returns an error if the symbol is not
683 # found.
684 is_xip_command = "nm " + filename + \
685 " | awk '/CONFIG_XIP/ { print $3 }'"
686 is_xip_output = subprocess.check_output(
687 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
688 "utf-8").strip()
Andrew Boie8f0211d2016-03-02 20:40:29 -0800689 if is_xip_output.endswith("no symbols"):
690 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700691 self.is_xip = (len(is_xip_output) != 0)
692
Andrew Boiebbd670c2015-08-17 13:16:11 -0700693 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700694 self.sections = []
695 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700696 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800697 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700698
699 self._calculate_sizes()
700
701 def get_ram_size(self):
702 """Get the amount of RAM the application will use up on the device
703
704 @return amount of RAM, in bytes
705 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700706 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700707
708 def get_rom_size(self):
709 """Get the size of the data that this application uses on device's flash
710
711 @return amount of ROM, in bytes
712 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700713 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700714
715 def unrecognized_sections(self):
716 """Get a list of sections inside the binary that weren't recognized
717
David B. Kinder29963c32017-06-16 12:32:42 -0700718 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700719 """
720 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700721 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700722 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700723 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700724 return slist
725
726 def _calculate_sizes(self):
727 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700728 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500729 objdump_output = subprocess.check_output(
730 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700731
732 for line in objdump_output:
733 words = line.split()
734
735 if (len(words) == 0): # Skip lines that are too short
736 continue
737
738 index = words[0]
739 if (not index[0].isdigit()): # Skip lines that do not start
740 continue # with a digit
741
742 name = words[1] # Skip lines with section names
743 if (name[0] == '.'): # starting with '.'
744 continue
745
Andrew Boie73b4ee62015-10-07 11:33:22 -0700746 # TODO this doesn't actually reflect the size in flash or RAM as
747 # it doesn't include linker-imposed padding between sections.
748 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700749 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700750 if size == 0:
751 continue
752
Andrew Boie73b4ee62015-10-07 11:33:22 -0700753 load_addr = int(words[4], 16)
754 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700755
756 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700757 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700758 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700759 if name in SizeCalculator.alloc_sections:
760 self.ram_size += size
761 stype = "alloc"
762 elif name in SizeCalculator.rw_sections:
763 self.ram_size += size
764 self.rom_size += size
765 stype = "rw"
766 elif name in SizeCalculator.ro_sections:
767 self.rom_size += size
768 if not self.is_xip:
769 self.ram_size += size
770 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700771 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700772 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800773 if name not in self.extra_sections:
774 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700775
Anas Nashif3ba1d432017-12-05 15:28:44 -0500776 self.sections.append({"name": name, "load_addr": load_addr,
777 "size": size, "virt_addr": virt_addr,
778 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700779
780
781class MakeGoal:
782 """Metadata class representing one of the sub-makes called by MakeGenerator
783
David B. Kinder29963c32017-06-16 12:32:42 -0700784 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -0700785 with TestInstances to get a complete picture of what happened during a test.
786 MakeGenerator is used for tasks outside of building tests (such as
787 defconfigs) which is why MakeGoal is a separate class from TestInstance.
788 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500789
Anas Nashif4d25b502017-11-25 17:37:17 -0500790 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -0700791 self.name = name
792 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -0500793 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -0700794 self.make_log = make_log
795 self.build_log = build_log
796 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -0500797 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700798 self.make_state = "waiting"
799 self.failed = False
800 self.finished = False
801 self.reason = None
802 self.metrics = {}
803
804 def get_error_log(self):
805 if self.make_state == "waiting":
806 # Shouldn't ever see this; breakage in the main Makefile itself.
807 return self.make_log
808 elif self.make_state == "building":
809 # Failure when calling the sub-make to build the code
810 return self.build_log
811 elif self.make_state == "running":
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400812 # Failure in sub-make for "make run", qemu probably failed to start
Andrew Boie6acbe632015-07-17 12:03:52 -0700813 return self.run_log
814 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -0500815 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -0500816 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700817
818 def fail(self, reason):
819 self.failed = True
820 self.finished = True
821 self.reason = reason
822
823 def success(self):
824 self.finished = True
825
826 def __str__(self):
827 if self.finished:
828 if self.failed:
829 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
830 self.get_error_log())
831 else:
832 return "[%s] passed" % self.name
833 else:
834 return "[%s] in progress (%s)" % (self.name, self.make_state)
835
836
837class MakeGenerator:
838 """Generates a Makefile which just calls a bunch of sub-make sessions
839
840 In any given test suite we may need to build dozens if not hundreds of
841 test cases. The cleanest way to parallelize this is to just let Make
842 do the parallelization, sharing the jobserver among all the different
843 sub-make targets.
844 """
845
846 GOAL_HEADER_TMPL = """.PHONY: {goal}
847{goal}:
848"""
849
850 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -0400851\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -0500852\t\t-G"{generator}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400853\t\t-H{directory}\\
854\t\t-B{outdir}\\
855\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
856\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -0500857\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400858\t\t{args}\\
859\t\t>{logfile} 2>&1
Anas Nashifa8a13882017-12-30 13:01:06 -0500860\t{generator_cmd} -C {outdir}\\
861\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400862\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -0700863"""
Anas Nashif20f553f2018-03-23 11:26:41 -0500864 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
865\t{generator_cmd} -C {outdir}\\
866\t\t{verb} {make_args}\\
867\t\t>>{logfile} 2>&1
868"""
Andrew Boie6acbe632015-07-17 12:03:52 -0700869
870 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
871
Anas Nashif3ba1d432017-12-05 15:28:44 -0500872 re_make = re.compile(
873 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700874
Anas Nashif37f9dc52018-02-23 08:53:46 -0600875 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -0700876 """MakeGenerator constructor
877
878 @param base_outdir Intended to be the base out directory. A make.log
879 file will be created here which contains the output of the
880 top-level Make session, as well as the dynamic control Makefile
881 @param verbose If true, pass V=1 to all the sub-makes which greatly
882 increases their verbosity
883 """
884 self.goals = {}
885 if not os.path.exists(base_outdir):
886 os.makedirs(base_outdir)
887 self.logfile = os.path.join(base_outdir, "make.log")
888 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -0600889 self.asserts = options.enable_asserts
890 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -0700891
892 def _get_rule_header(self, name):
893 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
894
Anas Nashif3ba1d432017-12-05 15:28:44 -0500895 def _get_sub_make(self, name, phase, workdir, outdir,
896 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -0400897 """
898 @param args Arguments given to CMake
899 @param make_args Arguments given to the Makefile generated by CMake
900 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500901 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -0500902 ldflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -0500903
Andrew Boie55121052016-07-20 11:52:04 -0700904 if self.asserts:
Anas Nashif3ba1d432017-12-05 15:28:44 -0500905 cflags = "-DCONFIG_ASSERT=1 -D__ASSERT_ON=2"
Andrew Boie55121052016-07-20 11:52:04 -0700906 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -0500907 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -0500908
909 if self.deprecations:
910 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800911
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +0200912 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -0500913
Anas Nashif25f6ab62018-03-06 07:15:11 -0600914 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100915 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -0700916 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100917 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -0600918 else:
919 generator = "Unix Makefiles"
920 generator_cmd = "$(MAKE)"
921 verb = "VERBOSE=1" if VERBOSE else "VERBOSE=0"
Anas Nashifa8a13882017-12-30 13:01:06 -0500922
Anas Nashif20f553f2018-03-23 11:26:41 -0500923 if phase == 'running':
924 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
925 generator_cmd=generator_cmd,
926 phase=phase,
927 goal=name,
928 outdir=outdir,
929 verb=verb,
930 logfile=logfile,
931 make_args=make_args
932 )
933 else:
934 return MakeGenerator.MAKE_RULE_TMPL.format(
935 generator=generator,
936 generator_cmd=generator_cmd,
937 phase=phase,
938 goal=name,
939 outdir=outdir,
940 cflags=cflags,
941 ldflags=ldflags,
942 directory=workdir,
943 verb=verb,
944 args=args,
945 logfile=logfile,
946 make_args=make_args
947 )
Andrew Boie6acbe632015-07-17 12:03:52 -0700948
949 def _get_rule_footer(self, name):
950 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
951
952 def _add_goal(self, outdir):
953 if not os.path.exists(outdir):
954 os.makedirs(outdir)
955
Anas Nashif576be982017-12-23 20:20:27 -0500956 def add_instance_build_goal(self, instance, args, buildlog, make_args=""):
957
958 self.add_build_goal(instance.name, instance.test.code_location,
959 instance.outdir, args, buildlog, make_args)
960
Anas Nashif3ba1d432017-12-05 15:28:44 -0500961 def add_build_goal(self, name, directory, outdir,
962 args, buildlog, make_args=""):
Andrew Boie6acbe632015-07-17 12:03:52 -0700963 """Add a goal to invoke a Kbuild session
964
965 @param name A unique string name for this build goal. The results
966 dictionary returned by execute() will be keyed by this name.
967 @param directory Absolute path to working directory, will be passed
968 to make -C
969 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +0100970 cmake via -B=<path>
971 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -0700972 environment variables or specific Make goals
973 """
974 self._add_goal(outdir)
Kumar Gala914d92e2017-06-22 16:19:11 -0500975 build_logfile = os.path.join(outdir, buildlog)
Anas Nashif3ba1d432017-12-05 15:28:44 -0500976 text = (
977 self._get_rule_header(name) +
978 self._get_sub_make(
979 name,
980 "building",
981 directory,
982 outdir,
983 build_logfile,
984 args,
985 make_args=make_args) +
986 self._get_rule_footer(name))
987 self.goals[name] = MakeGoal(
988 name,
989 text,
990 None,
991 self.logfile,
992 build_logfile,
993 None,
994 None)
Andrew Boie6acbe632015-07-17 12:03:52 -0700995
Anas Nashif576be982017-12-23 20:20:27 -0500996 def add_qemu_goal(self, instance, args):
Andrew Boie6acbe632015-07-17 12:03:52 -0700997 """Add a goal to build a Zephyr project and then run it under QEMU
998
999 The generated make goal invokes Make twice, the first time it will
1000 build the default goal, and the second will invoke the 'qemu' goal.
1001 The output of the QEMU session will be monitored, and terminated
1002 either upon pass/fail result of the test program, or the timeout
1003 is reached.
1004
1005 @param name A unique string name for this build goal. The results
1006 dictionary returned by execute() will be keyed by this name.
1007 @param directory Absolute path to working directory, will be passed
1008 to make -C
1009 @param outdir Absolute path to output directory, will be passed to
1010 Kbuild via -O=<path>
Sebastian Bøe71d7de02017-11-09 12:06:04 +01001011 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -07001012 @param timeout Maximum length of time QEMU session should be allowed
1013 to run before automatically killing it. Default is 30 seconds.
1014 """
1015
Anas Nashif576be982017-12-23 20:20:27 -05001016 name = instance.name
1017 directory = instance.test.code_location
1018 outdir = instance.outdir
1019
Andrew Boie6acbe632015-07-17 12:03:52 -07001020 build_logfile = os.path.join(outdir, "build.log")
1021 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001022 handler_logfile = os.path.join(outdir, "handler.log")
Anas Nashif576be982017-12-23 20:20:27 -05001023 self._add_goal(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001024
Anas Nashif576be982017-12-23 20:20:27 -05001025 qemu_handler = QEMUHandler(instance)
Anas Nashif4d25b502017-11-25 17:37:17 -05001026 args.append("QEMU_PIPE=%s" % qemu_handler.get_fifo())
Andrew Boie6acbe632015-07-17 12:03:52 -07001027 text = (self._get_rule_header(name) +
1028 self._get_sub_make(name, "building", directory,
1029 outdir, build_logfile, args) +
1030 self._get_sub_make(name, "running", directory,
1031 outdir, run_logfile,
Anas Nashiffb91ad62017-10-31 08:33:17 -04001032 args, make_args="run") +
Andrew Boie6acbe632015-07-17 12:03:52 -07001033 self._get_rule_footer(name))
Anas Nashif4d25b502017-11-25 17:37:17 -05001034 self.goals[name] = MakeGoal(name, text, qemu_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001035 run_logfile, handler_logfile)
Andrew Boie6acbe632015-07-17 12:03:52 -07001036
Anas Nashif37f9dc52018-02-23 08:53:46 -06001037 def add_unit_goal(self, instance, args, timeout=30):
Anas Nashif576be982017-12-23 20:20:27 -05001038 outdir = instance.outdir
1039 timeout = instance.test.timeout
1040 name = instance.name
1041 directory = instance.test.code_location
1042
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001043 self._add_goal(outdir)
1044 build_logfile = os.path.join(outdir, "build.log")
1045 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001046 handler_logfile = os.path.join(outdir, "handler.log")
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01001047
1048 args += ["COVERAGE=1", "EXTRA_LDFLAGS=--coverage"]
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001049
1050 # we handle running in the UnitHandler class
1051 text = (self._get_rule_header(name) +
1052 self._get_sub_make(name, "building", directory,
1053 outdir, build_logfile, args) +
1054 self._get_rule_footer(name))
Anas Nashif576be982017-12-23 20:20:27 -05001055 unit_handler = UnitHandler(instance)
Anas Nashif4d25b502017-11-25 17:37:17 -05001056 self.goals[name] = MakeGoal(name, text, unit_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001057 run_logfile, handler_logfile)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001058
Anas Nashif37f9dc52018-02-23 08:53:46 -06001059 def add_native_goal(self, instance, args):
Anas Nashif576be982017-12-23 20:20:27 -05001060
1061 outdir = instance.outdir
1062 timeout = instance.test.timeout
1063 name = instance.name
1064 directory = instance.test.code_location
1065
Anas Nashifcc164222017-12-26 11:02:46 -05001066 self._add_goal(outdir)
1067 build_logfile = os.path.join(outdir, "build.log")
1068 run_logfile = os.path.join(outdir, "run.log")
Anas Nashifa49048b2018-01-29 08:41:19 -05001069 handler_logfile = os.path.join(outdir, "handler.log")
Anas Nashifcc164222017-12-26 11:02:46 -05001070
1071 # we handle running in the NativeHandler class
1072 text = (self._get_rule_header(name) +
1073 self._get_sub_make(name, "building", directory,
1074 outdir, build_logfile, args) +
1075 self._get_rule_footer(name))
Anas Nashif576be982017-12-23 20:20:27 -05001076 native_handler = NativeHandler(instance)
Anas Nashifcc164222017-12-26 11:02:46 -05001077 self.goals[name] = MakeGoal(name, text, native_handler, self.logfile, build_logfile,
Anas Nashifa49048b2018-01-29 08:41:19 -05001078 run_logfile, handler_logfile)
Anas Nashif4d25b502017-11-25 17:37:17 -05001079
Anas Nashif73440ea2018-02-19 10:57:03 -06001080 def add_device_goal(self, instance, args):
1081
1082 outdir = instance.outdir
1083 timeout = instance.test.timeout
1084 name = instance.name
1085 directory = instance.test.code_location
1086
1087 self._add_goal(outdir)
1088 build_logfile = os.path.join(outdir, "build.log")
1089 run_logfile = os.path.join(outdir, "run.log")
1090 handler_logfile = os.path.join(outdir, "handler.log")
1091
1092 # we handle running in the NativeHandler class
1093 text = (self._get_rule_header(name) +
1094 self._get_sub_make(name, "building", directory,
1095 outdir, build_logfile, args) +
1096 self._get_rule_footer(name))
1097 handler = DeviceHandler(instance)
1098 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
1099 run_logfile, handler_logfile)
1100
Anas Nashif37f9dc52018-02-23 08:53:46 -06001101 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001102 """Add a goal to build/test a TestInstance object
1103
1104 @param ti TestInstance object to build. The status dictionary returned
1105 by execute() will be keyed by its .name field.
1106 """
1107 args = ti.test.extra_args[:]
Anas Nashiffa695d22017-10-04 16:14:27 -04001108 if len(ti.test.extra_configs) > 0:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001109 args.append("OVERLAY_CONFIG=%s" %
1110 os.path.join(ti.outdir, "overlay.conf"))
Anas Nashiffa695d22017-10-04 16:14:27 -04001111
Anas Nashiffb91ad62017-10-31 08:33:17 -04001112 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001113 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001114
Anas Nashif37f9dc52018-02-23 08:53:46 -06001115 do_run_slow = options.enable_slow or not ti.test.slow
1116 do_build_only = ti.build_only or options.build_only
Anas Nashif5df8cff2018-02-23 08:37:14 -06001117 do_run = (not do_build_only) and do_run_slow
1118
Anas Nashif5df8cff2018-02-23 08:37:14 -06001119 if ti.platform.qemu_support and do_run:
Anas Nashif576be982017-12-23 20:20:27 -05001120 self.add_qemu_goal(ti, args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001121
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001122 elif ti.test.type == "unit":
Anas Nashif37f9dc52018-02-23 08:53:46 -06001123 self.add_unit_goal(ti, args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001124
1125 elif ti.platform.type == "native" and do_run:
Anas Nashif37f9dc52018-02-23 08:53:46 -06001126 self.add_native_goal(ti, args)
1127
1128 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif73440ea2018-02-19 10:57:03 -06001129 self.add_device_goal(ti, args)
Anas Nashif37f9dc52018-02-23 08:53:46 -06001130
Andrew Boie6acbe632015-07-17 12:03:52 -07001131 else:
Anas Nashif576be982017-12-23 20:20:27 -05001132 self.add_instance_build_goal(ti, args, "build.log")
Andrew Boie6acbe632015-07-17 12:03:52 -07001133
1134 def execute(self, callback_fn=None, context=None):
1135 """Execute all the registered build goals
1136
1137 @param callback_fn If not None, a callback function will be called
1138 as individual goals transition between states. This function
1139 should accept two parameters: a string state and an arbitrary
1140 context object, supplied here
1141 @param context Context object to pass to the callback function.
1142 Type and semantics are specific to that callback function.
1143 @return A dictionary mapping goal names to final status.
1144 """
1145
Andrew Boie08ce5a52016-02-22 13:28:10 -08001146 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001147 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001148 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001149 # Create our dynamic Makefile and execute it.
1150 # Watch stderr output which is where we will keep
1151 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -08001152 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001153 tf.write(goal.text)
1154 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
1155 tf.flush()
1156
Andy Rossdec163f2018-05-21 10:12:59 -07001157 # Normally spawn 2x as many processes as CPUs. Ninja,
1158 # though, seems to have significant parallelism internally
1159 # and results in rather high loads, so use 1.5x there to
1160 # match what we see with GNU make.
1161 loadmul = 2
1162 if options.ninja:
1163 loadmul = 1.5
1164
Anas Nashif3ba1d432017-12-05 15:28:44 -05001165 cmd = ["make", "-k", "-j",
Andy Rossdec163f2018-05-21 10:12:59 -07001166 str(int(CPU_COUNTS * loadmul)), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001167 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1168 stdout=devnull)
1169
1170 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001171 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001172 make_log.write(line)
1173 verbose("MAKE: " + repr(line.strip()))
1174 m = MakeGenerator.re_make.match(line)
1175 if not m:
1176 continue
1177
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001178 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001179 if error:
1180 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001181 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001182 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001183 # nonzero status.
1184 # Need to distinguish this case from a compilation failure.
Anas Nashif4d25b502017-11-25 17:37:17 -05001185 if goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001186 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001187 else:
1188 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -07001189 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001190 goal = self.goals[name]
1191 goal.make_state = state
1192
Andrew Boie6acbe632015-07-17 12:03:52 -07001193 if state == "finished":
Anas Nashif4d25b502017-11-25 17:37:17 -05001194 if goal.handler:
Anas Nashif576be982017-12-23 20:20:27 -05001195 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001196 goal.handler.handle()
Anas Nashifa49048b2018-01-29 08:41:19 -05001197 goal.handler_log = goal.handler.handler_log
Anas Nashifcc164222017-12-26 11:02:46 -05001198
Anas Nashif4d25b502017-11-25 17:37:17 -05001199 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001200 goal.metrics.update(metrics)
1201 if thread_status == "passed":
1202 goal.success()
1203 else:
1204 goal.fail(thread_status)
1205 else:
1206 goal.success()
1207
1208 if callback_fn:
1209 callback_fn(context, self.goals, goal)
1210
1211 p.wait()
1212 return self.goals
1213
1214
1215# "list" - List of strings
1216# "list:<type>" - List of <type>
1217# "set" - Set of unordered, unique strings
1218# "set:<type>" - Set of <type>
1219# "float" - Floating point
1220# "int" - Integer
1221# "bool" - Boolean
1222# "str" - String
1223
1224# XXX Be sure to update __doc__ if you change any of this!!
1225
Anas Nashif3ba1d432017-12-05 15:28:44 -05001226platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
1227 "supported_toolchains": {"type": "list", "default": []}}
Andrew Boie6acbe632015-07-17 12:03:52 -07001228
Anas Nashif3ba1d432017-12-05 15:28:44 -05001229testcase_valid_keys = {"tags": {"type": "set", "required": False},
1230 "type": {"type": "str", "default": "integration"},
1231 "extra_args": {"type": "list"},
1232 "extra_configs": {"type": "list"},
1233 "build_only": {"type": "bool", "default": False},
1234 "build_on_all": {"type": "bool", "default": False},
1235 "skip": {"type": "bool", "default": False},
1236 "slow": {"type": "bool", "default": False},
1237 "timeout": {"type": "int", "default": 60},
1238 "min_ram": {"type": "int", "default": 8},
1239 "depends_on": {"type": "set"},
1240 "min_flash": {"type": "int", "default": 32},
1241 "arch_whitelist": {"type": "set"},
1242 "arch_exclude": {"type": "set"},
1243 "extra_sections": {"type": "list", "default": []},
1244 "platform_exclude": {"type": "set"},
1245 "platform_whitelist": {"type": "set"},
1246 "toolchain_exclude": {"type": "set"},
1247 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001248 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001249 "harness": {"type": "str"},
1250 "harness_config": {"type": "map"}
Anas Nashifab940162017-12-08 10:17:57 -05001251 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001252
1253
1254class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001255 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001256 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001257
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001258 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001259 """Instantiate a new SanityConfigParser object
1260
Anas Nashifa792a3d2017-04-04 18:47:49 -04001261 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001262 """
Anas Nashif255625b2017-12-05 15:08:26 -05001263 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001264 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001265 self.tests = {}
1266 self.common = {}
1267 if 'tests' in self.data:
1268 self.tests = self.data['tests']
1269 if 'common' in self.data:
1270 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001271
1272 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001273 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001274 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001275 if typestr == "str":
1276 return v
1277
1278 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001279 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001280
1281 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001282 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001283
1284 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001285 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001286
Anas Nashif3ba1d432017-12-05 15:28:44 -05001287 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001288 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001289 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001290 vs = v.split()
1291 if len(typestr) > 4 and typestr[4] == ":":
1292 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1293 else:
1294 return vs
1295
1296 elif typestr.startswith("set"):
1297 vs = v.split()
1298 if len(typestr) > 3 and typestr[3] == ":":
1299 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1300 else:
1301 return set(vs)
1302
Anas Nashif576be982017-12-23 20:20:27 -05001303 elif typestr.startswith("map"):
1304 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001305 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001306 raise ConfigurationError(
1307 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001308
Anas Nashifb4754ed2017-12-05 17:27:58 -05001309 def get_test(self, name, valid_keys):
1310 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001311
Anas Nashifb4754ed2017-12-05 17:27:58 -05001312 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001313 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001314 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001315 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001316 here, it will generate an error. Each value in this dictionary
1317 is another dictionary containing metadata:
1318
1319 "default" - Default value if not given
1320 "type" - Data type to convert the text value to. Simple types
1321 supported are "str", "float", "int", "bool" which will get
1322 converted to respective Python data types. "set" and "list"
1323 may also be specified which will split the value by
1324 whitespace (but keep the elements as strings). finally,
1325 "list:<type>" and "set:<type>" may be given which will
1326 perform a type conversion after splitting the value up.
1327 "required" - If true, raise an error if not defined. If false
1328 and "default" isn't specified, a type conversion will be
1329 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001330 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001331 type conversion and default values filled in per valid_keys
1332 """
1333
1334 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001335 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001336 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001337
Anas Nashifb4754ed2017-12-05 17:27:58 -05001338 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001339 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001340 raise ConfigurationError(
1341 self.filename,
1342 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001343 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001344
Anas Nashiffa695d22017-10-04 16:14:27 -04001345 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001346 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001347 d[k] += " " + v
1348 else:
1349 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001350
Andrew Boie08ce5a52016-02-22 13:28:10 -08001351 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001352 if k not in d:
1353 if "required" in kinfo:
1354 required = kinfo["required"]
1355 else:
1356 required = False
1357
1358 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001359 raise ConfigurationError(
1360 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001361 "missing required value for '%s' in test '%s'" %
1362 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001363 else:
1364 if "default" in kinfo:
1365 default = kinfo["default"]
1366 else:
1367 default = self._cast_value("", kinfo["type"])
1368 d[k] = default
1369 else:
1370 try:
1371 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001372 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001373 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001374 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1375 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001376
1377 return d
1378
1379
1380class Platform:
1381 """Class representing metadata for a particular platform
1382
Anas Nashifc7406082015-12-13 15:00:31 -05001383 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001384
1385 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001386 os.path.join(
1387 os.environ['ZEPHYR_BASE'],
1388 "scripts",
1389 "sanity_chk",
1390 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001391
Anas Nashifa792a3d2017-04-04 18:47:49 -04001392 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001393 """Constructor.
1394
Anas Nashif877d3ca2017-12-05 17:39:29 -05001395 @param cfile Path to platform configuration file, which gives
1396 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001397 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001398 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001399 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001400
Anas Nashif255625b2017-12-05 15:08:26 -05001401 self.name = data['identifier']
Anas Nashifa792a3d2017-04-04 18:47:49 -04001402 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001403 self.ram = data.get("ram", 128)
1404 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001405 self.ignore_tags = testing.get("ignore_tags", [])
1406 self.default = testing.get("default", False)
1407 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001408 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001409 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001410 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001411 for item in supp_feature.split(":"):
1412 self.supported.add(item)
1413
Anas Nashif8acdbd72018-01-04 14:15:22 -05001414 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001415 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001416 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001417 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001418 self.supported_toolchains = data.get("toolchain", [])
Andrew Boie41878222016-11-03 11:58:53 -07001419 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001420 pass
1421
Andrew Boie6acbe632015-07-17 12:03:52 -07001422 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001423 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001424
1425
1426class Architecture:
1427 """Class representing metadata for a particular architecture
1428 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001429
Anas Nashifa792a3d2017-04-04 18:47:49 -04001430 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001431 """Architecture constructor
1432
Anas Nashif877d3ca2017-12-05 17:39:29 -05001433 @param name String name for this architecture
1434 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001435 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001436 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001437
Anas Nashifa792a3d2017-04-04 18:47:49 -04001438 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001439
1440 def __repr__(self):
1441 return "<arch %s>" % self.name
1442
1443
1444class TestCase:
1445 """Class representing a test application
1446 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001447
Anas Nashif7fae29c2017-10-09 13:19:12 -04001448 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001449 """TestCase constructor.
1450
Anas Nashif877d3ca2017-12-05 17:39:29 -05001451 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001452 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001453 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001454
Andrew Boie6acbe632015-07-17 12:03:52 -07001455 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001456 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001457 the test case is <workdir>/<name>.
1458
1459 @param testcase_root Absolute path to the root directory where
1460 all the test cases live
1461 @param workdir Relative path to the project directory for this
1462 test application from the test_case root.
Anas Nashif877d3ca2017-12-05 17:39:29 -05001463 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001464 in the test case configuration file. For many test cases that just
1465 define one test, can be anything and is usually "test". This is
1466 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001467 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001468 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001469 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001470 """
1471 self.code_location = os.path.join(testcase_root, workdir)
Anas Nashifaae71d72018-04-21 22:26:48 -05001472 self.id = name
1473 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001474 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001475 self.tags = tc_dict["tags"]
1476 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001477 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001478 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001479 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001480 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001481 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001482 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001483 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1484 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001485 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001486 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001487 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001488 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001489 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001490 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001491 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001492 self.min_ram = tc_dict["min_ram"]
1493 self.depends_on = tc_dict["depends_on"]
1494 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001495 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001496
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001497 self.path = os.path.normpath(os.path.join(os.path.realpath(
1498 testcase_root).replace(os.path.realpath(ZEPHYR_BASE) + "/", ''),
1499 workdir, name))
1500
Anas Nashifaae71d72018-04-21 22:26:48 -05001501
Anas Nashifbd166f42017-09-02 12:32:08 -04001502 self.name = os.path.join(self.path)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001503 self.defconfig = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001504 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001505
Anas Nashifaae71d72018-04-21 22:26:48 -05001506 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001507 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001508 # do not match until end-of-line, otherwise we won't allow
1509 # stc_regex below to catch the ones that are declared in the same
1510 # line--as we only search starting the end of this match
1511 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001512 re.MULTILINE)
1513 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001514 br"^\s*" # empy space at the beginning is ok
1515 # catch the case where it is declared in the same sentence, e.g:
1516 #
1517 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1518 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1519 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1520 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1521 # Consume the argument that becomes the extra testcse
1522 br"\(\s*"
1523 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1524 # _setup_teardown() variant has two extra arguments that we ignore
1525 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1526 br"\s*\)",
1527 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001528 re.MULTILINE)
1529 suite_run_regex = re.compile(
1530 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1531 re.MULTILINE)
1532 achtung_regex = re.compile(
1533 br"(#ifdef|#endif)",
1534 re.MULTILINE)
1535 warnings = None
1536
1537 with open(inf_name) as inf:
1538 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1539 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001540 suite_regex_match = suite_regex.search(main_c)
1541 if not suite_regex_match:
1542 # can't find ztest_test_suite, maybe a client, because
1543 # it includes ztest.h
1544 return None, None
1545
1546 suite_run_match = suite_run_regex.search(main_c)
1547 if not suite_run_match:
1548 raise ValueError("can't find ztest_run_test_suite")
1549
1550 achtung_matches = re.findall(
1551 achtung_regex,
1552 main_c[suite_regex_match.end():suite_run_match.start()])
1553 if achtung_matches:
1554 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001555 % ", ".join(set([
1556 match.decode() for match in achtung_matches
1557 ]))
1558 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001559 stc_regex,
1560 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001561 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001562 return matches, warnings
1563
1564 def scan_path(self, path):
1565 subcases = []
1566 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1567 try:
1568 _subcases, warnings = self.scan_file(filename)
1569 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001570 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001571 if _subcases:
1572 subcases += _subcases
1573 except ValueError as e:
1574 error("%s: can't find: %s", filename, e)
1575 return subcases
1576
1577
1578 def parse_subcases(self):
1579 results = self.scan_path(self.code_location)
1580 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001581 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001582 self.cases.append(name)
1583
1584
Anas Nashif75547e22018-02-24 08:32:14 -06001585 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001586 return self.name
1587
1588
Andrew Boie6acbe632015-07-17 12:03:52 -07001589class TestInstance:
1590 """Class representing the execution of a particular TestCase on a platform
1591
1592 @param test The TestCase object we want to build/execute
1593 @param platform Platform object that we want to build and run against
1594 @param base_outdir Base directory for all test results. The actual
1595 out directory used is <outdir>/<platform>/<test case name>
1596 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001597
Anas Nashif37f9dc52018-02-23 08:53:46 -06001598 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001599 self.test = test
1600 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001601 self.name = os.path.join(platform.name, test.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001602 self.outdir = os.path.join(base_outdir, platform.name, test.path)
Anas Nashif37f9dc52018-02-23 08:53:46 -06001603 self.build_only = options.build_only or test.build_only or (test.harness and test.harness != 'console')
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001604 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001605
Anas Nashiffa695d22017-10-04 16:14:27 -04001606 def create_overlay(self):
1607 if len(self.test.extra_configs) > 0:
1608 file = os.path.join(self.outdir, "overlay.conf")
1609 os.makedirs(self.outdir, exist_ok=True)
1610 f = open(file, "w")
1611 content = ""
Anas Nashif981f77f2017-10-18 07:53:58 -04001612 content = "\n".join(self.test.extra_configs)
Anas Nashiffa695d22017-10-04 16:14:27 -04001613 f.write(content)
1614 f.close()
1615
Andrew Boie6acbe632015-07-17 12:03:52 -07001616 def calculate_sizes(self):
1617 """Get the RAM/ROM sizes of a test case.
1618
1619 This can only be run after the instance has been executed by
1620 MakeGenerator, otherwise there won't be any binaries to measure.
1621
1622 @return A SizeCalculator object
1623 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001624 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001625 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001626 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001627 if (len(fns) != 1):
1628 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001629 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001630
1631 def __repr__(self):
1632 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1633
1634
Andrew Boie4ef16c52015-08-28 12:36:03 -07001635def defconfig_cb(context, goals, goal):
1636 if not goal.failed:
1637 return
1638
1639 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001640 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001641 if INLINE_LOGS:
1642 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001643 data = fp.read()
1644 sys.stdout.write(data)
1645 if log_file:
1646 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001647 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001648 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001649
Andrew Boie6acbe632015-07-17 12:03:52 -07001650
1651class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001652 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001653
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001654 yaml_tc_schema = scl.yaml_load(
1655 os.path.join(os.environ['ZEPHYR_BASE'],
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001656 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001657
Anas Nashif37f9dc52018-02-23 08:53:46 -06001658 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001659 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001660 self.arches = {}
1661 self.testcases = {}
1662 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001663 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001664 self.instances = {}
1665 self.goals = None
1666 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001667 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001668
Andrew Boie3d348712016-04-08 11:52:13 -07001669 for testcase_root in testcase_roots:
1670 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001671
Andrew Boie3d348712016-04-08 11:52:13 -07001672 debug("Reading test case configuration files under %s..." %
1673 testcase_root)
1674 for dirpath, dirnames, filenames in os.walk(testcase_root,
1675 topdown=True):
1676 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001677 if 'sample.yaml' in filenames:
1678 filename = 'sample.yaml'
1679 elif 'testcase.yaml' in filenames:
1680 filename = 'testcase.yaml'
1681 else:
1682 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001683
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001684 verbose("Found possible test case in " + dirpath)
1685 dirnames[:] = []
1686 yaml_path = os.path.join(dirpath, filename)
1687 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001688 parsed_data = SanityConfigParser(
1689 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001690
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001691 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001692
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001693 for name in parsed_data.tests.keys():
1694 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1695 tc = TestCase(testcase_root, workdir, name, tc_dict,
1696 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001697 tc.parse_subcases()
Anas Nashif7fae29c2017-10-09 13:19:12 -04001698
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001699 self.testcases[tc.name] = tc
1700
1701 except Exception as e:
1702 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001703 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001704
Andrew Boie6acbe632015-07-17 12:03:52 -07001705
Anas Nashif86c8e232017-10-09 13:42:28 -04001706 for board_root in board_root_list:
1707 board_root = os.path.abspath(board_root)
1708
Anas Nashif3ba1d432017-12-05 15:28:44 -05001709 debug(
1710 "Reading platform configuration files under %s..." %
1711 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001712 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1713 verbose("Found plaform configuration " + fn)
1714 try:
1715 platform = Platform(fn)
1716 self.platforms.append(platform)
1717 except RuntimeError as e:
1718 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001719 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001720
Anas Nashifa792a3d2017-04-04 18:47:49 -04001721 arches = []
1722 for p in self.platforms:
1723 arches.append(p.arch)
1724 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001725 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001726 arch = Architecture(a, aplatforms)
1727 self.arches[a] = arch
1728
Andrew Boie6acbe632015-07-17 12:03:52 -07001729 self.instances = {}
1730
1731 def get_last_failed(self):
1732 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001733 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001734 result = []
1735 with open(LAST_SANITY, "r") as fp:
1736 cr = csv.DictReader(fp)
1737 for row in cr:
1738 if row["passed"] == "True":
1739 continue
1740 test = row["test"]
1741 platform = row["platform"]
1742 result.append((test, platform))
1743 return result
1744
Anas Nashifbd166f42017-09-02 12:32:08 -04001745 def load_from_file(self, file):
1746 if not os.path.exists(file):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001747 raise SanityRuntimeError(
1748 "Couldn't find input file with list of tests.")
Anas Nashifbd166f42017-09-02 12:32:08 -04001749 with open(file, "r") as fp:
1750 cr = csv.reader(fp)
1751 instance_list = []
1752 for row in cr:
1753 name = os.path.join(row[0], row[1])
1754 platforms = self.arches[row[3]].platforms
1755 myp = None
1756 for p in platforms:
1757 if p.name == row[2]:
1758 myp = p
1759 break
1760 instance = TestInstance(self.testcases[name], myp, self.outdir)
Anas Nashiffa695d22017-10-04 16:14:27 -04001761 instance.create_overlay()
Anas Nashifbd166f42017-09-02 12:32:08 -04001762 instance_list.append(instance)
1763 self.add_instances(instance_list)
1764
Anas Nashif4f028882017-12-30 11:48:43 -05001765 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04001766
Anas Nashif7fe35cf2018-02-15 07:20:18 -06001767 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
1768 os.environ.get("ZEPHYR_GCC_VARIANT", None)
1769 if not toolchain:
1770 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
1771
1772
Andrew Boie6acbe632015-07-17 12:03:52 -07001773 instances = []
1774 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05001775 platform_filter = options.platform
1776 last_failed = options.only_failed
1777 testcase_filter = options.test
1778 arch_filter = options.arch
1779 tag_filter = options.tag
1780 exclude_tag = options.exclude_tag
1781 config_filter = options.config
1782 extra_args = options.extra_args
1783 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04001784
Andrew Boie6acbe632015-07-17 12:03:52 -07001785 verbose("platform filter: " + str(platform_filter))
1786 verbose(" arch_filter: " + str(arch_filter))
1787 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001788 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001789 verbose(" config_filter: " + str(config_filter))
1790
1791 if last_failed:
1792 failed_tests = self.get_last_failed()
1793
Andrew Boie821d8322016-03-22 10:08:35 -07001794 default_platforms = False
1795
1796 if all_plats:
1797 info("Selecting all possible platforms per test case")
1798 # When --all used, any --platform arguments ignored
1799 platform_filter = []
1800 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001801 info("Selecting default platforms per test case")
1802 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001803
Sebastian Bøe781e3982017-11-09 11:43:33 +01001804 mg = MakeGenerator(self.outdir)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001805 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001806 for tc_name, tc in self.testcases.items():
1807 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001808 for plat in arch.platforms:
1809 instance = TestInstance(tc, plat, self.outdir)
1810
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001811 if (arch_name == "unit") != (tc.type == "unit"):
1812 continue
1813
Anas Nashifbfab06b2017-06-22 09:22:24 -04001814 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001815 platform_filter = []
1816
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001817 if tc.skip:
1818 continue
1819
Anas Nashif2cf0df02015-10-10 09:29:43 -04001820 if tag_filter and not tc.tags.intersection(tag_filter):
1821 continue
1822
Anas Nashifdfa86e22016-10-24 17:08:56 -04001823 if exclude_tag and tc.tags.intersection(exclude_tag):
1824 continue
1825
Anas Nashif2cf0df02015-10-10 09:29:43 -04001826 if testcase_filter and tc_name not in testcase_filter:
1827 continue
1828
Anas Nashif3ba1d432017-12-05 15:28:44 -05001829 if last_failed and (
1830 tc.name, plat.name) not in failed_tests:
Anas Nashif2cf0df02015-10-10 09:29:43 -04001831 continue
1832
1833 if arch_filter and arch_name not in arch_filter:
1834 continue
1835
1836 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1837 continue
1838
1839 if tc.arch_exclude and arch.name in tc.arch_exclude:
1840 continue
1841
1842 if tc.platform_exclude and plat.name in tc.platform_exclude:
1843 continue
1844
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001845 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1846 continue
1847
Anas Nashif2cf0df02015-10-10 09:29:43 -04001848 if platform_filter and plat.name not in platform_filter:
1849 continue
1850
Anas Nashif62224182017-08-09 23:55:53 -04001851 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001852 continue
1853
1854 if set(plat.ignore_tags) & tc.tags:
1855 continue
1856
Kumar Gala5141d522017-07-07 08:05:48 -05001857 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001858 dep_intersection = tc.depends_on.intersection(
1859 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001860 if dep_intersection != set(tc.depends_on):
1861 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001862
1863 if plat.flash < tc.min_flash:
1864 continue
1865
Anas Nashif2cf0df02015-10-10 09:29:43 -04001866 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1867 continue
1868
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001869 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1870 continue
1871
Anas Nashif3ba1d432017-12-05 15:28:44 -05001872 if (tc.tc_filter and (plat.default or all_plats or platform_filter)
1873 and toolchain in plat.supported_toolchains):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001874 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04001875 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07001876 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001877 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001878 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07001879 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05001880 # each other since they all try to build them
1881 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04001882
1883 o = os.path.join(self.outdir, plat.name, tc.path)
Anas Nashif3ba1d432017-12-05 15:28:44 -05001884 dlist[tc, plat, tc.name.split(
1885 "/")[-1]] = os.path.join(o, "zephyr", ".config")
1886 goal = "_".join([plat.name, "_".join(
1887 tc.name.split("/")), "config-sanitycheck"])
Anas Nashif576be982017-12-23 20:20:27 -05001888 mg.add_build_goal(goal,
1889 os.path.join(ZEPHYR_BASE, tc.code_location),
1890 o, args,
1891 "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04001892
1893 info("Building testcase defconfigs...")
1894 results = mg.execute(defconfig_cb)
1895
Andrew Boie08ce5a52016-02-22 13:28:10 -08001896 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001897 if goal.failed:
1898 raise SanityRuntimeError("Couldn't build some defconfigs")
1899
Andrew Boie08ce5a52016-02-22 13:28:10 -08001900 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001901 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001902 defconfig = {}
1903 with open(out_config, "r") as fp:
1904 for line in fp.readlines():
1905 m = TestSuite.config_re.match(line)
1906 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001907 if line.strip() and not line.startswith("#"):
1908 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001909 continue
1910 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001911 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001912
Andrew Boie08ce5a52016-02-22 13:28:10 -08001913 for tc_name, tc in self.testcases.items():
1914 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001915 instance_list = []
1916 for plat in arch.platforms:
1917 instance = TestInstance(tc, plat, self.outdir)
1918
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001919 if (arch_name == "unit") != (tc.type == "unit"):
1920 # Discard silently
1921 continue
1922
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001923 if tc.skip:
1924 discards[instance] = "Skip filter"
1925 continue
1926
Anas Nashifbfab06b2017-06-22 09:22:24 -04001927 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001928 platform_filter = []
1929
Andrew Boie6acbe632015-07-17 12:03:52 -07001930 if tag_filter and not tc.tags.intersection(tag_filter):
1931 discards[instance] = "Command line testcase tag filter"
1932 continue
1933
Anas Nashifdfa86e22016-10-24 17:08:56 -04001934 if exclude_tag and tc.tags.intersection(exclude_tag):
1935 discards[instance] = "Command line testcase exclude filter"
1936 continue
1937
Andrew Boie6acbe632015-07-17 12:03:52 -07001938 if testcase_filter and tc_name not in testcase_filter:
1939 discards[instance] = "Testcase name filter"
1940 continue
1941
Anas Nashif3ba1d432017-12-05 15:28:44 -05001942 if last_failed and (
1943 tc.name, plat.name) not in failed_tests:
Andrew Boie6acbe632015-07-17 12:03:52 -07001944 discards[instance] = "Passed or skipped during last run"
1945 continue
1946
1947 if arch_filter and arch_name not in arch_filter:
1948 discards[instance] = "Command line testcase arch filter"
1949 continue
1950
1951 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1952 discards[instance] = "Not in test case arch whitelist"
1953 continue
1954
Anas Nashif30d13872015-10-05 10:02:45 -04001955 if tc.arch_exclude and arch.name in tc.arch_exclude:
1956 discards[instance] = "In test case arch exclude"
1957 continue
1958
1959 if tc.platform_exclude and plat.name in tc.platform_exclude:
1960 discards[instance] = "In test case platform exclude"
1961 continue
1962
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001963 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1964 discards[instance] = "In test case toolchain exclude"
1965 continue
1966
Andrew Boie6acbe632015-07-17 12:03:52 -07001967 if platform_filter and plat.name not in platform_filter:
1968 discards[instance] = "Command line platform filter"
1969 continue
1970
1971 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1972 discards[instance] = "Not in testcase platform whitelist"
1973 continue
1974
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001975 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1976 discards[instance] = "Not in testcase toolchain whitelist"
1977 continue
1978
Anas Nashif86c8e232017-10-09 13:42:28 -04001979 if toolchain and toolchain not in plat.supported_toolchains and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05001980 discards[instance] = "Not supported by the toolchain"
1981 continue
1982
Anas Nashif62224182017-08-09 23:55:53 -04001983 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001984 discards[instance] = "Not enough RAM"
1985 continue
1986
Kumar Gala5141d522017-07-07 08:05:48 -05001987 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001988 dep_intersection = tc.depends_on.intersection(
1989 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001990 if dep_intersection != set(tc.depends_on):
1991 discards[instance] = "No hardware support"
1992 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001993
Anas Nashif62224182017-08-09 23:55:53 -04001994 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001995 discards[instance] = "Not enough FLASH"
1996 continue
1997
1998 if set(plat.ignore_tags) & tc.tags:
1999 discards[instance] = "Excluded tags per platform"
2000 continue
2001
Anas Nashif674bb282018-01-09 09:12:15 -05002002 defconfig = {
2003 "ASSERT": 1 if options.enable_asserts else 0,
2004 "ARCH": arch.name,
2005 "PLATFORM": plat.name
2006 }
Javier B Perez79414542016-08-08 12:24:59 -05002007 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07002008 for p, tdefconfig in tc.defconfig.items():
2009 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07002010 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04002011 break
2012
Andrew Boie3ea78922016-03-24 14:46:00 -07002013 if tc.tc_filter:
2014 try:
2015 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07002016 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002017 sys.stderr.write(
2018 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07002019 raise se
2020 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002021 discards[instance] = (
2022 "defconfig doesn't satisfy expression '%s'" %
2023 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07002024 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04002025
Andrew Boie6acbe632015-07-17 12:03:52 -07002026 instance_list.append(instance)
2027
2028 if not instance_list:
2029 # Every platform in this arch was rejected already
2030 continue
2031
Anas Nashifa792a3d2017-04-04 18:47:49 -04002032 if default_platforms and not tc.build_on_all:
2033 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002034 instances = list(
2035 filter(
2036 lambda tc: tc.platform.default,
2037 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04002038 self.add_instances(instances)
2039 else:
Anas Nashifab747062017-12-05 17:59:01 -05002040 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04002041
Anas Nashif3ba1d432017-12-05 15:28:44 -05002042 for instance in list(
2043 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04002044 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07002045 else:
Andrew Boie821d8322016-03-22 10:08:35 -07002046 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05002047
2048 for name, case in self.instances.items():
2049 case.create_overlay()
2050
Andrew Boie6acbe632015-07-17 12:03:52 -07002051 self.discards = discards
2052 return discards
2053
Andrew Boie821d8322016-03-22 10:08:35 -07002054 def add_instances(self, ti_list):
2055 for ti in ti_list:
2056 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07002057
Anas Nashif37f9dc52018-02-23 08:53:46 -06002058 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07002059
2060 def calc_one_elf_size(name, goal):
2061 if not goal.failed:
2062 i = self.instances[name]
2063 sc = i.calculate_sizes()
2064 goal.metrics["ram_size"] = sc.get_ram_size()
2065 goal.metrics["rom_size"] = sc.get_rom_size()
2066 goal.metrics["unrecognized"] = sc.unrecognized_sections()
Daniel Leung6b170072016-04-07 12:10:25 -07002067
Anas Nashif37f9dc52018-02-23 08:53:46 -06002068 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002069 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002070 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002071 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002072
2073 # Parallelize size calculation
2074 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
Anas Nashif3ba1d432017-12-05 15:28:44 -05002075 futures = [executor.submit(calc_one_elf_size, name, goal)
2076 for name, goal in self.goals.items()]
Daniel Leung6b170072016-04-07 12:10:25 -07002077 concurrent.futures.wait(futures)
2078
Andrew Boie6acbe632015-07-17 12:03:52 -07002079 return self.goals
2080
Anas Nashifbd166f42017-09-02 12:32:08 -04002081 def run_report(self, filename):
2082 with open(filename, "at") as csvfile:
2083 fieldnames = ['path', 'test', 'platform', 'arch']
2084 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2085 for instance in self.instances.values():
2086 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002087 "path": os.path.dirname(instance.test.name),
2088 "test": os.path.basename(instance.test.name),
2089 "platform": instance.platform.name,
2090 "arch": instance.platform.arch
2091 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002092 cw.writerow(rowdict)
2093
Andrew Boie6acbe632015-07-17 12:03:52 -07002094 def discard_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002095 if self.discards is None:
2096 raise SanityRuntimeError("apply_filters() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002097
Anas Nashifbd166f42017-09-02 12:32:08 -04002098 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002099 fieldnames = ["test", "arch", "platform", "reason"]
2100 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2101 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002102 for instance, reason in self.discards.items():
Anas Nashif3ba1d432017-12-05 15:28:44 -05002103 rowdict = {"test": instance.test.name,
2104 "arch": instance.platform.arch,
2105 "platform": instance.platform.name,
2106 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002107 cw.writerow(rowdict)
2108
2109 def compare_metrics(self, filename):
2110 # name, datatype, lower results better
2111 interesting_metrics = [("ram_size", int, True),
2112 ("rom_size", int, True)]
2113
Anas Nashif3ba1d432017-12-05 15:28:44 -05002114 if self.goals is None:
2115 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002116
2117 if not os.path.exists(filename):
2118 info("Cannot compare metrics, %s not found" % filename)
2119 return []
2120
2121 results = []
2122 saved_metrics = {}
2123 with open(filename) as fp:
2124 cr = csv.DictReader(fp)
2125 for row in cr:
2126 d = {}
2127 for m, _, _ in interesting_metrics:
2128 d[m] = row[m]
2129 saved_metrics[(row["test"], row["platform"])] = d
2130
Andrew Boie08ce5a52016-02-22 13:28:10 -08002131 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002132 i = self.instances[name]
2133 mkey = (i.test.name, i.platform.name)
2134 if mkey not in saved_metrics:
2135 continue
2136 sm = saved_metrics[mkey]
2137 for metric, mtype, lower_better in interesting_metrics:
2138 if metric not in goal.metrics:
2139 continue
2140 if sm[metric] == "":
2141 continue
2142 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002143 if delta == 0:
2144 continue
2145 results.append((i, metric, goal.metrics[metric], delta,
2146 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002147 return results
2148
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002149
2150
2151 def encode_for_xml(self, unicode_data, encoding='ascii'):
2152 unicode_data = unicode_data.replace('\x00', '')
2153 return unicode_data
2154
2155 def testcase_target_report(self, report_file):
2156
2157 run = "Sanitycheck"
2158 eleTestsuite = None
2159 append = options.only_failed
2160
2161 errors = 0
2162 passes = 0
2163 fails = 0
2164 duration = 0
2165 skips = 0
2166
2167 for identifier, ti in self.instances.items():
2168 for k in ti.results.keys():
2169 if ti.results[k] == 'PASS':
2170 passes += 1
2171 elif ti.results[k] == 'BLOCK':
2172 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002173 elif ti.results[k] == 'SKIP':
2174 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002175 else:
2176 fails += 1
2177
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002178 eleTestsuites = ET.Element('testsuites')
2179 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2180 name=run, time="%d" % duration,
2181 tests="%d" % (errors + passes + fails),
2182 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002183 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002184
2185 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002186
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002187 # print out test results
2188 for identifier, ti in self.instances.items():
2189 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002190
2191 eleTestcase = ET.SubElement(
2192 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002193 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002194 if ti.results[k] in ['FAIL', 'BLOCK']:
2195 el = None
2196
2197 if ti.results[k] == 'FAIL':
2198 el = ET.SubElement(
2199 eleTestcase,
2200 'failure',
2201 type="failure",
2202 message="failed")
2203 elif ti.results[k] == 'BLOCK':
2204 el = ET.SubElement(
2205 eleTestcase,
2206 'error',
2207 type="failure",
2208 message="failed")
2209 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
2210 bl = os.path.join(p, "handler.log")
2211
2212 if os.path.exists(bl):
2213 with open(bl, "rb") as f:
2214 log = f.read().decode("utf-8")
2215 el.text = self.encode_for_xml(log)
2216
Anas Nashif61e21632018-04-08 13:30:16 -05002217 elif ti.results[k] == 'SKIP':
2218 el = ET.SubElement(
2219 eleTestcase,
2220 'skipped')
2221
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002222 result = ET.tostring(eleTestsuites)
2223 f = open(report_file, 'wb')
2224 f.write(result)
2225 f.close()
2226
2227
Anas Nashif4f028882017-12-30 11:48:43 -05002228 def testcase_xunit_report(self, filename, duration):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002229 if self.goals is None:
2230 raise SanityRuntimeError("execute() hasn't been run!")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002231
2232 fails = 0
2233 passes = 0
2234 errors = 0
2235
2236 for name, goal in self.goals.items():
2237 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002238 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002239 errors += 1
2240 else:
2241 fails += 1
2242 else:
2243 passes += 1
2244
2245 run = "Sanitycheck"
2246 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002247 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002248
Anas Nashif0605fa32017-05-07 08:51:02 -04002249 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002250 tree = ET.parse(filename)
2251 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002252 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002253 else:
2254 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002255 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2256 name=run, time="%d" % duration,
2257 tests="%d" % (errors + passes + fails),
2258 failures="%d" % fails,
2259 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002260
Anas Nashifc8390f12017-11-25 17:14:12 -05002261 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002262 for name, goal in self.goals.items():
2263
2264 i = self.instances[name]
2265 if append:
2266 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002267 if tc.get('classname') == "%s:%s" % (
2268 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002269 eleTestsuite.remove(tc)
2270
Anas Nashif4d25b502017-11-25 17:37:17 -05002271 if not goal.failed and goal.handler:
2272 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002273
Anas Nashif3ba1d432017-12-05 15:28:44 -05002274 eleTestcase = ET.SubElement(
2275 eleTestsuite, 'testcase', classname="%s:%s" %
2276 (i.platform.name, i.test.name), name="%s" %
2277 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002278 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002279 failure = ET.SubElement(
2280 eleTestcase,
2281 'failure',
2282 type="failure",
2283 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002284 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002285 bl = os.path.join(p, "build.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002286 if goal.reason != 'build_error':
Anas Nashifa49048b2018-01-29 08:41:19 -05002287 bl = os.path.join(p, "handler.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002288
Anas Nashifb3311ed2017-04-13 14:44:48 -04002289 if os.path.exists(bl):
Anas Nashif712d3452017-12-29 22:09:03 -05002290 with open(bl, "rb") as f:
2291 log = f.read().decode("utf-8")
2292 failure.text = log
Anas Nashifb3311ed2017-04-13 14:44:48 -04002293
2294 result = ET.tostring(eleTestsuites)
2295 f = open(filename, 'wb')
2296 f.write(result)
2297 f.close()
2298
Andrew Boie6acbe632015-07-17 12:03:52 -07002299 def testcase_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002300 if self.goals is None:
2301 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002302
Andrew Boie08ce5a52016-02-22 13:28:10 -08002303 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002304 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002305 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002306 "rom_size"]
2307 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2308 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002309 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002310 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002311 rowdict = {"test": i.test.name,
2312 "arch": i.platform.arch,
2313 "platform": i.platform.name,
2314 "extra_args": " ".join(i.test.extra_args),
2315 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002316 if goal.failed:
2317 rowdict["passed"] = False
2318 rowdict["status"] = goal.reason
2319 else:
2320 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002321 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002322 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002323 rowdict["ram_size"] = goal.metrics["ram_size"]
2324 rowdict["rom_size"] = goal.metrics["rom_size"]
2325 cw.writerow(rowdict)
2326
2327
2328def parse_arguments():
2329
Anas Nashif3ba1d432017-12-05 15:28:44 -05002330 parser = argparse.ArgumentParser(
2331 description=__doc__,
2332 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002333 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002334
Anas Nashif3ba1d432017-12-05 15:28:44 -05002335 parser.add_argument(
2336 "-p", "--platform", action="append",
2337 help="Platform filter for testing. This option may be used multiple "
2338 "times. Testcases will only be built/run on the platforms "
2339 "specified. If this option is not used, then platforms marked "
2340 "as default in the platform metadata file will be chosen "
2341 "to build and test. ")
2342 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002343 "-a", "--arch", action="append",
2344 help="Arch filter for testing. Takes precedence over --platform. "
2345 "If unspecified, test all arches. Multiple invocations "
2346 "are treated as a logical 'or' relationship")
2347 parser.add_argument(
2348 "-t", "--tag", action="append",
2349 help="Specify tags to restrict which tests to run by tag value. "
2350 "Default is to not do any tag filtering. Multiple invocations "
2351 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002352 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002353 help="Specify tags of tests that should not run. "
2354 "Default is to run all tests with all tags.")
2355 parser.add_argument(
2356 "-f",
2357 "--only-failed",
2358 action="store_true",
2359 help="Run only those tests that failed the previous sanity check "
2360 "invocation.")
2361 parser.add_argument(
2362 "-c", "--config", action="append",
2363 help="Specify platform configuration values filtering. This can be "
2364 "specified two ways: <config>=<value> or just <config>. The "
2365 "defconfig for all platforms will be "
2366 "checked. For the <config>=<value> case, only match defconfig "
2367 "that have that value defined. For the <config> case, match "
2368 "defconfig that have that value assigned to any value. "
2369 "Prepend a '!' to invert the match.")
2370 parser.add_argument(
2371 "-s", "--test", action="append",
2372 help="Run only the specified test cases. These are named by "
2373 "<path to test project relative to "
2374 "--testcase-root>/<testcase.yaml section name>")
2375 parser.add_argument(
2376 "-l", "--all", action="store_true",
2377 help="Build/test on all platforms. Any --platform arguments "
2378 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002379
Anas Nashif3ba1d432017-12-05 15:28:44 -05002380 parser.add_argument(
2381 "-o", "--testcase-report",
2382 help="Output a CSV spreadsheet containing results of the test run")
2383 parser.add_argument(
2384 "-d", "--discard-report",
2385 help="Output a CSV spreadsheet showing tests that were skipped "
2386 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002387 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002388 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002389
Anas Nashif3ba1d432017-12-05 15:28:44 -05002390 parser.add_argument(
2391 "-B", "--subset",
2392 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2393 "3/5 means run the 3rd fifth of the total. "
2394 "This option is useful when running a large number of tests on "
2395 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002396
2397 parser.add_argument(
2398 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002399 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002400
Anas Nashif3ba1d432017-12-05 15:28:44 -05002401 parser.add_argument(
2402 "-y", "--dry-run", action="store_true",
2403 help="Create the filtered list of test cases, but don't actually "
2404 "run them. Useful if you're just interested in "
2405 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002406
Anas Nashif75547e22018-02-24 08:32:14 -06002407 parser.add_argument("--list-tags", action="store_true",
2408 help="list all tags in selected tests")
2409
Anas Nashifc0149cc2018-04-14 23:12:58 -05002410 parser.add_argument("--list-tests", action="store_true",
2411 help="list all tests.")
2412
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002413 parser.add_argument("--detailed-report",
2414 action="store",
2415 metavar="FILENAME",
2416 help="Generate a junit report with detailed testcase results.")
2417
Anas Nashif3ba1d432017-12-05 15:28:44 -05002418 parser.add_argument(
2419 "-r", "--release", action="store_true",
2420 help="Update the benchmark database with the results of this test "
2421 "run. Intended to be run by CI when tagging an official "
2422 "release. This database is used as a basis for comparison "
2423 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002424 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002425 help="Treat warning conditions as errors")
2426 parser.add_argument(
2427 "-v",
2428 "--verbose",
2429 action="count",
2430 default=0,
2431 help="Emit debugging information, call multiple times to increase "
2432 "verbosity")
2433 parser.add_argument(
2434 "-i", "--inline-logs", action="store_true",
2435 help="Upon test failure, print relevant log data to stdout "
2436 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002437 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002438 help="log also to file")
2439 parser.add_argument(
2440 "-m", "--last-metrics", action="store_true",
2441 help="Instead of comparing metrics from the last --release, "
2442 "compare with the results of the previous sanity check "
2443 "invocation")
2444 parser.add_argument(
2445 "-u",
2446 "--no-update",
2447 action="store_true",
2448 help="do not update the results of the last run of the sanity "
2449 "checks")
2450 parser.add_argument(
2451 "-F",
2452 "--load-tests",
2453 metavar="FILENAME",
2454 action="store",
2455 help="Load list of tests to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002456
Anas Nashif3ba1d432017-12-05 15:28:44 -05002457 parser.add_argument(
2458 "-E",
2459 "--save-tests",
2460 metavar="FILENAME",
2461 action="store",
2462 help="Save list of tests to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002463
Anas Nashif3ba1d432017-12-05 15:28:44 -05002464 parser.add_argument(
2465 "-b", "--build-only", action="store_true",
2466 help="Only build the code, do not execute any of it in QEMU")
2467 parser.add_argument(
2468 "-j", "--jobs", type=int,
2469 help="Number of cores to use when building, defaults to "
2470 "number of CPUs * 2")
Anas Nashif73440ea2018-02-19 10:57:03 -06002471
2472 parser.add_argument(
2473 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002474 help="Test on device directly. Specify the serial device to "
2475 "use with the --device-serial option.")
Anas Nashif73440ea2018-02-19 10:57:03 -06002476 parser.add_argument(
2477 "--device-serial",
Anas Nashif333a3152018-05-24 14:35:33 -05002478 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002479 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002480 "--show-footprint", action="store_true",
2481 help="Show footprint statistics and deltas since last release."
2482 )
2483 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002484 "-H", "--footprint-threshold", type=float, default=5,
2485 help="When checking test case footprint sizes, warn the user if "
2486 "the new app size is greater then the specified percentage "
2487 "from the last release. Default is 5. 0 to warn on any "
2488 "increase on app size")
2489 parser.add_argument(
2490 "-D", "--all-deltas", action="store_true",
2491 help="Show all footprint deltas, positive or negative. Implies "
2492 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002493 parser.add_argument(
2494 "-O", "--outdir",
2495 default="%s/sanity-out" % ZEPHYR_BASE,
2496 help="Output directory for logs and binaries. "
2497 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002498 parser.add_argument(
2499 "-n", "--no-clean", action="store_true",
2500 help="Do not delete the outdir before building. Will result in "
2501 "faster compilation since builds will be incremental")
2502 parser.add_argument(
2503 "-T", "--testcase-root", action="append", default=[],
2504 help="Base directory to recursively search for test cases. All "
2505 "testcase.yaml files under here will be processed. May be "
2506 "called multiple times. Defaults to the 'samples' and "
2507 "'tests' directories in the Zephyr tree.")
2508 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2509 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
2510 parser.add_argument(
2511 "-A", "--board-root", action="append", default=board_root_list,
2512 help="Directory to search for board configuration files. All .yaml "
2513 "files in the directory will be processed.")
2514 parser.add_argument(
2515 "-z", "--size", action="append",
2516 help="Don't run sanity checks. Instead, produce a report to "
2517 "stdout detailing RAM/ROM sizes on the specified filenames. "
2518 "All other command line arguments ignored.")
2519 parser.add_argument(
2520 "-S", "--enable-slow", action="store_true",
2521 help="Execute time-consuming test cases that have been marked "
2522 "as 'slow' in testcase.yaml. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07002523 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002524 default=True,
2525 help="Build all test cases with assertions enabled. "
Anas Nashif333a3152018-05-24 14:35:33 -05002526 "(This is the default)")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002527 parser.add_argument("--disable-asserts", action="store_false",
2528 dest="enable_asserts",
2529 help="Build all test cases with assertions disabled.")
Anas Nashife3febe92016-11-30 14:25:44 -05002530 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002531 help="Error on deprecation warnings.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002532
2533 parser.add_argument(
2534 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002535 help="""Extra CMake cache entries to define when building test cases.
2536 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01002537 prefixed with -D before being passed to CMake.
2538
2539 E.g
2540 "sanitycheck -x=USE_CCACHE=0"
2541 will translate to
2542 "cmake -DUSE_CCACHE=0"
2543
2544 which will ultimately disable ccache.
2545 """
2546 )
2547
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002548 parser.add_argument("-C", "--coverage", action="store_true",
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002549 help="Generate coverage report for unit tests, and"
2550 " tests and samples run in native_posix.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002551
2552 return parser.parse_args()
2553
Anas Nashif3ba1d432017-12-05 15:28:44 -05002554
Andrew Boie6acbe632015-07-17 12:03:52 -07002555def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01002556 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002557 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002558 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08002559
2560 try:
2561 with open(filename) as fp:
2562 data = fp.read()
2563 except Exception as e:
2564 data = "Unable to read log data (%s)\n" % (str(e))
2565
2566 sys.stdout.write(data)
2567 if log_file:
2568 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002569 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002570 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002571 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07002572
Anas Nashif3ba1d432017-12-05 15:28:44 -05002573
Andrew Boie6acbe632015-07-17 12:03:52 -07002574def terse_test_cb(instances, goals, goal):
2575 total_tests = len(goals)
2576 total_done = 0
2577 total_failed = 0
2578
Andrew Boie08ce5a52016-02-22 13:28:10 -08002579 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002580 if g.finished:
2581 total_done += 1
2582 if g.failed:
2583 total_failed += 1
2584
2585 if goal.failed:
2586 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002587 info(
2588 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
2589 i.platform.name,
2590 i.test.name,
2591 COLOR_RED,
2592 COLOR_NORMAL,
2593 goal.reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002594 log_info(goal.get_error_log())
2595 info("")
2596
Anas Nashif3ba1d432017-12-05 15:28:44 -05002597 sys.stdout.write(
2598 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
2599 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
2600 int((float(total_done) / total_tests) * 100),
2601 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
2602 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07002603 sys.stdout.flush()
2604
Anas Nashif3ba1d432017-12-05 15:28:44 -05002605
Andrew Boie6acbe632015-07-17 12:03:52 -07002606def chatty_test_cb(instances, goals, goal):
2607 i = instances[goal.name]
2608
2609 if VERBOSE < 2 and not goal.finished:
2610 return
2611
2612 if goal.failed:
2613 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
2614 elif goal.finished:
2615 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2616 else:
2617 status = goal.make_state
2618
2619 info("{:<25} {:<50} {}".format(i.platform.name, i.test.name, status))
2620 if goal.failed:
2621 log_info(goal.get_error_log())
2622
Andrew Boiebbd670c2015-08-17 13:16:11 -07002623
2624def size_report(sc):
2625 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07002626 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07002627 for i in range(len(sc.sections)):
2628 v = sc.sections[i]
2629
Andrew Boie73b4ee62015-10-07 11:33:22 -07002630 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
2631 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
2632 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07002633
Andrew Boie73b4ee62015-10-07 11:33:22 -07002634 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002635 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002636 info("")
2637
Anas Nashif3ba1d432017-12-05 15:28:44 -05002638
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002639def generate_coverage(outdir, ignores):
2640 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
2641 coveragefile = os.path.join(outdir, "coverage.info")
2642 ztestfile = os.path.join(outdir, "ztest.info")
2643 subprocess.call(["lcov", "--capture", "--directory", outdir,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002644 "--rc", "lcov_branch_coverage=1",
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002645 "--output-file", coveragefile], stdout=coveragelog)
2646 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
2647 subprocess.call(["lcov", "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05002648 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002649 "--output-file", ztestfile,
2650 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
2651
2652 if os.path.getsize(ztestfile) > 0:
2653 subprocess.call(["lcov", "--remove", ztestfile,
2654 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
2655 "--output-file", ztestfile,
2656 "--rc", "lcov_branch_coverage=1"],
2657 stdout=coveragelog)
2658 files = [coveragefile, ztestfile];
2659 else:
2660 files = [coveragefile];
2661
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002662 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002663 subprocess.call(
2664 ["lcov", "--remove", coveragefile, i, "--output-file",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002665 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05002666 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002667
2668 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
2669 "-output-directory",
2670 os.path.join(outdir, "coverage")] + files,
2671 stdout=coveragelog)
2672 if ret==0:
2673 info("HTML report generated: %s"%
2674 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05002675
Andrew Boiebbd670c2015-08-17 13:16:11 -07002676
Andrew Boie6acbe632015-07-17 12:03:52 -07002677def main():
Andrew Boie4b182472015-07-31 12:25:22 -07002678 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002679 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05002680 global options
2681 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07002682
Anas Nashife10b6512017-12-30 13:01:45 -05002683 if options.size:
2684 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08002685 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002686 sys.exit(0)
2687
Anas Nashif73440ea2018-02-19 10:57:03 -06002688
2689 if options.device_testing:
2690 if options.device_serial is None or len(options.platform) != 1:
2691 sys.exit(1)
2692
Anas Nashife10b6512017-12-30 13:01:45 -05002693 VERBOSE += options.verbose
2694 INLINE_LOGS = options.inline_logs
2695 if options.log_file:
2696 log_file = open(options.log_file, "w")
2697 if options.jobs:
2698 CPU_COUNTS = options.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07002699
Anas Nashife10b6512017-12-30 13:01:45 -05002700 if options.subset:
2701 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04002702 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002703 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04002704 else:
Anas Nashife10b6512017-12-30 13:01:45 -05002705 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04002706 return
2707
Anas Nashife10b6512017-12-30 13:01:45 -05002708 if os.path.exists(options.outdir) and not options.no_clean:
2709 info("Cleaning output directory " + options.outdir)
2710 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002711
Anas Nashife10b6512017-12-30 13:01:45 -05002712 if not options.testcase_root:
2713 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07002714 os.path.join(ZEPHYR_BASE, "samples")]
2715
Anas Nashif37f9dc52018-02-23 08:53:46 -06002716 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04002717
Kumar Galac84235e2018-04-10 13:32:51 -05002718 if ts.load_errors:
2719 sys.exit(1)
2720
Anas Nashif75547e22018-02-24 08:32:14 -06002721 if options.list_tags:
2722 tags = set()
2723 for n,tc in ts.testcases.items():
2724 tags = tags.union(tc.tags)
2725
2726 for t in tags:
2727 print("- {}".format(t))
2728
2729 return
2730
Anas Nashifc0149cc2018-04-14 23:12:58 -05002731 if options.list_tests:
2732 cnt = 0
Anas Nashifa3abe962018-05-05 19:10:22 -05002733 unq = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05002734 for n,tc in ts.testcases.items():
2735 for c in tc.cases:
Anas Nashifa3abe962018-05-05 19:10:22 -05002736 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002737
Anas Nashifa3abe962018-05-05 19:10:22 -05002738 for u in sorted(set(unq)):
2739 cnt = cnt + 1
2740 print(" - {}".format(u))
Anas Nashifc0149cc2018-04-14 23:12:58 -05002741 print("{} total.".format(cnt))
2742 return
2743
Anas Nashifbd166f42017-09-02 12:32:08 -04002744 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05002745 if options.load_tests:
2746 ts.load_from_file(options.load_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002747 else:
Anas Nashif4f028882017-12-30 11:48:43 -05002748 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07002749
Anas Nashife10b6512017-12-30 13:01:45 -05002750 if options.discard_report:
2751 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07002752
Anas Nashif30551f42018-01-12 21:56:59 -05002753 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002754 # if we are using command line platform filter, no need to list every
2755 # other platform as excluded, we know that already.
2756 # Show only the discards that apply to the selected platforms on the
2757 # command line
2758
Andrew Boie08ce5a52016-02-22 13:28:10 -08002759 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002760 if options.platform and i.platform.name not in options.platform:
2761 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05002762 debug(
2763 "{:<25} {:<50} {}SKIPPED{}: {}".format(
2764 i.platform.name,
2765 i.test.name,
2766 COLOR_YELLOW,
2767 COLOR_NORMAL,
2768 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002769
Anas Nashif1a5bba72018-01-05 08:07:45 -05002770
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002771 def native_posix_and_unit_first(a, b):
2772 if a[0].startswith('native_posix') or a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002773 return -1
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002774 if b[0].startswith('native_posix') or b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002775 return 1
2776 return (a > b) - (a < b)
2777
2778 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002779 key=cmp_to_key(native_posix_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04002780
Anas Nashife10b6512017-12-30 13:01:45 -05002781 if options.save_tests:
2782 ts.run_report(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002783 return
2784
Anas Nashife10b6512017-12-30 13:01:45 -05002785 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05002786
Anas Nashife10b6512017-12-30 13:01:45 -05002787 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04002788 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04002789 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05002790 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04002791 if subset == sets:
2792 end = total
2793 else:
2794 end = start + per_set
2795
Anas Nashif3ba1d432017-12-05 15:28:44 -05002796 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04002797 ts.instances = OrderedDict(sliced_instances)
2798
Andrew Boie6acbe632015-07-17 12:03:52 -07002799 info("%d tests selected, %d tests discarded due to filters" %
2800 (len(ts.instances), len(discards)))
2801
Anas Nashife10b6512017-12-30 13:01:45 -05002802 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07002803 return
2804
2805 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002806 goals = ts.execute(
2807 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002808 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07002809 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002810 goals = ts.execute(
2811 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002812 ts.instances)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002813 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07002814
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002815 if options.detailed_report:
2816 ts.testcase_target_report(options.detailed_report)
2817
Daniel Leung7f850102016-04-08 11:07:32 -07002818 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05002819 if options.compare_report:
2820 report_to_use = options.compare_report
2821 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07002822 report_to_use = LAST_SANITY
2823 else:
2824 report_to_use = RELEASE_DATA
2825
2826 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07002827 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06002828 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07002829 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05002830 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07002831 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07002832 continue
2833
Andrew Boieea7928f2015-08-14 14:27:38 -07002834 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05002835 if not options.all_deltas and (percentage <
2836 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07002837 continue
2838
Daniel Leung00525c22016-04-11 10:27:56 -07002839 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07002840 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05002841 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07002842 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07002843 warnings += 1
2844
2845 if warnings:
2846 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05002847 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07002848
2849 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08002850 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002851 if goal.failed:
2852 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002853 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07002854 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2855 (COLOR_RED, COLOR_NORMAL, goal.name,
2856 str(goal.metrics["unrecognized"])))
2857 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002858
Anas Nashife10b6512017-12-30 13:01:45 -05002859 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002860 info("Generating coverage files...")
Anas Nashife10b6512017-12-30 13:01:45 -05002861 generate_coverage(options.outdir, ["tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002862
Anas Nashif0605fa32017-05-07 08:51:02 -04002863 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07002864 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002865 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
2866 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
2867 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07002868
Anas Nashife10b6512017-12-30 13:01:45 -05002869 if options.testcase_report:
2870 ts.testcase_report(options.testcase_report)
2871 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05002872 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07002873 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05002874 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07002875 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002876 if log_file:
2877 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05002878 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07002879 sys.exit(1)
2880
Anas Nashif3ba1d432017-12-05 15:28:44 -05002881
Andrew Boie6acbe632015-07-17 12:03:52 -07002882if __name__ == "__main__":
2883 main()