blob: b46dcaccbf9d16de985e327044162fa09ec5eab1 [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"
Anas Nashif13773752018-07-06 18:20:23 -0500305 self.run = False
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300306 self.metrics = {}
Anas Nashifc8390f12017-11-25 17:14:12 -0500307 self.metrics["handler_time"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300308 self.metrics["ram_size"] = 0
309 self.metrics["rom_size"] = 0
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300310
Anas Nashifd3384fb2018-02-22 06:44:16 -0600311 self.name = instance.name
312 self.instance = instance
313 self.timeout = instance.test.timeout
314 self.sourcedir = instance.test.code_location
315 self.outdir = instance.outdir
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500316 self.log = os.path.join(self.outdir, "handler.log")
Anas Nashifd3384fb2018-02-22 06:44:16 -0600317 self.returncode = 0
318 self.set_state("running", {})
319
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300320 def set_state(self, state, metrics):
321 self.lock.acquire()
322 self.state = state
323 self.metrics.update(metrics)
324 self.lock.release()
325
326 def get_state(self):
327 self.lock.acquire()
328 ret = (self.state, self.metrics)
329 self.lock.release()
330 return ret
331
Anas Nashif73440ea2018-02-19 10:57:03 -0600332
333class DeviceHandler(Handler):
334
335 def __init__(self, instance):
336 """Constructor
337
338 @param instance Test Instance
339 """
340 super().__init__(instance)
341
Anas Nashif73440ea2018-02-19 10:57:03 -0600342 def monitor_serial(self, ser, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500343 log_out_fp = open(self.log, "wt")
Anas Nashif73440ea2018-02-19 10:57:03 -0600344
345 while ser.isOpen():
Anas Nashif61e21632018-04-08 13:30:16 -0500346 try:
347 serial_line = ser.readline()
348 except TypeError:
349 pass
350
Anas Nashif73440ea2018-02-19 10:57:03 -0600351 if serial_line:
Anas Nashifd3384fb2018-02-22 06:44:16 -0600352 sl = serial_line.decode('utf-8', 'ignore')
353 verbose("DEVICE: {0}".format(sl.rstrip()))
354
355 log_out_fp.write(sl)
356 log_out_fp.flush()
357 harness.handle(sl.rstrip())
Anas Nashif73440ea2018-02-19 10:57:03 -0600358 if harness.state:
359 ser.close()
360 break
361
362 log_out_fp.close()
363
364 def handle(self):
365 out_state = "failed"
366
Anas Nashifd3384fb2018-02-22 06:44:16 -0600367 if options.ninja:
368 generator_cmd = "ninja"
369 else:
370 generator_cmd = "make"
371
372 command = [generator_cmd, "-C", self.outdir, "flash"]
373
Anas Nashif73440ea2018-02-19 10:57:03 -0600374 device = options.device_serial
375 ser = serial.Serial(
376 device,
377 baudrate=115200,
378 parity=serial.PARITY_NONE,
379 stopbits=serial.STOPBITS_ONE,
380 bytesize=serial.EIGHTBITS,
381 timeout=self.timeout
382 )
383
384 ser.flush()
385
386 harness_name = self.instance.test.harness.capitalize()
387 harness_import = HarnessImporter(harness_name)
388 harness = harness_import.instance
389 harness.configure(self.instance)
390
391 t = threading.Thread(target=self.monitor_serial, args=(ser, harness))
392 t.start()
393
Anas Nashif61e21632018-04-08 13:30:16 -0500394 try:
395 subprocess.check_output(command, stderr=subprocess.PIPE)
396 except subprocess.CalledProcessError:
397 pass
Anas Nashif73440ea2018-02-19 10:57:03 -0600398
399 t.join(self.timeout)
400 if t.is_alive():
401 out_state = "timeout"
402 ser.close()
403
404 if ser.isOpen():
405 ser.close()
406
Anas Nashifd3384fb2018-02-22 06:44:16 -0600407 if out_state == "timeout":
408 for c in self.instance.test.cases:
409 if c not in harness.tests:
410 harness.tests[c] = "BLOCK"
Anas Nashif61e21632018-04-08 13:30:16 -0500411
412 self.instance.results = harness.tests
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600413
Anas Nashif73440ea2018-02-19 10:57:03 -0600414 if harness.state:
415 self.set_state(harness.state, {})
416 else:
417 self.set_state(out_state, {})
418
Anas Nashifcc164222017-12-26 11:02:46 -0500419class NativeHandler(Handler):
Anas Nashif576be982017-12-23 20:20:27 -0500420 def __init__(self, instance):
Anas Nashifcc164222017-12-26 11:02:46 -0500421 """Constructor
422
Anas Nashifb630ca62018-01-29 08:38:57 -0500423 @param instance Test Instance
Anas Nashifcc164222017-12-26 11:02:46 -0500424 """
Anas Nashif576be982017-12-23 20:20:27 -0500425 super().__init__(instance)
Anas Nashifcc164222017-12-26 11:02:46 -0500426
Anas Nashifcc164222017-12-26 11:02:46 -0500427 self.valgrind = False
Anas Nashifcc164222017-12-26 11:02:46 -0500428
Anas Nashiffd6eb762017-12-31 11:16:49 -0500429 def _output_reader(self, proc, harness):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500430 log_out_fp = open(self.log, "wt")
Anas Nashif576be982017-12-23 20:20:27 -0500431 for line in iter(proc.stdout.readline, b''):
432 verbose("NATIVE: {0}".format(line.decode('utf-8').rstrip()))
Anas Nashiffd6eb762017-12-31 11:16:49 -0500433 log_out_fp.write(line.decode('utf-8'))
434 log_out_fp.flush()
Anas Nashif576be982017-12-23 20:20:27 -0500435 harness.handle(line.decode('utf-8').rstrip())
436 if harness.state:
437 proc.terminate()
438 break
439
Anas Nashiffd6eb762017-12-31 11:16:49 -0500440 log_out_fp.close()
441
Anas Nashifcc164222017-12-26 11:02:46 -0500442 def handle(self):
443 out_state = "failed"
444
Anas Nashif576be982017-12-23 20:20:27 -0500445 harness_name = self.instance.test.harness.capitalize()
446 harness_import = HarnessImporter(harness_name)
447 harness = harness_import.instance
448 harness.configure(self.instance)
Anas Nashifcc164222017-12-26 11:02:46 -0500449
Anas Nashif576be982017-12-23 20:20:27 -0500450 binary = os.path.join(self.outdir, "zephyr", "zephyr.exe")
451 command = [binary]
452 if shutil.which("valgrind") and self.valgrind:
453 command = ["valgrind", "--error-exitcode=2",
454 "--leak-check=full"] + command
455
456 with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
Anas Nashiffd6eb762017-12-31 11:16:49 -0500457 t = threading.Thread(target=self._output_reader, args=(proc, harness, ))
Anas Nashif576be982017-12-23 20:20:27 -0500458 t.start()
459 t.join(self.timeout)
460 if t.is_alive():
461 proc.terminate()
Anas Nashifcc164222017-12-26 11:02:46 -0500462 out_state = "timeout"
Anas Nashif576be982017-12-23 20:20:27 -0500463 t.join()
Anas Nashifcc164222017-12-26 11:02:46 -0500464
Anas Nashif576be982017-12-23 20:20:27 -0500465 proc.wait()
466 self.returncode = proc.returncode
467 if proc.returncode != 0:
Anas Nashifa49048b2018-01-29 08:41:19 -0500468 out_state = "failed"
Anas Nashifcc164222017-12-26 11:02:46 -0500469
Anas Nashif576be982017-12-23 20:20:27 -0500470 returncode = subprocess.call(["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-b", "-s", self.outdir], shell=True)
Anas Nashifcc164222017-12-26 11:02:46 -0500471
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600472
473 self.instance.results = harness.tests
Anas Nashif576be982017-12-23 20:20:27 -0500474 if harness.state:
475 self.set_state(harness.state, {})
476 else:
477 self.set_state(out_state, {})
478
Anas Nashif3ba1d432017-12-05 15:28:44 -0500479
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300480class UnitHandler(Handler):
Anas Nashif576be982017-12-23 20:20:27 -0500481 def __init__(self, instance):
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300482 """Constructor
483
Anas Nashifb630ca62018-01-29 08:38:57 -0500484 @param instance Test instance
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300485 """
Anas Nashif576be982017-12-23 20:20:27 -0500486 super().__init__(instance)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300487
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300488
489 def handle(self):
490 out_state = "failed"
491
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500492 with open(self.log, "wt") as hl:
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300493 try:
494 binary = os.path.join(self.outdir, "testbinary")
495 command = [binary]
496 if shutil.which("valgrind"):
497 command = ["valgrind", "--error-exitcode=2",
498 "--leak-check=full"] + command
499 returncode = subprocess.call(command, timeout=self.timeout,
Anas Nashifd3384fb2018-02-22 06:44:16 -0600500 stdout=hl, stderr=hl)
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300501 self.returncode = returncode
502 if returncode != 0:
503 if self.returncode == 1:
504 out_state = "failed"
505 else:
506 out_state = "failed valgrind"
507 else:
508 out_state = "passed"
509 except subprocess.TimeoutExpired:
510 out_state = "timeout"
511 self.returncode = 1
512
Anas Nashif3ba1d432017-12-05 15:28:44 -0500513 returncode = subprocess.call(
514 ["GCOV_PREFIX=" + self.outdir, "gcov", self.sourcedir, "-s", self.outdir], shell=True)
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +0300515
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300516 self.set_state(out_state, {})
517
Anas Nashif3ba1d432017-12-05 15:28:44 -0500518
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300519class QEMUHandler(Handler):
Andrew Boie6acbe632015-07-17 12:03:52 -0700520 """Spawns a thread to monitor QEMU output from pipes
521
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400522 We pass QEMU_PIPE to 'make run' and monitor the pipes for output.
Andrew Boie6acbe632015-07-17 12:03:52 -0700523 We need to do this as once qemu starts, it runs forever until killed.
524 Test cases emit special messages to the console as they run, we check
525 for these to collect whether the test passed or failed.
526 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700527
528 @staticmethod
Anas Nashif576be982017-12-23 20:20:27 -0500529 def _thread(handler, timeout, outdir, logfile, fifo_fn, pid_fn, results, harness):
Andrew Boie6acbe632015-07-17 12:03:52 -0700530 fifo_in = fifo_fn + ".in"
531 fifo_out = fifo_fn + ".out"
532
533 # These in/out nodes are named from QEMU's perspective, not ours
534 if os.path.exists(fifo_in):
535 os.unlink(fifo_in)
536 os.mkfifo(fifo_in)
537 if os.path.exists(fifo_out):
538 os.unlink(fifo_out)
539 os.mkfifo(fifo_out)
540
541 # We don't do anything with out_fp but we need to open it for
542 # writing so that QEMU doesn't block, due to the way pipes work
543 out_fp = open(fifo_in, "wb")
544 # Disable internal buffering, we don't
545 # want read() or poll() to ever block if there is data in there
546 in_fp = open(fifo_out, "rb", buffering=0)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800547 log_out_fp = open(logfile, "wt")
Andrew Boie6acbe632015-07-17 12:03:52 -0700548
549 start_time = time.time()
550 timeout_time = start_time + timeout
551 p = select.poll()
552 p.register(in_fp, select.POLLIN)
553
554 metrics = {}
555 line = ""
556 while True:
557 this_timeout = int((timeout_time - time.time()) * 1000)
558 if this_timeout < 0 or not p.poll(this_timeout):
559 out_state = "timeout"
560 break
561
Genaro Saucedo Tejada2968b362016-08-09 15:11:53 -0500562 try:
563 c = in_fp.read(1).decode("utf-8")
564 except UnicodeDecodeError:
565 # Test is writing something weird, fail
566 out_state = "unexpected byte"
567 break
568
Andrew Boie6acbe632015-07-17 12:03:52 -0700569 if c == "":
570 # EOF, this shouldn't happen unless QEMU crashes
571 out_state = "unexpected eof"
572 break
573 line = line + c
574 if c != "\n":
575 continue
576
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300577 # line contains a full line of data output from QEMU
Andrew Boie6acbe632015-07-17 12:03:52 -0700578 log_out_fp.write(line)
579 log_out_fp.flush()
580 line = line.strip()
581 verbose("QEMU: %s" % line)
582
Anas Nashif576be982017-12-23 20:20:27 -0500583 harness.handle(line)
584 if harness.state:
585 out_state = harness.state
Andrew Boie6acbe632015-07-17 12:03:52 -0700586 break
587
588 # TODO: Add support for getting numerical performance data
589 # from test cases. Will involve extending test case reporting
590 # APIs. Add whatever gets reported to the metrics dictionary
591 line = ""
592
Anas Nashifc8390f12017-11-25 17:14:12 -0500593 metrics["handler_time"] = time.time() - start_time
Andrew Boie6acbe632015-07-17 12:03:52 -0700594 verbose("QEMU complete (%s) after %f seconds" %
Anas Nashifc8390f12017-11-25 17:14:12 -0500595 (out_state, metrics["handler_time"]))
Andrew Boie6acbe632015-07-17 12:03:52 -0700596 handler.set_state(out_state, metrics)
597
598 log_out_fp.close()
599 out_fp.close()
600 in_fp.close()
601
602 pid = int(open(pid_fn).read())
603 os.unlink(pid_fn)
Andrew Boie08ce5a52016-02-22 13:28:10 -0800604 try:
605 os.kill(pid, signal.SIGTERM)
606 except ProcessLookupError:
607 # Oh well, as long as it's dead! User probably sent Ctrl-C
608 pass
609
Andrew Boie6acbe632015-07-17 12:03:52 -0700610 os.unlink(fifo_in)
611 os.unlink(fifo_out)
612
Anas Nashif576be982017-12-23 20:20:27 -0500613 def __init__(self, instance):
Andrew Boie6acbe632015-07-17 12:03:52 -0700614 """Constructor
615
Anas Nashifd3384fb2018-02-22 06:44:16 -0600616 @param instance Test instance
Andrew Boie6acbe632015-07-17 12:03:52 -0700617 """
Anas Nashif576be982017-12-23 20:20:27 -0500618
Anas Nashif576be982017-12-23 20:20:27 -0500619 super().__init__(instance)
Anas Nashif576be982017-12-23 20:20:27 -0500620
Andrew Boie6acbe632015-07-17 12:03:52 -0700621 self.results = {}
Anas Nashif13773752018-07-06 18:20:23 -0500622 self.run = True
Andrew Boie6acbe632015-07-17 12:03:52 -0700623
624 # We pass this to QEMU which looks for fifos with .in and .out
625 # suffixes.
Anas Nashif576be982017-12-23 20:20:27 -0500626 self.fifo_fn = os.path.join(instance.outdir, "qemu-fifo")
Andrew Boie6acbe632015-07-17 12:03:52 -0700627
Anas Nashif576be982017-12-23 20:20:27 -0500628 self.pid_fn = os.path.join(instance.outdir, "qemu.pid")
Andrew Boie6acbe632015-07-17 12:03:52 -0700629 if os.path.exists(self.pid_fn):
630 os.unlink(self.pid_fn)
631
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500632 self.log_fn = self.log
Anas Nashif576be982017-12-23 20:20:27 -0500633
634 harness_import = HarnessImporter(instance.test.harness.capitalize())
635 harness = harness_import.instance
Anas Nashifd3384fb2018-02-22 06:44:16 -0600636 harness.configure(self.instance)
637 self.thread = threading.Thread(name=self.name, target=QEMUHandler._thread,
638 args=(self, self.timeout, self.outdir,
Jaakko Hannikainenca505f82016-08-22 15:03:46 +0300639 self.log_fn, self.fifo_fn,
Anas Nashif576be982017-12-23 20:20:27 -0500640 self.pid_fn, self.results, harness))
Anas Nashife0a6a0b2018-02-15 20:07:24 -0600641
642 self.instance.results = harness.tests
Andrew Boie6acbe632015-07-17 12:03:52 -0700643 self.thread.daemon = True
Anas Nashifd3384fb2018-02-22 06:44:16 -0600644 verbose("Spawning QEMU process for %s" % self.name)
Andrew Boie6acbe632015-07-17 12:03:52 -0700645 self.thread.start()
646
Andrew Boie6acbe632015-07-17 12:03:52 -0700647 def get_fifo(self):
648 return self.fifo_fn
649
Anas Nashif3ba1d432017-12-05 15:28:44 -0500650
Andrew Boie6acbe632015-07-17 12:03:52 -0700651class SizeCalculator:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700652
Erwin Rolcb3d1272018-02-10 11:40:40 +0100653 alloc_sections = ["bss", "noinit", "app_bss", "app_noinit", "ccm_bss",
654 "ccm_noinit"]
Andrew Boie18ba1532017-01-17 13:47:06 -0800655 rw_sections = ["datas", "initlevel", "_k_task_list", "_k_event_list",
Allan Stephens3f5c74c2016-11-01 14:37:15 -0500656 "_k_memory_pool", "exceptions", "initshell",
Andrew Boie3ef0b562017-08-31 12:36:45 -0700657 "_static_thread_area", "_k_timer_area", "_k_work_area",
658 "_k_mem_slab_area", "_k_mem_pool_area",
659 "_k_sem_area", "_k_mutex_area", "_k_alert_area",
660 "_k_fifo_area", "_k_lifo_area", "_k_stack_area",
661 "_k_msgq_area", "_k_mbox_area", "_k_pipe_area",
Jukka Rissanen60492072018-02-07 15:00:08 +0200662 "net_if", "net_if_dev", "net_stack", "net_l2_data",
Andrew Boie945af952017-08-22 13:15:23 -0700663 "_k_queue_area", "_net_buf_pool_area", "app_datas",
Erwin Rolcb3d1272018-02-10 11:40:40 +0100664 "kobject_data", "mmu_tables", "app_pad", "priv_stacks",
Anas Nashif0b685602018-06-29 12:57:47 -0500665 "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc",
666 'log_backends_sections', 'log_dynamic_sections',
667 'log_const_sections']
Andrew Boie73b4ee62015-10-07 11:33:22 -0700668 # These get copied into RAM only on non-XIP
Andrew Boie877f82e2017-10-17 11:20:22 -0700669 ro_sections = ["text", "ctors", "init_array", "reset", "object_access",
Andy Rossa3a7e8e2018-05-23 16:39:16 -0700670 "rodata", "devconfig", "net_l2", "vector", "_bt_settings_area"]
Andrew Boie73b4ee62015-10-07 11:33:22 -0700671
Andrew Boie52fef672016-11-29 12:21:59 -0800672 def __init__(self, filename, extra_sections):
Andrew Boie6acbe632015-07-17 12:03:52 -0700673 """Constructor
674
Andrew Boiebbd670c2015-08-17 13:16:11 -0700675 @param filename Path to the output binary
676 The <filename> is parsed by objdump to determine section sizes
Andrew Boie6acbe632015-07-17 12:03:52 -0700677 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700678 # Make sure this is an ELF binary
Andrew Boiebbd670c2015-08-17 13:16:11 -0700679 with open(filename, "rb") as f:
Andrew Boie6acbe632015-07-17 12:03:52 -0700680 magic = f.read(4)
681
Andrew Boie08ce5a52016-02-22 13:28:10 -0800682 if (magic != b'\x7fELF'):
Andrew Boiebbd670c2015-08-17 13:16:11 -0700683 raise SanityRuntimeError("%s is not an ELF binary" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700684
685 # Search for CONFIG_XIP in the ELF's list of symbols using NM and AWK.
Anas Nashif3ba1d432017-12-05 15:28:44 -0500686 # GREP can not be used as it returns an error if the symbol is not
687 # found.
688 is_xip_command = "nm " + filename + \
689 " | awk '/CONFIG_XIP/ { print $3 }'"
690 is_xip_output = subprocess.check_output(
691 is_xip_command, shell=True, stderr=subprocess.STDOUT).decode(
692 "utf-8").strip()
Andrew Boie8f0211d2016-03-02 20:40:29 -0800693 if is_xip_output.endswith("no symbols"):
694 raise SanityRuntimeError("%s has no symbol information" % filename)
Andrew Boie6acbe632015-07-17 12:03:52 -0700695 self.is_xip = (len(is_xip_output) != 0)
696
Andrew Boiebbd670c2015-08-17 13:16:11 -0700697 self.filename = filename
Andrew Boie73b4ee62015-10-07 11:33:22 -0700698 self.sections = []
699 self.rom_size = 0
Andrew Boie6acbe632015-07-17 12:03:52 -0700700 self.ram_size = 0
Andrew Boie52fef672016-11-29 12:21:59 -0800701 self.extra_sections = extra_sections
Andrew Boie6acbe632015-07-17 12:03:52 -0700702
703 self._calculate_sizes()
704
705 def get_ram_size(self):
706 """Get the amount of RAM the application will use up on the device
707
708 @return amount of RAM, in bytes
709 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700710 return self.ram_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700711
712 def get_rom_size(self):
713 """Get the size of the data that this application uses on device's flash
714
715 @return amount of ROM, in bytes
716 """
Andrew Boie73b4ee62015-10-07 11:33:22 -0700717 return self.rom_size
Andrew Boie6acbe632015-07-17 12:03:52 -0700718
719 def unrecognized_sections(self):
720 """Get a list of sections inside the binary that weren't recognized
721
David B. Kinder29963c32017-06-16 12:32:42 -0700722 @return list of unrecognized section names
Andrew Boie6acbe632015-07-17 12:03:52 -0700723 """
724 slist = []
Andrew Boie73b4ee62015-10-07 11:33:22 -0700725 for v in self.sections:
Andrew Boie6acbe632015-07-17 12:03:52 -0700726 if not v["recognized"]:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700727 slist.append(v["name"])
Andrew Boie6acbe632015-07-17 12:03:52 -0700728 return slist
729
730 def _calculate_sizes(self):
731 """ Calculate RAM and ROM usage by section """
Andrew Boiebbd670c2015-08-17 13:16:11 -0700732 objdump_command = "objdump -h " + self.filename
Anas Nashif3ba1d432017-12-05 15:28:44 -0500733 objdump_output = subprocess.check_output(
734 objdump_command, shell=True).decode("utf-8").splitlines()
Andrew Boie6acbe632015-07-17 12:03:52 -0700735
736 for line in objdump_output:
737 words = line.split()
738
739 if (len(words) == 0): # Skip lines that are too short
740 continue
741
742 index = words[0]
743 if (not index[0].isdigit()): # Skip lines that do not start
744 continue # with a digit
745
746 name = words[1] # Skip lines with section names
747 if (name[0] == '.'): # starting with '.'
748 continue
749
Andrew Boie73b4ee62015-10-07 11:33:22 -0700750 # TODO this doesn't actually reflect the size in flash or RAM as
751 # it doesn't include linker-imposed padding between sections.
752 # It is close though.
Andrew Boie6acbe632015-07-17 12:03:52 -0700753 size = int(words[2], 16)
Andrew Boie9882dcd2015-10-07 14:25:51 -0700754 if size == 0:
755 continue
756
Andrew Boie73b4ee62015-10-07 11:33:22 -0700757 load_addr = int(words[4], 16)
758 virt_addr = int(words[3], 16)
Andrew Boie6acbe632015-07-17 12:03:52 -0700759
760 # Add section to memory use totals (for both non-XIP and XIP scenarios)
Andrew Boie6acbe632015-07-17 12:03:52 -0700761 # Unrecognized section names are not included in the calculations.
Andrew Boie6acbe632015-07-17 12:03:52 -0700762 recognized = True
Andrew Boie73b4ee62015-10-07 11:33:22 -0700763 if name in SizeCalculator.alloc_sections:
764 self.ram_size += size
765 stype = "alloc"
766 elif name in SizeCalculator.rw_sections:
767 self.ram_size += size
768 self.rom_size += size
769 stype = "rw"
770 elif name in SizeCalculator.ro_sections:
771 self.rom_size += size
772 if not self.is_xip:
773 self.ram_size += size
774 stype = "ro"
Andrew Boie6acbe632015-07-17 12:03:52 -0700775 else:
Andrew Boie73b4ee62015-10-07 11:33:22 -0700776 stype = "unknown"
Andrew Boie52fef672016-11-29 12:21:59 -0800777 if name not in self.extra_sections:
778 recognized = False
Andrew Boie6acbe632015-07-17 12:03:52 -0700779
Anas Nashif3ba1d432017-12-05 15:28:44 -0500780 self.sections.append({"name": name, "load_addr": load_addr,
781 "size": size, "virt_addr": virt_addr,
782 "type": stype, "recognized": recognized})
Andrew Boie6acbe632015-07-17 12:03:52 -0700783
784
785class MakeGoal:
786 """Metadata class representing one of the sub-makes called by MakeGenerator
787
David B. Kinder29963c32017-06-16 12:32:42 -0700788 MakeGenerator returns a dictionary of these which can then be associated
Andrew Boie6acbe632015-07-17 12:03:52 -0700789 with TestInstances to get a complete picture of what happened during a test.
790 MakeGenerator is used for tasks outside of building tests (such as
791 defconfigs) which is why MakeGoal is a separate class from TestInstance.
792 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500793
Anas Nashif4d25b502017-11-25 17:37:17 -0500794 def __init__(self, name, text, handler, make_log, build_log, run_log, handler_log):
Andrew Boie6acbe632015-07-17 12:03:52 -0700795 self.name = name
796 self.text = text
Anas Nashif4d25b502017-11-25 17:37:17 -0500797 self.handler = handler
Andrew Boie6acbe632015-07-17 12:03:52 -0700798 self.make_log = make_log
799 self.build_log = build_log
800 self.run_log = run_log
Anas Nashif4d25b502017-11-25 17:37:17 -0500801 self.handler_log = handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700802 self.make_state = "waiting"
803 self.failed = False
804 self.finished = False
805 self.reason = None
806 self.metrics = {}
807
808 def get_error_log(self):
809 if self.make_state == "waiting":
810 # Shouldn't ever see this; breakage in the main Makefile itself.
811 return self.make_log
812 elif self.make_state == "building":
813 # Failure when calling the sub-make to build the code
814 return self.build_log
815 elif self.make_state == "running":
Anas Nashif3ac7b3a2017-08-03 10:03:02 -0400816 # Failure in sub-make for "make run", qemu probably failed to start
Andrew Boie6acbe632015-07-17 12:03:52 -0700817 return self.run_log
818 elif self.make_state == "finished":
Anas Nashifcc164222017-12-26 11:02:46 -0500819 # Execution handler finished, but timed out or otherwise wasn't successful
Anas Nashif4d25b502017-11-25 17:37:17 -0500820 return self.handler_log
Andrew Boie6acbe632015-07-17 12:03:52 -0700821
822 def fail(self, reason):
823 self.failed = True
824 self.finished = True
825 self.reason = reason
826
827 def success(self):
828 self.finished = True
829
830 def __str__(self):
831 if self.finished:
832 if self.failed:
833 return "[%s] failed (%s: see %s)" % (self.name, self.reason,
834 self.get_error_log())
835 else:
836 return "[%s] passed" % self.name
837 else:
838 return "[%s] in progress (%s)" % (self.name, self.make_state)
839
840
841class MakeGenerator:
842 """Generates a Makefile which just calls a bunch of sub-make sessions
843
844 In any given test suite we may need to build dozens if not hundreds of
845 test cases. The cleanest way to parallelize this is to just let Make
846 do the parallelization, sharing the jobserver among all the different
847 sub-make targets.
848 """
849
850 GOAL_HEADER_TMPL = """.PHONY: {goal}
851{goal}:
852"""
853
854 MAKE_RULE_TMPL = """\t@echo sanity_test_{phase} {goal} >&2
Anas Nashiffb91ad62017-10-31 08:33:17 -0400855\tcmake \\
Anas Nashifa8a13882017-12-30 13:01:06 -0500856\t\t-G"{generator}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400857\t\t-H{directory}\\
858\t\t-B{outdir}\\
859\t\t-DEXTRA_CFLAGS="-Werror {cflags}"\\
860\t\t-DEXTRA_AFLAGS=-Wa,--fatal-warnings\\
Anas Nashif262e4a42017-12-14 08:42:45 -0500861\t\t-DEXTRA_LDFLAGS="{ldflags}"\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400862\t\t{args}\\
863\t\t>{logfile} 2>&1
Anas Nashifa8a13882017-12-30 13:01:06 -0500864\t{generator_cmd} -C {outdir}\\
865\t\t{verb} {make_args}\\
Anas Nashiffb91ad62017-10-31 08:33:17 -0400866\t\t>>{logfile} 2>&1
Andrew Boie6acbe632015-07-17 12:03:52 -0700867"""
Anas Nashif20f553f2018-03-23 11:26:41 -0500868 MAKE_RULE_TMPL_RUN = """\t@echo sanity_test_{phase} {goal} >&2
869\t{generator_cmd} -C {outdir}\\
870\t\t{verb} {make_args}\\
871\t\t>>{logfile} 2>&1
872"""
Andrew Boie6acbe632015-07-17 12:03:52 -0700873
874 GOAL_FOOTER_TMPL = "\t@echo sanity_test_finished {goal} >&2\n\n"
875
Anas Nashif3ba1d432017-12-05 15:28:44 -0500876 re_make = re.compile(
877 "sanity_test_([A-Za-z0-9]+) (.+)|$|make[:] \*\*\* \[(.+:.+: )?(.+)\] Error.+$")
Andrew Boie6acbe632015-07-17 12:03:52 -0700878
Anas Nashif37f9dc52018-02-23 08:53:46 -0600879 def __init__(self, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -0700880 """MakeGenerator constructor
881
882 @param base_outdir Intended to be the base out directory. A make.log
883 file will be created here which contains the output of the
884 top-level Make session, as well as the dynamic control Makefile
885 @param verbose If true, pass V=1 to all the sub-makes which greatly
886 increases their verbosity
887 """
888 self.goals = {}
889 if not os.path.exists(base_outdir):
890 os.makedirs(base_outdir)
891 self.logfile = os.path.join(base_outdir, "make.log")
892 self.makefile = os.path.join(base_outdir, "Makefile")
Anas Nashif37f9dc52018-02-23 08:53:46 -0600893 self.deprecations = options.error_on_deprecations
Andrew Boie6acbe632015-07-17 12:03:52 -0700894
895 def _get_rule_header(self, name):
896 return MakeGenerator.GOAL_HEADER_TMPL.format(goal=name)
897
Anas Nashif3ba1d432017-12-05 15:28:44 -0500898 def _get_sub_make(self, name, phase, workdir, outdir,
899 logfile, args, make_args=""):
Anas Nashiffb91ad62017-10-31 08:33:17 -0400900 """
901 @param args Arguments given to CMake
902 @param make_args Arguments given to the Makefile generated by CMake
903 """
Anas Nashif3ba1d432017-12-05 15:28:44 -0500904 args = " ".join(["-D{}".format(a) for a in args])
Anas Nashif262e4a42017-12-14 08:42:45 -0500905 ldflags = ""
Andrew Boie29599f62018-05-24 13:33:09 -0700906 cflags = ""
Anas Nashife3febe92016-11-30 14:25:44 -0500907
908 if self.deprecations:
909 cflags = cflags + " -Wno-deprecated-declarations"
Andrew Boief7a6e282017-02-02 13:04:57 -0800910
Alberto Escolar Piedras770178b2018-05-02 13:49:51 +0200911 ldflags="-Wl,--fatal-warnings"
Anas Nashif262e4a42017-12-14 08:42:45 -0500912
Anas Nashif25f6ab62018-03-06 07:15:11 -0600913 if options.ninja:
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100914 generator = "Ninja"
Andy Rossdec163f2018-05-21 10:12:59 -0700915 generator_cmd = "ninja -j1"
Sebastian Bøe0e6689d2018-01-18 14:40:07 +0100916 verb = "-v" if VERBOSE else ""
Anas Nashif25f6ab62018-03-06 07:15:11 -0600917 else:
918 generator = "Unix Makefiles"
919 generator_cmd = "$(MAKE)"
Andrew Boie3efd2692018-06-26 10:41:35 -0700920 verb = "VERBOSE=1" if VERBOSE else ""
Anas Nashifa8a13882017-12-30 13:01:06 -0500921
Anas Nashif20f553f2018-03-23 11:26:41 -0500922 if phase == 'running':
923 return MakeGenerator.MAKE_RULE_TMPL_RUN.format(
924 generator_cmd=generator_cmd,
925 phase=phase,
926 goal=name,
927 outdir=outdir,
928 verb=verb,
929 logfile=logfile,
930 make_args=make_args
931 )
932 else:
933 return MakeGenerator.MAKE_RULE_TMPL.format(
934 generator=generator,
935 generator_cmd=generator_cmd,
936 phase=phase,
937 goal=name,
938 outdir=outdir,
939 cflags=cflags,
940 ldflags=ldflags,
941 directory=workdir,
942 verb=verb,
943 args=args,
944 logfile=logfile,
945 make_args=make_args
946 )
Andrew Boie6acbe632015-07-17 12:03:52 -0700947
948 def _get_rule_footer(self, name):
949 return MakeGenerator.GOAL_FOOTER_TMPL.format(goal=name)
950
Anas Nashif3ba1d432017-12-05 15:28:44 -0500951 def add_build_goal(self, name, directory, outdir,
952 args, buildlog, make_args=""):
Anas Nashif13773752018-07-06 18:20:23 -0500953 """Add a goal to invoke a build session
Andrew Boie6acbe632015-07-17 12:03:52 -0700954
955 @param name A unique string name for this build goal. The results
956 dictionary returned by execute() will be keyed by this name.
957 @param directory Absolute path to working directory, will be passed
958 to make -C
959 @param outdir Absolute path to output directory, will be passed to
Sebastian Bøe71d7de02017-11-09 12:06:04 +0100960 cmake via -B=<path>
961 @param args Extra command line arguments to pass to 'cmake', typically
Andrew Boie6acbe632015-07-17 12:03:52 -0700962 environment variables or specific Make goals
963 """
Andrew Boie6acbe632015-07-17 12:03:52 -0700964
Anas Nashif13773752018-07-06 18:20:23 -0500965 if not os.path.exists(outdir):
966 os.makedirs(outdir)
967
968 build_logfile = os.path.join(outdir, buildlog)
969 text = self._get_rule_header(name)
970 text += self._get_sub_make(name, "building", directory, outdir, build_logfile,
971 args, make_args=make_args)
972 text += self._get_rule_footer(name)
973
974 self.goals[name] = MakeGoal( name, text, None, self.logfile, build_logfile, None, None)
975
976 def add_goal(self, instance, type, args, make_args=""):
Anas Nashif4a9f3e62018-07-06 18:58:18 -0500977
Anas Nashif13773752018-07-06 18:20:23 -0500978 """Add a goal to build a Zephyr project and then run it using a handler
Andrew Boie6acbe632015-07-17 12:03:52 -0700979
980 The generated make goal invokes Make twice, the first time it will
981 build the default goal, and the second will invoke the 'qemu' goal.
Anas Nashif13773752018-07-06 18:20:23 -0500982 The output of the handler session will be monitored, and terminated
Andrew Boie6acbe632015-07-17 12:03:52 -0700983 either upon pass/fail result of the test program, or the timeout
984 is reached.
985
Sebastian Bøe71d7de02017-11-09 12:06:04 +0100986 @param args Extra cache entries to define in CMake.
Andrew Boie6acbe632015-07-17 12:03:52 -0700987 """
988
Anas Nashif576be982017-12-23 20:20:27 -0500989 name = instance.name
990 directory = instance.test.code_location
991 outdir = instance.outdir
992
Andrew Boie6acbe632015-07-17 12:03:52 -0700993 build_logfile = os.path.join(outdir, "build.log")
994 run_logfile = os.path.join(outdir, "run.log")
Andrew Boie6acbe632015-07-17 12:03:52 -0700995
Anas Nashif13773752018-07-06 18:20:23 -0500996 if not os.path.exists(outdir):
997 os.makedirs(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -0700998
Anas Nashif13773752018-07-06 18:20:23 -0500999 handler = None
1000 if type == "qemu":
1001 handler = QEMUHandler(instance)
1002 elif type == "native":
1003 handler = NativeHandler(instance)
1004 elif type == "unit":
1005 handler = UnitHandler(instance)
1006 elif type == "device":
1007 handler = DeviceHandler(instance)
Anas Nashif576be982017-12-23 20:20:27 -05001008
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001009 if options.enable_coverage:
1010 args += ["COVERAGE=1", "EXTRA_LDFLAGS=--coverage"]
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001011 args += ["CONFIG_COVERAGE=y"]
1012
Anas Nashif13773752018-07-06 18:20:23 -05001013 if type == 'qemu':
1014 args.append("QEMU_PIPE=%s" % handler.get_fifo())
1015
Anas Nashifcc164222017-12-26 11:02:46 -05001016 text = (self._get_rule_header(name) +
1017 self._get_sub_make(name, "building", directory,
Anas Nashif13773752018-07-06 18:20:23 -05001018 outdir, build_logfile, args, make_args=make_args))
1019 if handler and handler.run:
1020 text += self._get_sub_make(name, "running", directory,
1021 outdir, run_logfile,
1022 args, make_args="run")
Anas Nashif4d25b502017-11-25 17:37:17 -05001023
Anas Nashif13773752018-07-06 18:20:23 -05001024 text += self._get_rule_footer(name)
Anas Nashif73440ea2018-02-19 10:57:03 -06001025
Anas Nashif73440ea2018-02-19 10:57:03 -06001026 self.goals[name] = MakeGoal(name, text, handler, self.logfile, build_logfile,
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001027 run_logfile, handler.log if handler else None)
Anas Nashif73440ea2018-02-19 10:57:03 -06001028
Anas Nashif13773752018-07-06 18:20:23 -05001029
Anas Nashif37f9dc52018-02-23 08:53:46 -06001030 def add_test_instance(self, ti, extra_args=[]):
Andrew Boie6acbe632015-07-17 12:03:52 -07001031 """Add a goal to build/test a TestInstance object
1032
1033 @param ti TestInstance object to build. The status dictionary returned
1034 by execute() will be keyed by its .name field.
1035 """
1036 args = ti.test.extra_args[:]
Anas Nashiffa695d22017-10-04 16:14:27 -04001037 if len(ti.test.extra_configs) > 0:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001038 args.append("OVERLAY_CONFIG=%s" %
1039 os.path.join(ti.outdir, "overlay.conf"))
Anas Nashiffa695d22017-10-04 16:14:27 -04001040
Anas Nashiffb91ad62017-10-31 08:33:17 -04001041 args.append("BOARD={}".format(ti.platform.name))
Andrew Boieba612002016-09-01 10:41:03 -07001042 args.extend(extra_args)
Anas Nashif5df8cff2018-02-23 08:37:14 -06001043
Anas Nashif37f9dc52018-02-23 08:53:46 -06001044 do_run_slow = options.enable_slow or not ti.test.slow
1045 do_build_only = ti.build_only or options.build_only
Anas Nashif5df8cff2018-02-23 08:37:14 -06001046 do_run = (not do_build_only) and do_run_slow
1047
Anas Nashif13773752018-07-06 18:20:23 -05001048 # FIXME: Need refactoring and cleanup
1049 type = None
Anas Nashif5df8cff2018-02-23 08:37:14 -06001050 if ti.platform.qemu_support and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001051 type = "qemu"
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001052 elif ti.test.type == "unit":
Anas Nashif13773752018-07-06 18:20:23 -05001053 type = "unit"
Anas Nashif5df8cff2018-02-23 08:37:14 -06001054 elif ti.platform.type == "native" and do_run:
Anas Nashif13773752018-07-06 18:20:23 -05001055 type = "native"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001056 elif options.device_testing and (not ti.build_only) and (not options.build_only):
Anas Nashif13773752018-07-06 18:20:23 -05001057 type = "device"
Anas Nashif37f9dc52018-02-23 08:53:46 -06001058
Anas Nashif13773752018-07-06 18:20:23 -05001059 self.add_goal(ti, type, args)
Andrew Boie6acbe632015-07-17 12:03:52 -07001060
1061 def execute(self, callback_fn=None, context=None):
1062 """Execute all the registered build goals
1063
1064 @param callback_fn If not None, a callback function will be called
1065 as individual goals transition between states. This function
1066 should accept two parameters: a string state and an arbitrary
1067 context object, supplied here
1068 @param context Context object to pass to the callback function.
1069 Type and semantics are specific to that callback function.
1070 @return A dictionary mapping goal names to final status.
1071 """
1072
Andrew Boie08ce5a52016-02-22 13:28:10 -08001073 with open(self.makefile, "wt") as tf, \
Andrew Boie6acbe632015-07-17 12:03:52 -07001074 open(os.devnull, "wb") as devnull, \
Andrew Boie08ce5a52016-02-22 13:28:10 -08001075 open(self.logfile, "wt") as make_log:
Andrew Boie6acbe632015-07-17 12:03:52 -07001076 # Create our dynamic Makefile and execute it.
1077 # Watch stderr output which is where we will keep
1078 # track of build state
Andrew Boie08ce5a52016-02-22 13:28:10 -08001079 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001080 tf.write(goal.text)
1081 tf.write("all: %s\n" % (" ".join(self.goals.keys())))
1082 tf.flush()
1083
Andy Rossdec163f2018-05-21 10:12:59 -07001084 # Normally spawn 2x as many processes as CPUs. Ninja,
1085 # though, seems to have significant parallelism internally
1086 # and results in rather high loads, so use 1.5x there to
1087 # match what we see with GNU make.
1088 loadmul = 2
1089 if options.ninja:
1090 loadmul = 1.5
1091
Anas Nashif3ba1d432017-12-05 15:28:44 -05001092 cmd = ["make", "-k", "-j",
Andy Rossdec163f2018-05-21 10:12:59 -07001093 str(int(CPU_COUNTS * loadmul)), "-f", tf.name, "all"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001094 p = subprocess.Popen(cmd, stderr=subprocess.PIPE,
1095 stdout=devnull)
1096
1097 for line in iter(p.stderr.readline, b''):
Andrew Boie08ce5a52016-02-22 13:28:10 -08001098 line = line.decode("utf-8")
Andrew Boie6acbe632015-07-17 12:03:52 -07001099 make_log.write(line)
1100 verbose("MAKE: " + repr(line.strip()))
1101 m = MakeGenerator.re_make.match(line)
1102 if not m:
1103 continue
1104
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001105 state, name, _, error = m.groups()
Andrew Boie6acbe632015-07-17 12:03:52 -07001106 if error:
1107 goal = self.goals[error]
Andrew Boie822b0872017-01-10 13:32:40 -08001108 # Sometimes QEMU will run an image and then crash out, which
Anas Nashif3ac7b3a2017-08-03 10:03:02 -04001109 # will cause the 'make run' invocation to exit with
Andrew Boie822b0872017-01-10 13:32:40 -08001110 # nonzero status.
1111 # Need to distinguish this case from a compilation failure.
Anas Nashif4d25b502017-11-25 17:37:17 -05001112 if goal.handler:
Anas Nashif9a839df2018-01-29 08:42:38 -05001113 goal.fail("handler_crash")
Andrew Boie822b0872017-01-10 13:32:40 -08001114 else:
1115 goal.fail("build_error")
Andrew Boie6acbe632015-07-17 12:03:52 -07001116 else:
Anas Nashifcc164222017-12-26 11:02:46 -05001117 goal = self.goals[name]
1118 goal.make_state = state
1119
Andrew Boie6acbe632015-07-17 12:03:52 -07001120 if state == "finished":
Anas Nashif4d25b502017-11-25 17:37:17 -05001121 if goal.handler:
Anas Nashif576be982017-12-23 20:20:27 -05001122 if hasattr(goal.handler, "handle"):
Anas Nashif4d25b502017-11-25 17:37:17 -05001123 goal.handler.handle()
Anas Nashif4a9f3e62018-07-06 18:58:18 -05001124 goal.handler_log = goal.handler.log
Anas Nashifcc164222017-12-26 11:02:46 -05001125
Anas Nashif4d25b502017-11-25 17:37:17 -05001126 thread_status, metrics = goal.handler.get_state()
Andrew Boie6acbe632015-07-17 12:03:52 -07001127 goal.metrics.update(metrics)
1128 if thread_status == "passed":
1129 goal.success()
1130 else:
1131 goal.fail(thread_status)
1132 else:
1133 goal.success()
1134
1135 if callback_fn:
1136 callback_fn(context, self.goals, goal)
1137
1138 p.wait()
1139 return self.goals
1140
1141
1142# "list" - List of strings
1143# "list:<type>" - List of <type>
1144# "set" - Set of unordered, unique strings
1145# "set:<type>" - Set of <type>
1146# "float" - Floating point
1147# "int" - Integer
1148# "bool" - Boolean
1149# "str" - String
1150
1151# XXX Be sure to update __doc__ if you change any of this!!
1152
Anas Nashif3ba1d432017-12-05 15:28:44 -05001153platform_valid_keys = {"qemu_support": {"type": "bool", "default": False},
1154 "supported_toolchains": {"type": "list", "default": []}}
Andrew Boie6acbe632015-07-17 12:03:52 -07001155
Anas Nashif3ba1d432017-12-05 15:28:44 -05001156testcase_valid_keys = {"tags": {"type": "set", "required": False},
1157 "type": {"type": "str", "default": "integration"},
1158 "extra_args": {"type": "list"},
1159 "extra_configs": {"type": "list"},
1160 "build_only": {"type": "bool", "default": False},
1161 "build_on_all": {"type": "bool", "default": False},
1162 "skip": {"type": "bool", "default": False},
1163 "slow": {"type": "bool", "default": False},
1164 "timeout": {"type": "int", "default": 60},
1165 "min_ram": {"type": "int", "default": 8},
1166 "depends_on": {"type": "set"},
1167 "min_flash": {"type": "int", "default": 32},
1168 "arch_whitelist": {"type": "set"},
1169 "arch_exclude": {"type": "set"},
1170 "extra_sections": {"type": "list", "default": []},
1171 "platform_exclude": {"type": "set"},
1172 "platform_whitelist": {"type": "set"},
1173 "toolchain_exclude": {"type": "set"},
1174 "toolchain_whitelist": {"type": "set"},
Anas Nashifab940162017-12-08 10:17:57 -05001175 "filter": {"type": "str"},
Anas Nashif576be982017-12-23 20:20:27 -05001176 "harness": {"type": "str"},
1177 "harness_config": {"type": "map"}
Anas Nashifab940162017-12-08 10:17:57 -05001178 }
Andrew Boie6acbe632015-07-17 12:03:52 -07001179
1180
1181class SanityConfigParser:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001182 """Class to read test case files with semantic checking
Andrew Boie6acbe632015-07-17 12:03:52 -07001183 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001184
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001185 def __init__(self, filename, schema):
Andrew Boie6acbe632015-07-17 12:03:52 -07001186 """Instantiate a new SanityConfigParser object
1187
Anas Nashifa792a3d2017-04-04 18:47:49 -04001188 @param filename Source .yaml file to read
Andrew Boie6acbe632015-07-17 12:03:52 -07001189 """
Anas Nashif255625b2017-12-05 15:08:26 -05001190 self.data = scl.yaml_load_verify(filename, schema)
Andrew Boie6acbe632015-07-17 12:03:52 -07001191 self.filename = filename
Anas Nashif255625b2017-12-05 15:08:26 -05001192 self.tests = {}
1193 self.common = {}
1194 if 'tests' in self.data:
1195 self.tests = self.data['tests']
1196 if 'common' in self.data:
1197 self.common = self.data['common']
Andrew Boie6acbe632015-07-17 12:03:52 -07001198
1199 def _cast_value(self, value, typestr):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001200 if isinstance(value, str):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001201 v = value.strip()
Andrew Boie6acbe632015-07-17 12:03:52 -07001202 if typestr == "str":
1203 return v
1204
1205 elif typestr == "float":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001206 return float(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001207
1208 elif typestr == "int":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001209 return int(value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001210
1211 elif typestr == "bool":
Anas Nashifa792a3d2017-04-04 18:47:49 -04001212 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001213
Anas Nashif3ba1d432017-12-05 15:28:44 -05001214 elif typestr.startswith("list") and isinstance(value, list):
Anas Nashiffa695d22017-10-04 16:14:27 -04001215 return value
Anas Nashif3ba1d432017-12-05 15:28:44 -05001216 elif typestr.startswith("list") and isinstance(value, str):
Andrew Boie6acbe632015-07-17 12:03:52 -07001217 vs = v.split()
1218 if len(typestr) > 4 and typestr[4] == ":":
1219 return [self._cast_value(vsi, typestr[5:]) for vsi in vs]
1220 else:
1221 return vs
1222
1223 elif typestr.startswith("set"):
1224 vs = v.split()
1225 if len(typestr) > 3 and typestr[3] == ":":
1226 return set([self._cast_value(vsi, typestr[4:]) for vsi in vs])
1227 else:
1228 return set(vs)
1229
Anas Nashif576be982017-12-23 20:20:27 -05001230 elif typestr.startswith("map"):
1231 return value
Andrew Boie6acbe632015-07-17 12:03:52 -07001232 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001233 raise ConfigurationError(
1234 self.filename, "unknown type '%s'" % value)
Andrew Boie6acbe632015-07-17 12:03:52 -07001235
Anas Nashifb4754ed2017-12-05 17:27:58 -05001236 def get_test(self, name, valid_keys):
1237 """Get a dictionary representing the keys/values within a test
Andrew Boie6acbe632015-07-17 12:03:52 -07001238
Anas Nashifb4754ed2017-12-05 17:27:58 -05001239 @param name The test in the .yaml file to retrieve data from
Andrew Boie6acbe632015-07-17 12:03:52 -07001240 @param valid_keys A dictionary representing the intended semantics
Anas Nashifb4754ed2017-12-05 17:27:58 -05001241 for this test. Each key in this dictionary is a key that could
Anas Nashifa792a3d2017-04-04 18:47:49 -04001242 be specified, if a key is given in the .yaml file which isn't in
Andrew Boie6acbe632015-07-17 12:03:52 -07001243 here, it will generate an error. Each value in this dictionary
1244 is another dictionary containing metadata:
1245
1246 "default" - Default value if not given
1247 "type" - Data type to convert the text value to. Simple types
1248 supported are "str", "float", "int", "bool" which will get
1249 converted to respective Python data types. "set" and "list"
1250 may also be specified which will split the value by
1251 whitespace (but keep the elements as strings). finally,
1252 "list:<type>" and "set:<type>" may be given which will
1253 perform a type conversion after splitting the value up.
1254 "required" - If true, raise an error if not defined. If false
1255 and "default" isn't specified, a type conversion will be
1256 done on an empty string
Anas Nashifb4754ed2017-12-05 17:27:58 -05001257 @return A dictionary containing the test key-value pairs with
Andrew Boie6acbe632015-07-17 12:03:52 -07001258 type conversion and default values filled in per valid_keys
1259 """
1260
1261 d = {}
Anas Nashif255625b2017-12-05 15:08:26 -05001262 for k, v in self.common.items():
Anas Nashiffa695d22017-10-04 16:14:27 -04001263 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001264
Anas Nashifb4754ed2017-12-05 17:27:58 -05001265 for k, v in self.tests[name].items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001266 if k not in valid_keys:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001267 raise ConfigurationError(
1268 self.filename,
1269 "Unknown config key '%s' in definition for '%s'" %
Anas Nashifb4754ed2017-12-05 17:27:58 -05001270 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001271
Anas Nashiffa695d22017-10-04 16:14:27 -04001272 if k in d:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001273 if isinstance(d[k], str):
Anas Nashiffa695d22017-10-04 16:14:27 -04001274 d[k] += " " + v
1275 else:
1276 d[k] = v
Anas Nashif75547e22018-02-24 08:32:14 -06001277
Andrew Boie08ce5a52016-02-22 13:28:10 -08001278 for k, kinfo in valid_keys.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001279 if k not in d:
1280 if "required" in kinfo:
1281 required = kinfo["required"]
1282 else:
1283 required = False
1284
1285 if required:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001286 raise ConfigurationError(
1287 self.filename,
Anas Nashifb4754ed2017-12-05 17:27:58 -05001288 "missing required value for '%s' in test '%s'" %
1289 (k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001290 else:
1291 if "default" in kinfo:
1292 default = kinfo["default"]
1293 else:
1294 default = self._cast_value("", kinfo["type"])
1295 d[k] = default
1296 else:
1297 try:
1298 d[k] = self._cast_value(d[k], kinfo["type"])
Andrew Boie08ce5a52016-02-22 13:28:10 -08001299 except ValueError as ve:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001300 raise ConfigurationError(
Anas Nashifb4754ed2017-12-05 17:27:58 -05001301 self.filename, "bad %s value '%s' for key '%s' in name '%s'" %
1302 (kinfo["type"], d[k], k, name))
Andrew Boie6acbe632015-07-17 12:03:52 -07001303
1304 return d
1305
1306
1307class Platform:
1308 """Class representing metadata for a particular platform
1309
Anas Nashifc7406082015-12-13 15:00:31 -05001310 Maps directly to BOARD when building"""
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001311
1312 yaml_platform_schema = scl.yaml_load(
Anas Nashif3ba1d432017-12-05 15:28:44 -05001313 os.path.join(
1314 os.environ['ZEPHYR_BASE'],
1315 "scripts",
1316 "sanity_chk",
1317 "sanitycheck-platform-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001318
Anas Nashifa792a3d2017-04-04 18:47:49 -04001319 def __init__(self, cfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001320 """Constructor.
1321
Anas Nashif877d3ca2017-12-05 17:39:29 -05001322 @param cfile Path to platform configuration file, which gives
1323 info about the platform to be added.
Andrew Boie6acbe632015-07-17 12:03:52 -07001324 """
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001325 scp = SanityConfigParser(cfile, self.yaml_platform_schema)
Anas Nashif255625b2017-12-05 15:08:26 -05001326 data = scp.data
Anas Nashifa792a3d2017-04-04 18:47:49 -04001327
Anas Nashif255625b2017-12-05 15:08:26 -05001328 self.name = data['identifier']
Anas Nashifa792a3d2017-04-04 18:47:49 -04001329 # if no RAM size is specified by the board, take a default of 128K
Anas Nashif255625b2017-12-05 15:08:26 -05001330 self.ram = data.get("ram", 128)
1331 testing = data.get("testing", {})
Anas Nashifa792a3d2017-04-04 18:47:49 -04001332 self.ignore_tags = testing.get("ignore_tags", [])
1333 self.default = testing.get("default", False)
1334 # if no flash size is specified by the board, take a default of 512K
Anas Nashif255625b2017-12-05 15:08:26 -05001335 self.flash = data.get("flash", 512)
Anas Nashif95276932017-07-31 08:47:41 -04001336 self.supported = set()
Anas Nashif255625b2017-12-05 15:08:26 -05001337 for supp_feature in data.get("supported", []):
Anas Nashif95276932017-07-31 08:47:41 -04001338 for item in supp_feature.split(":"):
1339 self.supported.add(item)
1340
Anas Nashif8acdbd72018-01-04 14:15:22 -05001341 self.qemu_support = True if data.get('simulation', "na") == 'qemu' else False
Anas Nashif255625b2017-12-05 15:08:26 -05001342 self.arch = data['arch']
Anas Nashifcc164222017-12-26 11:02:46 -05001343 self.type = data.get('type', "na")
Anas Nashif8acdbd72018-01-04 14:15:22 -05001344 self.simulation = data.get('simulation', "na")
Anas Nashif255625b2017-12-05 15:08:26 -05001345 self.supported_toolchains = data.get("toolchain", [])
Andrew Boie41878222016-11-03 11:58:53 -07001346 self.defconfig = None
Andrew Boie6acbe632015-07-17 12:03:52 -07001347 pass
1348
Andrew Boie6acbe632015-07-17 12:03:52 -07001349 def __repr__(self):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001350 return "<%s on %s>" % (self.name, self.arch)
Andrew Boie6acbe632015-07-17 12:03:52 -07001351
1352
1353class Architecture:
1354 """Class representing metadata for a particular architecture
1355 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001356
Anas Nashifa792a3d2017-04-04 18:47:49 -04001357 def __init__(self, name, platforms):
Andrew Boie6acbe632015-07-17 12:03:52 -07001358 """Architecture constructor
1359
Anas Nashif877d3ca2017-12-05 17:39:29 -05001360 @param name String name for this architecture
1361 @param platforms list of platforms belonging to this architecture
Andrew Boie6acbe632015-07-17 12:03:52 -07001362 """
Anas Nashifa792a3d2017-04-04 18:47:49 -04001363 self.platforms = platforms
Andrew Boie6acbe632015-07-17 12:03:52 -07001364
Anas Nashifa792a3d2017-04-04 18:47:49 -04001365 self.name = name
Andrew Boie6acbe632015-07-17 12:03:52 -07001366
1367 def __repr__(self):
1368 return "<arch %s>" % self.name
1369
1370
1371class TestCase:
1372 """Class representing a test application
1373 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001374
Anas Nashif7fae29c2017-10-09 13:19:12 -04001375 def __init__(self, testcase_root, workdir, name, tc_dict, yamlfile):
Andrew Boie6acbe632015-07-17 12:03:52 -07001376 """TestCase constructor.
1377
Anas Nashif877d3ca2017-12-05 17:39:29 -05001378 This gets called by TestSuite as it finds and reads test yaml files.
Anas Nashifa792a3d2017-04-04 18:47:49 -04001379 Multiple TestCase instances may be generated from a single testcase.yaml,
Anas Nashif877d3ca2017-12-05 17:39:29 -05001380 each one corresponds to an entry within that file.
Andrew Boie6acbe632015-07-17 12:03:52 -07001381
Andrew Boie6acbe632015-07-17 12:03:52 -07001382 We need to have a unique name for every single test case. Since
Anas Nashifa792a3d2017-04-04 18:47:49 -04001383 a testcase.yaml can define multiple tests, the canonical name for
Andrew Boie6acbe632015-07-17 12:03:52 -07001384 the test case is <workdir>/<name>.
1385
1386 @param testcase_root Absolute path to the root directory where
1387 all the test cases live
1388 @param workdir Relative path to the project directory for this
1389 test application from the test_case root.
Anas Nashif877d3ca2017-12-05 17:39:29 -05001390 @param name Name of this test case, corresponding to the entry name
Andrew Boie6acbe632015-07-17 12:03:52 -07001391 in the test case configuration file. For many test cases that just
1392 define one test, can be anything and is usually "test". This is
1393 really only used to distinguish between different cases when
Anas Nashifa792a3d2017-04-04 18:47:49 -04001394 the testcase.yaml defines multiple tests
Anas Nashif877d3ca2017-12-05 17:39:29 -05001395 @param tc_dict Dictionary with test values for this test case
Anas Nashifa792a3d2017-04-04 18:47:49 -04001396 from the testcase.yaml file
Andrew Boie6acbe632015-07-17 12:03:52 -07001397 """
1398 self.code_location = os.path.join(testcase_root, workdir)
Anas Nashifaae71d72018-04-21 22:26:48 -05001399 self.id = name
1400 self.cases = []
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001401 self.type = tc_dict["type"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001402 self.tags = tc_dict["tags"]
1403 self.extra_args = tc_dict["extra_args"]
Anas Nashiffa695d22017-10-04 16:14:27 -04001404 self.extra_configs = tc_dict["extra_configs"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001405 self.arch_whitelist = tc_dict["arch_whitelist"]
Anas Nashif30d13872015-10-05 10:02:45 -04001406 self.arch_exclude = tc_dict["arch_exclude"]
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001407 self.skip = tc_dict["skip"]
Anas Nashif30d13872015-10-05 10:02:45 -04001408 self.platform_exclude = tc_dict["platform_exclude"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001409 self.platform_whitelist = tc_dict["platform_whitelist"]
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001410 self.toolchain_exclude = tc_dict["toolchain_exclude"]
1411 self.toolchain_whitelist = tc_dict["toolchain_whitelist"]
Andrew Boie3ea78922016-03-24 14:46:00 -07001412 self.tc_filter = tc_dict["filter"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001413 self.timeout = tc_dict["timeout"]
Anas Nashifb0f3ae02017-12-08 12:48:39 -05001414 self.harness = tc_dict["harness"]
Anas Nashif576be982017-12-23 20:20:27 -05001415 self.harness_config = tc_dict["harness_config"]
Andrew Boie6acbe632015-07-17 12:03:52 -07001416 self.build_only = tc_dict["build_only"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001417 self.build_on_all = tc_dict["build_on_all"]
Andrew Boie6bb087c2016-02-10 13:39:00 -08001418 self.slow = tc_dict["slow"]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001419 self.min_ram = tc_dict["min_ram"]
1420 self.depends_on = tc_dict["depends_on"]
1421 self.min_flash = tc_dict["min_flash"]
Andrew Boie52fef672016-11-29 12:21:59 -08001422 self.extra_sections = tc_dict["extra_sections"]
Anas Nashifbd166f42017-09-02 12:32:08 -04001423
Alberto Escolar Piedras2151b862018-01-29 15:09:21 +01001424 self.path = os.path.normpath(os.path.join(os.path.realpath(
1425 testcase_root).replace(os.path.realpath(ZEPHYR_BASE) + "/", ''),
1426 workdir, name))
1427
Anas Nashifaae71d72018-04-21 22:26:48 -05001428
Anas Nashifbd166f42017-09-02 12:32:08 -04001429 self.name = os.path.join(self.path)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001430 self.defconfig = {}
Anas Nashif7fae29c2017-10-09 13:19:12 -04001431 self.yamlfile = yamlfile
Andrew Boie6acbe632015-07-17 12:03:52 -07001432
Anas Nashifaae71d72018-04-21 22:26:48 -05001433 def scan_file(self, inf_name):
Anas Nashifaae71d72018-04-21 22:26:48 -05001434 suite_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001435 # do not match until end-of-line, otherwise we won't allow
1436 # stc_regex below to catch the ones that are declared in the same
1437 # line--as we only search starting the end of this match
1438 br"^\s*ztest_test_suite\(\s*(?P<suite_name>[a-zA-Z0-9_]+)\s*,",
Anas Nashifaae71d72018-04-21 22:26:48 -05001439 re.MULTILINE)
1440 stc_regex = re.compile(
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001441 br"^\s*" # empy space at the beginning is ok
1442 # catch the case where it is declared in the same sentence, e.g:
1443 #
1444 # ztest_test_suite(mutex_complex, ztest_user_unit_test(TESTNAME));
1445 br"(?:ztest_test_suite\([a-zA-Z0-9_]+,\s*)?"
1446 # Catch ztest[_user]_unit_test-[_setup_teardown](TESTNAME)
1447 br"ztest_(?:user_)?unit_test(?:_setup_teardown)?"
1448 # Consume the argument that becomes the extra testcse
1449 br"\(\s*"
1450 br"(?P<stc_name>[a-zA-Z0-9_]+)"
1451 # _setup_teardown() variant has two extra arguments that we ignore
1452 br"(?:\s*,\s*[a-zA-Z0-9_]+\s*,\s*[a-zA-Z0-9_]+)?"
1453 br"\s*\)",
1454 # We don't check how it finishes; we don't care
Anas Nashifaae71d72018-04-21 22:26:48 -05001455 re.MULTILINE)
1456 suite_run_regex = re.compile(
1457 br"^\s*ztest_run_test_suite\((?P<suite_name>[a-zA-Z0-9_]+)\)",
1458 re.MULTILINE)
1459 achtung_regex = re.compile(
1460 br"(#ifdef|#endif)",
1461 re.MULTILINE)
1462 warnings = None
1463
1464 with open(inf_name) as inf:
1465 with contextlib.closing(mmap.mmap(inf.fileno(), 0, mmap.MAP_PRIVATE,
1466 mmap.PROT_READ, 0)) as main_c:
Anas Nashifaae71d72018-04-21 22:26:48 -05001467 suite_regex_match = suite_regex.search(main_c)
1468 if not suite_regex_match:
1469 # can't find ztest_test_suite, maybe a client, because
1470 # it includes ztest.h
1471 return None, None
1472
1473 suite_run_match = suite_run_regex.search(main_c)
1474 if not suite_run_match:
1475 raise ValueError("can't find ztest_run_test_suite")
1476
1477 achtung_matches = re.findall(
1478 achtung_regex,
1479 main_c[suite_regex_match.end():suite_run_match.start()])
1480 if achtung_matches:
1481 warnings = "found invalid %s in ztest_test_suite()" \
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001482 % ", ".join(set([
1483 match.decode() for match in achtung_matches
1484 ]))
1485 _matches = re.findall(
Anas Nashifaae71d72018-04-21 22:26:48 -05001486 stc_regex,
1487 main_c[suite_regex_match.end():suite_run_match.start()])
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001488 matches = [ match.decode().replace("test_", "") for match in _matches ]
Anas Nashifaae71d72018-04-21 22:26:48 -05001489 return matches, warnings
1490
1491 def scan_path(self, path):
1492 subcases = []
1493 for filename in glob.glob(os.path.join(path, "src", "*.c")):
1494 try:
1495 _subcases, warnings = self.scan_file(filename)
1496 if warnings:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001497 error("%s: %s" % (filename, warnings))
Anas Nashifaae71d72018-04-21 22:26:48 -05001498 if _subcases:
1499 subcases += _subcases
1500 except ValueError as e:
1501 error("%s: can't find: %s", filename, e)
1502 return subcases
1503
1504
1505 def parse_subcases(self):
1506 results = self.scan_path(self.code_location)
1507 for sub in results:
Inaky Perez-Gonzalez75e2d902018-04-26 00:17:01 -07001508 name = "{}.{}".format(self.id, sub)
Anas Nashifaae71d72018-04-21 22:26:48 -05001509 self.cases.append(name)
1510
1511
Anas Nashif75547e22018-02-24 08:32:14 -06001512 def __str__(self):
Andrew Boie6acbe632015-07-17 12:03:52 -07001513 return self.name
1514
1515
Andrew Boie6acbe632015-07-17 12:03:52 -07001516class TestInstance:
1517 """Class representing the execution of a particular TestCase on a platform
1518
1519 @param test The TestCase object we want to build/execute
1520 @param platform Platform object that we want to build and run against
1521 @param base_outdir Base directory for all test results. The actual
1522 out directory used is <outdir>/<platform>/<test case name>
1523 """
Anas Nashif3ba1d432017-12-05 15:28:44 -05001524
Anas Nashif37f9dc52018-02-23 08:53:46 -06001525 def __init__(self, test, platform, base_outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001526 self.test = test
1527 self.platform = platform
Anas Nashifbd166f42017-09-02 12:32:08 -04001528 self.name = os.path.join(platform.name, test.name)
Andrew Boie6acbe632015-07-17 12:03:52 -07001529 self.outdir = os.path.join(base_outdir, platform.name, test.path)
Anas Nashif37f9dc52018-02-23 08:53:46 -06001530 self.build_only = options.build_only or test.build_only or (test.harness and test.harness != 'console')
Anas Nashife0a6a0b2018-02-15 20:07:24 -06001531 self.results = {}
Andrew Boie6acbe632015-07-17 12:03:52 -07001532
Anas Nashiffa695d22017-10-04 16:14:27 -04001533 def create_overlay(self):
1534 if len(self.test.extra_configs) > 0:
1535 file = os.path.join(self.outdir, "overlay.conf")
1536 os.makedirs(self.outdir, exist_ok=True)
1537 f = open(file, "w")
1538 content = ""
Anas Nashif981f77f2017-10-18 07:53:58 -04001539 content = "\n".join(self.test.extra_configs)
Anas Nashiffa695d22017-10-04 16:14:27 -04001540 f.write(content)
1541 f.close()
1542
Andrew Boie6acbe632015-07-17 12:03:52 -07001543 def calculate_sizes(self):
1544 """Get the RAM/ROM sizes of a test case.
1545
1546 This can only be run after the instance has been executed by
1547 MakeGenerator, otherwise there won't be any binaries to measure.
1548
1549 @return A SizeCalculator object
1550 """
Anas Nashiffb91ad62017-10-31 08:33:17 -04001551 fns = glob.glob(os.path.join(self.outdir, "zephyr", "*.elf"))
Anas Nashif2f4e1702017-11-24 08:11:25 -05001552 fns.extend(glob.glob(os.path.join(self.outdir, "zephyr", "*.exe")))
Anas Nashiff8dcad42016-10-27 18:10:08 -04001553 fns = [x for x in fns if not x.endswith('_prebuilt.elf')]
Andrew Boie5d4eb782015-10-02 10:04:56 -07001554 if (len(fns) != 1):
1555 raise BuildError("Missing/multiple output ELF binary")
Andrew Boie52fef672016-11-29 12:21:59 -08001556 return SizeCalculator(fns[0], self.test.extra_sections)
Andrew Boie6acbe632015-07-17 12:03:52 -07001557
1558 def __repr__(self):
1559 return "<TestCase %s on %s>" % (self.test.name, self.platform.name)
1560
1561
Andrew Boie4ef16c52015-08-28 12:36:03 -07001562def defconfig_cb(context, goals, goal):
1563 if not goal.failed:
1564 return
1565
1566 info("%sCould not build defconfig for %s%s" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05001567 (COLOR_RED, goal.name, COLOR_NORMAL))
Andrew Boie4ef16c52015-08-28 12:36:03 -07001568 if INLINE_LOGS:
1569 with open(goal.get_error_log()) as fp:
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08001570 data = fp.read()
1571 sys.stdout.write(data)
1572 if log_file:
1573 log_file.write(data)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001574 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08001575 info("\tsee: " + COLOR_YELLOW + goal.get_error_log() + COLOR_NORMAL)
Andrew Boie4ef16c52015-08-28 12:36:03 -07001576
Andrew Boie6acbe632015-07-17 12:03:52 -07001577
1578class TestSuite:
Andrew Boie60823c22017-02-10 09:43:07 -08001579 config_re = re.compile('(CONFIG_[A-Za-z0-9_]+)[=]\"?([^\"]*)\"?$')
Andrew Boie6acbe632015-07-17 12:03:52 -07001580
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001581 yaml_tc_schema = scl.yaml_load(
1582 os.path.join(os.environ['ZEPHYR_BASE'],
Anas Nashifdb3d55f2017-09-02 06:31:25 -04001583 "scripts", "sanity_chk", "sanitycheck-tc-schema.yaml"))
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001584
Anas Nashif37f9dc52018-02-23 08:53:46 -06001585 def __init__(self, board_root_list, testcase_roots, outdir):
Andrew Boie6acbe632015-07-17 12:03:52 -07001586 # Keep track of which test cases we've filtered out and why
Andrew Boie6acbe632015-07-17 12:03:52 -07001587 self.arches = {}
1588 self.testcases = {}
1589 self.platforms = []
Andrew Boieb391e662015-08-31 15:25:45 -07001590 self.outdir = os.path.abspath(outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001591 self.instances = {}
1592 self.goals = None
1593 self.discards = None
Kumar Galac84235e2018-04-10 13:32:51 -05001594 self.load_errors = 0
Andrew Boie6acbe632015-07-17 12:03:52 -07001595
Andrew Boie3d348712016-04-08 11:52:13 -07001596 for testcase_root in testcase_roots:
1597 testcase_root = os.path.abspath(testcase_root)
Andrew Boie6acbe632015-07-17 12:03:52 -07001598
Andrew Boie3d348712016-04-08 11:52:13 -07001599 debug("Reading test case configuration files under %s..." %
1600 testcase_root)
1601 for dirpath, dirnames, filenames in os.walk(testcase_root,
1602 topdown=True):
1603 verbose("scanning %s" % dirpath)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001604 if 'sample.yaml' in filenames:
1605 filename = 'sample.yaml'
1606 elif 'testcase.yaml' in filenames:
1607 filename = 'testcase.yaml'
1608 else:
1609 continue
Anas Nashif61e21632018-04-08 13:30:16 -05001610
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001611 verbose("Found possible test case in " + dirpath)
1612 dirnames[:] = []
1613 yaml_path = os.path.join(dirpath, filename)
1614 try:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001615 parsed_data = SanityConfigParser(
1616 yaml_path, self.yaml_tc_schema)
Andrew Boie3d348712016-04-08 11:52:13 -07001617
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001618 workdir = os.path.relpath(dirpath, testcase_root)
Inaky Perez-Gonzalez662dde62017-07-24 10:24:35 -07001619
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001620 for name in parsed_data.tests.keys():
1621 tc_dict = parsed_data.get_test(name, testcase_valid_keys)
1622 tc = TestCase(testcase_root, workdir, name, tc_dict,
1623 yaml_path)
Anas Nashifaae71d72018-04-21 22:26:48 -05001624 tc.parse_subcases()
Anas Nashif7fae29c2017-10-09 13:19:12 -04001625
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001626 self.testcases[tc.name] = tc
1627
1628 except Exception as e:
1629 error("E: %s: can't load (skipping): %s" % (yaml_path, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001630 self.load_errors += 1
Paul Sokolovsky100474d2018-01-03 17:19:43 +02001631
Andrew Boie6acbe632015-07-17 12:03:52 -07001632
Anas Nashif86c8e232017-10-09 13:42:28 -04001633 for board_root in board_root_list:
1634 board_root = os.path.abspath(board_root)
1635
Anas Nashif3ba1d432017-12-05 15:28:44 -05001636 debug(
1637 "Reading platform configuration files under %s..." %
1638 board_root)
Anas Nashif8b11a1f2017-11-26 17:08:47 -05001639 for fn in glob.glob(os.path.join(board_root, "*", "*", "*.yaml")):
1640 verbose("Found plaform configuration " + fn)
1641 try:
1642 platform = Platform(fn)
1643 self.platforms.append(platform)
1644 except RuntimeError as e:
1645 error("E: %s: can't load: %s" % (fn, e))
Kumar Galac84235e2018-04-10 13:32:51 -05001646 self.load_errors += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07001647
Anas Nashifa792a3d2017-04-04 18:47:49 -04001648 arches = []
1649 for p in self.platforms:
1650 arches.append(p.arch)
1651 for a in list(set(arches)):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001652 aplatforms = [p for p in self.platforms if p.arch == a]
Anas Nashifa792a3d2017-04-04 18:47:49 -04001653 arch = Architecture(a, aplatforms)
1654 self.arches[a] = arch
1655
Andrew Boie6acbe632015-07-17 12:03:52 -07001656 self.instances = {}
1657
1658 def get_last_failed(self):
1659 if not os.path.exists(LAST_SANITY):
Anas Nashifd9616b92016-11-29 13:34:53 -05001660 raise SanityRuntimeError("Couldn't find last sanity run.")
Andrew Boie6acbe632015-07-17 12:03:52 -07001661 result = []
1662 with open(LAST_SANITY, "r") as fp:
1663 cr = csv.DictReader(fp)
1664 for row in cr:
1665 if row["passed"] == "True":
1666 continue
1667 test = row["test"]
1668 platform = row["platform"]
1669 result.append((test, platform))
1670 return result
1671
Anas Nashifbd166f42017-09-02 12:32:08 -04001672 def load_from_file(self, file):
1673 if not os.path.exists(file):
Anas Nashif3ba1d432017-12-05 15:28:44 -05001674 raise SanityRuntimeError(
1675 "Couldn't find input file with list of tests.")
Anas Nashifbd166f42017-09-02 12:32:08 -04001676 with open(file, "r") as fp:
1677 cr = csv.reader(fp)
1678 instance_list = []
1679 for row in cr:
1680 name = os.path.join(row[0], row[1])
1681 platforms = self.arches[row[3]].platforms
1682 myp = None
1683 for p in platforms:
1684 if p.name == row[2]:
1685 myp = p
1686 break
1687 instance = TestInstance(self.testcases[name], myp, self.outdir)
Anas Nashiffa695d22017-10-04 16:14:27 -04001688 instance.create_overlay()
Anas Nashifbd166f42017-09-02 12:32:08 -04001689 instance_list.append(instance)
1690 self.add_instances(instance_list)
1691
Anas Nashif4f028882017-12-30 11:48:43 -05001692 def apply_filters(self):
Anas Nashifbd166f42017-09-02 12:32:08 -04001693
Anas Nashif7fe35cf2018-02-15 07:20:18 -06001694 toolchain = os.environ.get("ZEPHYR_TOOLCHAIN_VARIANT", None) or \
1695 os.environ.get("ZEPHYR_GCC_VARIANT", None)
1696 if not toolchain:
1697 raise SanityRuntimeError("E: Variable ZEPHYR_TOOLCHAIN_VARIANT is not defined")
1698
1699
Andrew Boie6acbe632015-07-17 12:03:52 -07001700 instances = []
1701 discards = {}
Anas Nashif4f028882017-12-30 11:48:43 -05001702 platform_filter = options.platform
1703 last_failed = options.only_failed
1704 testcase_filter = options.test
1705 arch_filter = options.arch
1706 tag_filter = options.tag
1707 exclude_tag = options.exclude_tag
1708 config_filter = options.config
1709 extra_args = options.extra_args
1710 all_plats = options.all
Anas Nashiffa695d22017-10-04 16:14:27 -04001711
Andrew Boie6acbe632015-07-17 12:03:52 -07001712 verbose("platform filter: " + str(platform_filter))
1713 verbose(" arch_filter: " + str(arch_filter))
1714 verbose(" tag_filter: " + str(tag_filter))
Anas Nashifdfa86e22016-10-24 17:08:56 -04001715 verbose(" exclude_tag: " + str(exclude_tag))
Andrew Boie6acbe632015-07-17 12:03:52 -07001716 verbose(" config_filter: " + str(config_filter))
1717
1718 if last_failed:
1719 failed_tests = self.get_last_failed()
1720
Andrew Boie821d8322016-03-22 10:08:35 -07001721 default_platforms = False
1722
1723 if all_plats:
1724 info("Selecting all possible platforms per test case")
1725 # When --all used, any --platform arguments ignored
1726 platform_filter = []
1727 elif not platform_filter:
Andrew Boie6acbe632015-07-17 12:03:52 -07001728 info("Selecting default platforms per test case")
1729 default_platforms = True
Andrew Boie6acbe632015-07-17 12:03:52 -07001730
Sebastian Bøe781e3982017-11-09 11:43:33 +01001731 mg = MakeGenerator(self.outdir)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001732 dlist = {}
Andrew Boie08ce5a52016-02-22 13:28:10 -08001733 for tc_name, tc in self.testcases.items():
1734 for arch_name, arch in self.arches.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001735 for plat in arch.platforms:
1736 instance = TestInstance(tc, plat, self.outdir)
1737
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001738 if (arch_name == "unit") != (tc.type == "unit"):
1739 continue
1740
Anas Nashifbfab06b2017-06-22 09:22:24 -04001741 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001742 platform_filter = []
1743
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001744 if tc.skip:
1745 continue
1746
Anas Nashif2cf0df02015-10-10 09:29:43 -04001747 if tag_filter and not tc.tags.intersection(tag_filter):
1748 continue
1749
Anas Nashifdfa86e22016-10-24 17:08:56 -04001750 if exclude_tag and tc.tags.intersection(exclude_tag):
1751 continue
1752
Anas Nashif2cf0df02015-10-10 09:29:43 -04001753 if testcase_filter and tc_name not in testcase_filter:
1754 continue
1755
Anas Nashif3ba1d432017-12-05 15:28:44 -05001756 if last_failed and (
1757 tc.name, plat.name) not in failed_tests:
Anas Nashif2cf0df02015-10-10 09:29:43 -04001758 continue
1759
1760 if arch_filter and arch_name not in arch_filter:
1761 continue
1762
1763 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1764 continue
1765
1766 if tc.arch_exclude and arch.name in tc.arch_exclude:
1767 continue
1768
1769 if tc.platform_exclude and plat.name in tc.platform_exclude:
1770 continue
1771
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001772 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1773 continue
1774
Anas Nashif2cf0df02015-10-10 09:29:43 -04001775 if platform_filter and plat.name not in platform_filter:
1776 continue
1777
Anas Nashif62224182017-08-09 23:55:53 -04001778 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001779 continue
1780
1781 if set(plat.ignore_tags) & tc.tags:
1782 continue
1783
Kumar Gala5141d522017-07-07 08:05:48 -05001784 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001785 dep_intersection = tc.depends_on.intersection(
1786 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001787 if dep_intersection != set(tc.depends_on):
1788 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001789
1790 if plat.flash < tc.min_flash:
1791 continue
1792
Anas Nashif2cf0df02015-10-10 09:29:43 -04001793 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1794 continue
1795
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001796 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1797 continue
1798
Anas Nashif3ba1d432017-12-05 15:28:44 -05001799 if (tc.tc_filter and (plat.default or all_plats or platform_filter)
1800 and toolchain in plat.supported_toolchains):
Anas Nashif8ea9d022015-11-10 12:24:20 -05001801 args = tc.extra_args[:]
Anas Nashiffb91ad62017-10-31 08:33:17 -04001802 args.append("BOARD={}".format(plat.name))
Andrew Boieba612002016-09-01 10:41:03 -07001803 args.extend(extra_args)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001804 # FIXME would be nice to use a common outdir for this so that
Andrew Boie41878222016-11-03 11:58:53 -07001805 # conf, gen_idt, etc aren't rebuilt for every combination,
David B. Kinder29963c32017-06-16 12:32:42 -07001806 # need a way to avoid different Make processes from clobbering
Anas Nashif3ba1d432017-12-05 15:28:44 -05001807 # each other since they all try to build them
1808 # simultaneously
Anas Nashif2cf0df02015-10-10 09:29:43 -04001809
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02001810 if plat.type == "native" and options.enable_coverage:
1811 args.append("CONFIG_COVERAGE=y")
1812
Anas Nashif2cf0df02015-10-10 09:29:43 -04001813 o = os.path.join(self.outdir, plat.name, tc.path)
Anas Nashif13773752018-07-06 18:20:23 -05001814 dlist[tc, plat, tc.name.split("/")[-1]] = os.path.join(o, "zephyr", ".config")
1815 goal = "_".join([plat.name, "_".join(tc.name.split("/")), "config-sanitycheck"])
1816 mg.add_build_goal(goal, os.path.join(ZEPHYR_BASE, tc.code_location),
1817 o, args, "config-sanitycheck.log", make_args="config-sanitycheck")
Anas Nashif2cf0df02015-10-10 09:29:43 -04001818
1819 info("Building testcase defconfigs...")
1820 results = mg.execute(defconfig_cb)
1821
Andrew Boie08ce5a52016-02-22 13:28:10 -08001822 for name, goal in results.items():
Anas Nashif2cf0df02015-10-10 09:29:43 -04001823 if goal.failed:
1824 raise SanityRuntimeError("Couldn't build some defconfigs")
1825
Andrew Boie08ce5a52016-02-22 13:28:10 -08001826 for k, out_config in dlist.items():
Andrew Boie41878222016-11-03 11:58:53 -07001827 test, plat, name = k
Anas Nashif2cf0df02015-10-10 09:29:43 -04001828 defconfig = {}
1829 with open(out_config, "r") as fp:
1830 for line in fp.readlines():
1831 m = TestSuite.config_re.match(line)
1832 if not m:
Andrew Boie3ea78922016-03-24 14:46:00 -07001833 if line.strip() and not line.startswith("#"):
1834 sys.stderr.write("Unrecognized line %s\n" % line)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001835 continue
1836 defconfig[m.group(1)] = m.group(2).strip()
Andrew Boie41878222016-11-03 11:58:53 -07001837 test.defconfig[plat] = defconfig
Anas Nashif2cf0df02015-10-10 09:29:43 -04001838
Andrew Boie08ce5a52016-02-22 13:28:10 -08001839 for tc_name, tc in self.testcases.items():
1840 for arch_name, arch in self.arches.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07001841 instance_list = []
1842 for plat in arch.platforms:
1843 instance = TestInstance(tc, plat, self.outdir)
1844
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03001845 if (arch_name == "unit") != (tc.type == "unit"):
1846 # Discard silently
1847 continue
1848
Anas Nashif2bd99bc2015-10-12 13:10:57 -04001849 if tc.skip:
1850 discards[instance] = "Skip filter"
1851 continue
1852
Anas Nashifbfab06b2017-06-22 09:22:24 -04001853 if tc.build_on_all and not platform_filter:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001854 platform_filter = []
1855
Andrew Boie6acbe632015-07-17 12:03:52 -07001856 if tag_filter and not tc.tags.intersection(tag_filter):
1857 discards[instance] = "Command line testcase tag filter"
1858 continue
1859
Anas Nashifdfa86e22016-10-24 17:08:56 -04001860 if exclude_tag and tc.tags.intersection(exclude_tag):
1861 discards[instance] = "Command line testcase exclude filter"
1862 continue
1863
Andrew Boie6acbe632015-07-17 12:03:52 -07001864 if testcase_filter and tc_name not in testcase_filter:
1865 discards[instance] = "Testcase name filter"
1866 continue
1867
Anas Nashif3ba1d432017-12-05 15:28:44 -05001868 if last_failed and (
1869 tc.name, plat.name) not in failed_tests:
Andrew Boie6acbe632015-07-17 12:03:52 -07001870 discards[instance] = "Passed or skipped during last run"
1871 continue
1872
1873 if arch_filter and arch_name not in arch_filter:
1874 discards[instance] = "Command line testcase arch filter"
1875 continue
1876
1877 if tc.arch_whitelist and arch.name not in tc.arch_whitelist:
1878 discards[instance] = "Not in test case arch whitelist"
1879 continue
1880
Anas Nashif30d13872015-10-05 10:02:45 -04001881 if tc.arch_exclude and arch.name in tc.arch_exclude:
1882 discards[instance] = "In test case arch exclude"
1883 continue
1884
1885 if tc.platform_exclude and plat.name in tc.platform_exclude:
1886 discards[instance] = "In test case platform exclude"
1887 continue
1888
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001889 if tc.toolchain_exclude and toolchain in tc.toolchain_exclude:
1890 discards[instance] = "In test case toolchain exclude"
1891 continue
1892
Andrew Boie6acbe632015-07-17 12:03:52 -07001893 if platform_filter and plat.name not in platform_filter:
1894 discards[instance] = "Command line platform filter"
1895 continue
1896
1897 if tc.platform_whitelist and plat.name not in tc.platform_whitelist:
1898 discards[instance] = "Not in testcase platform whitelist"
1899 continue
1900
Anas Nashifb17e1ca2017-06-27 18:05:30 -04001901 if tc.toolchain_whitelist and toolchain not in tc.toolchain_whitelist:
1902 discards[instance] = "Not in testcase toolchain whitelist"
1903 continue
1904
Anas Nashif86c8e232017-10-09 13:42:28 -04001905 if toolchain and toolchain not in plat.supported_toolchains and tc.type != 'unit':
Javier B Perez4b554ba2016-08-15 13:25:33 -05001906 discards[instance] = "Not supported by the toolchain"
1907 continue
1908
Anas Nashif62224182017-08-09 23:55:53 -04001909 if plat.ram < tc.min_ram:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001910 discards[instance] = "Not enough RAM"
1911 continue
1912
Kumar Gala5141d522017-07-07 08:05:48 -05001913 if tc.depends_on:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001914 dep_intersection = tc.depends_on.intersection(
1915 set(plat.supported))
Kumar Gala5141d522017-07-07 08:05:48 -05001916 if dep_intersection != set(tc.depends_on):
1917 discards[instance] = "No hardware support"
1918 continue
Anas Nashifa792a3d2017-04-04 18:47:49 -04001919
Anas Nashif62224182017-08-09 23:55:53 -04001920 if plat.flash < tc.min_flash:
Anas Nashifa792a3d2017-04-04 18:47:49 -04001921 discards[instance] = "Not enough FLASH"
1922 continue
1923
1924 if set(plat.ignore_tags) & tc.tags:
1925 discards[instance] = "Excluded tags per platform"
1926 continue
1927
Anas Nashif674bb282018-01-09 09:12:15 -05001928 defconfig = {
Anas Nashif674bb282018-01-09 09:12:15 -05001929 "ARCH": arch.name,
1930 "PLATFORM": plat.name
1931 }
Javier B Perez79414542016-08-08 12:24:59 -05001932 defconfig.update(os.environ)
Andrew Boie41878222016-11-03 11:58:53 -07001933 for p, tdefconfig in tc.defconfig.items():
1934 if p == plat:
Andrew Boie3ea78922016-03-24 14:46:00 -07001935 defconfig.update(tdefconfig)
Anas Nashif2cf0df02015-10-10 09:29:43 -04001936 break
1937
Andrew Boie3ea78922016-03-24 14:46:00 -07001938 if tc.tc_filter:
1939 try:
1940 res = expr_parser.parse(tc.tc_filter, defconfig)
Andrew Boiec09b4b82017-04-18 11:46:07 -07001941 except (ValueError, SyntaxError) as se:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001942 sys.stderr.write(
1943 "Failed processing %s\n" % tc.yamlfile)
Andrew Boie3ea78922016-03-24 14:46:00 -07001944 raise se
1945 if not res:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001946 discards[instance] = (
1947 "defconfig doesn't satisfy expression '%s'" %
1948 tc.tc_filter)
Andrew Boie3ea78922016-03-24 14:46:00 -07001949 continue
Anas Nashif2cf0df02015-10-10 09:29:43 -04001950
Andrew Boie6acbe632015-07-17 12:03:52 -07001951 instance_list.append(instance)
1952
1953 if not instance_list:
1954 # Every platform in this arch was rejected already
1955 continue
1956
Anas Nashifa792a3d2017-04-04 18:47:49 -04001957 if default_platforms and not tc.build_on_all:
1958 if not tc.platform_whitelist:
Anas Nashif3ba1d432017-12-05 15:28:44 -05001959 instances = list(
1960 filter(
1961 lambda tc: tc.platform.default,
1962 instance_list))
Anas Nashifa792a3d2017-04-04 18:47:49 -04001963 self.add_instances(instances)
1964 else:
Anas Nashifab747062017-12-05 17:59:01 -05001965 self.add_instances(instance_list[:1])
Anas Nashifa792a3d2017-04-04 18:47:49 -04001966
Anas Nashif3ba1d432017-12-05 15:28:44 -05001967 for instance in list(
1968 filter(lambda tc: not tc.platform.default, instance_list)):
Anas Nashifa792a3d2017-04-04 18:47:49 -04001969 discards[instance] = "Not a default test platform"
Andrew Boie6acbe632015-07-17 12:03:52 -07001970 else:
Andrew Boie821d8322016-03-22 10:08:35 -07001971 self.add_instances(instance_list)
Anas Nashifab351f42018-04-08 08:57:48 -05001972
1973 for name, case in self.instances.items():
1974 case.create_overlay()
1975
Andrew Boie6acbe632015-07-17 12:03:52 -07001976 self.discards = discards
1977 return discards
1978
Andrew Boie821d8322016-03-22 10:08:35 -07001979 def add_instances(self, ti_list):
1980 for ti in ti_list:
1981 self.instances[ti.name] = ti
Andrew Boie6acbe632015-07-17 12:03:52 -07001982
Anas Nashif37f9dc52018-02-23 08:53:46 -06001983 def execute(self, cb, cb_context):
Daniel Leung6b170072016-04-07 12:10:25 -07001984
1985 def calc_one_elf_size(name, goal):
1986 if not goal.failed:
Alberto Escolar Piedrasb1045fe2018-07-14 13:11:02 +02001987 if self.instances[name].platform.type != "native":
1988 i = self.instances[name]
1989 sc = i.calculate_sizes()
1990 goal.metrics["ram_size"] = sc.get_ram_size()
1991 goal.metrics["rom_size"] = sc.get_rom_size()
1992 goal.metrics["unrecognized"] = sc.unrecognized_sections()
1993 else:
1994 goal.metrics["ram_size"] = 0
1995 goal.metrics["rom_size"] = 0
1996 goal.metrics["unrecognized"] = []
Daniel Leung6b170072016-04-07 12:10:25 -07001997
Anas Nashif37f9dc52018-02-23 08:53:46 -06001998 mg = MakeGenerator(self.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07001999 for i in self.instances.values():
Anas Nashif37f9dc52018-02-23 08:53:46 -06002000 mg.add_test_instance(i, options.extra_args)
Andrew Boie6acbe632015-07-17 12:03:52 -07002001 self.goals = mg.execute(cb, cb_context)
Daniel Leung6b170072016-04-07 12:10:25 -07002002
2003 # Parallelize size calculation
2004 executor = concurrent.futures.ThreadPoolExecutor(CPU_COUNTS)
Anas Nashif3ba1d432017-12-05 15:28:44 -05002005 futures = [executor.submit(calc_one_elf_size, name, goal)
2006 for name, goal in self.goals.items()]
Daniel Leung6b170072016-04-07 12:10:25 -07002007 concurrent.futures.wait(futures)
2008
Andrew Boie6acbe632015-07-17 12:03:52 -07002009 return self.goals
2010
Anas Nashifbd166f42017-09-02 12:32:08 -04002011 def run_report(self, filename):
2012 with open(filename, "at") as csvfile:
2013 fieldnames = ['path', 'test', 'platform', 'arch']
2014 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2015 for instance in self.instances.values():
2016 rowdict = {
Anas Nashif3ba1d432017-12-05 15:28:44 -05002017 "path": os.path.dirname(instance.test.name),
2018 "test": os.path.basename(instance.test.name),
2019 "platform": instance.platform.name,
2020 "arch": instance.platform.arch
2021 }
Anas Nashifbd166f42017-09-02 12:32:08 -04002022 cw.writerow(rowdict)
2023
Andrew Boie6acbe632015-07-17 12:03:52 -07002024 def discard_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002025 if self.discards is None:
2026 raise SanityRuntimeError("apply_filters() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002027
Anas Nashifbd166f42017-09-02 12:32:08 -04002028 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002029 fieldnames = ["test", "arch", "platform", "reason"]
2030 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2031 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002032 for instance, reason in self.discards.items():
Anas Nashif3ba1d432017-12-05 15:28:44 -05002033 rowdict = {"test": instance.test.name,
2034 "arch": instance.platform.arch,
2035 "platform": instance.platform.name,
2036 "reason": reason}
Andrew Boie6acbe632015-07-17 12:03:52 -07002037 cw.writerow(rowdict)
2038
2039 def compare_metrics(self, filename):
2040 # name, datatype, lower results better
2041 interesting_metrics = [("ram_size", int, True),
2042 ("rom_size", int, True)]
2043
Anas Nashif3ba1d432017-12-05 15:28:44 -05002044 if self.goals is None:
2045 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002046
2047 if not os.path.exists(filename):
2048 info("Cannot compare metrics, %s not found" % filename)
2049 return []
2050
2051 results = []
2052 saved_metrics = {}
2053 with open(filename) as fp:
2054 cr = csv.DictReader(fp)
2055 for row in cr:
2056 d = {}
2057 for m, _, _ in interesting_metrics:
2058 d[m] = row[m]
2059 saved_metrics[(row["test"], row["platform"])] = d
2060
Andrew Boie08ce5a52016-02-22 13:28:10 -08002061 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002062 i = self.instances[name]
2063 mkey = (i.test.name, i.platform.name)
2064 if mkey not in saved_metrics:
2065 continue
2066 sm = saved_metrics[mkey]
2067 for metric, mtype, lower_better in interesting_metrics:
2068 if metric not in goal.metrics:
2069 continue
2070 if sm[metric] == "":
2071 continue
2072 delta = goal.metrics[metric] - mtype(sm[metric])
Andrew Boieea7928f2015-08-14 14:27:38 -07002073 if delta == 0:
2074 continue
2075 results.append((i, metric, goal.metrics[metric], delta,
2076 lower_better))
Andrew Boie6acbe632015-07-17 12:03:52 -07002077 return results
2078
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002079
2080
2081 def encode_for_xml(self, unicode_data, encoding='ascii'):
2082 unicode_data = unicode_data.replace('\x00', '')
2083 return unicode_data
2084
2085 def testcase_target_report(self, report_file):
2086
2087 run = "Sanitycheck"
2088 eleTestsuite = None
2089 append = options.only_failed
2090
2091 errors = 0
2092 passes = 0
2093 fails = 0
2094 duration = 0
2095 skips = 0
2096
2097 for identifier, ti in self.instances.items():
2098 for k in ti.results.keys():
2099 if ti.results[k] == 'PASS':
2100 passes += 1
2101 elif ti.results[k] == 'BLOCK':
2102 errors += 1
Anas Nashif61e21632018-04-08 13:30:16 -05002103 elif ti.results[k] == 'SKIP':
2104 skips += 1
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002105 else:
2106 fails += 1
2107
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002108 eleTestsuites = ET.Element('testsuites')
2109 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2110 name=run, time="%d" % duration,
2111 tests="%d" % (errors + passes + fails),
2112 failures="%d" % fails,
Anas Nashif61e21632018-04-08 13:30:16 -05002113 errors="%d" % errors, skipped="%d" %skips)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002114
2115 handler_time = "0"
Anas Nashif61e21632018-04-08 13:30:16 -05002116
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002117 # print out test results
2118 for identifier, ti in self.instances.items():
2119 for k in ti.results.keys():
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002120
2121 eleTestcase = ET.SubElement(
2122 eleTestsuite, 'testcase', classname="%s:%s" %(ti.platform.name, os.path.basename(ti.test.name)),
Anas Nashif61e21632018-04-08 13:30:16 -05002123 name="%s" % (k), time=handler_time)
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002124 if ti.results[k] in ['FAIL', 'BLOCK']:
2125 el = None
2126
2127 if ti.results[k] == 'FAIL':
2128 el = ET.SubElement(
2129 eleTestcase,
2130 'failure',
2131 type="failure",
2132 message="failed")
2133 elif ti.results[k] == 'BLOCK':
2134 el = ET.SubElement(
2135 eleTestcase,
2136 'error',
2137 type="failure",
2138 message="failed")
2139 p = os.path.join(options.outdir, ti.platform.name, ti.test.name)
2140 bl = os.path.join(p, "handler.log")
2141
2142 if os.path.exists(bl):
2143 with open(bl, "rb") as f:
2144 log = f.read().decode("utf-8")
2145 el.text = self.encode_for_xml(log)
2146
Anas Nashif61e21632018-04-08 13:30:16 -05002147 elif ti.results[k] == 'SKIP':
2148 el = ET.SubElement(
2149 eleTestcase,
2150 'skipped')
2151
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002152 result = ET.tostring(eleTestsuites)
2153 f = open(report_file, 'wb')
2154 f.write(result)
2155 f.close()
2156
2157
Anas Nashif4f028882017-12-30 11:48:43 -05002158 def testcase_xunit_report(self, filename, duration):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002159 if self.goals is None:
2160 raise SanityRuntimeError("execute() hasn't been run!")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002161
2162 fails = 0
2163 passes = 0
2164 errors = 0
2165
2166 for name, goal in self.goals.items():
2167 if goal.failed:
Anas Nashif9a839df2018-01-29 08:42:38 -05002168 if goal.reason in ['build_error', 'handler_crash']:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002169 errors += 1
2170 else:
2171 fails += 1
2172 else:
2173 passes += 1
2174
2175 run = "Sanitycheck"
2176 eleTestsuite = None
Anas Nashif4f028882017-12-30 11:48:43 -05002177 append = options.only_failed
Anas Nashifb3311ed2017-04-13 14:44:48 -04002178
Anas Nashif0605fa32017-05-07 08:51:02 -04002179 if os.path.exists(filename) and append:
Anas Nashifb3311ed2017-04-13 14:44:48 -04002180 tree = ET.parse(filename)
2181 eleTestsuites = tree.getroot()
Anas Nashif3ba1d432017-12-05 15:28:44 -05002182 eleTestsuite = tree.findall('testsuite')[0]
Anas Nashifb3311ed2017-04-13 14:44:48 -04002183 else:
2184 eleTestsuites = ET.Element('testsuites')
Anas Nashif3ba1d432017-12-05 15:28:44 -05002185 eleTestsuite = ET.SubElement(eleTestsuites, 'testsuite',
2186 name=run, time="%d" % duration,
2187 tests="%d" % (errors + passes + fails),
2188 failures="%d" % fails,
2189 errors="%d" % errors, skip="0")
Anas Nashifb3311ed2017-04-13 14:44:48 -04002190
Anas Nashifc8390f12017-11-25 17:14:12 -05002191 handler_time = "0"
Anas Nashifb3311ed2017-04-13 14:44:48 -04002192 for name, goal in self.goals.items():
2193
2194 i = self.instances[name]
2195 if append:
2196 for tc in eleTestsuite.findall('testcase'):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002197 if tc.get('classname') == "%s:%s" % (
2198 i.platform.name, i.test.name):
Anas Nashifb3311ed2017-04-13 14:44:48 -04002199 eleTestsuite.remove(tc)
2200
Anas Nashif4d25b502017-11-25 17:37:17 -05002201 if not goal.failed and goal.handler:
2202 handler_time = "%s" %(goal.metrics["handler_time"])
Anas Nashifb3311ed2017-04-13 14:44:48 -04002203
Anas Nashif3ba1d432017-12-05 15:28:44 -05002204 eleTestcase = ET.SubElement(
2205 eleTestsuite, 'testcase', classname="%s:%s" %
2206 (i.platform.name, i.test.name), name="%s" %
2207 (name), time=handler_time)
Anas Nashifb3311ed2017-04-13 14:44:48 -04002208 if goal.failed:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002209 failure = ET.SubElement(
2210 eleTestcase,
2211 'failure',
2212 type="failure",
2213 message=goal.reason)
Anas Nashif4f028882017-12-30 11:48:43 -05002214 p = ("%s/%s/%s" % (options.outdir, i.platform.name, i.test.name))
Anas Nashifb3311ed2017-04-13 14:44:48 -04002215 bl = os.path.join(p, "build.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002216 if goal.reason != 'build_error':
Anas Nashifa49048b2018-01-29 08:41:19 -05002217 bl = os.path.join(p, "handler.log")
Anas Nashifaccc8eb2017-05-01 16:33:43 -04002218
Anas Nashifb3311ed2017-04-13 14:44:48 -04002219 if os.path.exists(bl):
Anas Nashif712d3452017-12-29 22:09:03 -05002220 with open(bl, "rb") as f:
2221 log = f.read().decode("utf-8")
2222 failure.text = log
Anas Nashifb3311ed2017-04-13 14:44:48 -04002223
2224 result = ET.tostring(eleTestsuites)
2225 f = open(filename, 'wb')
2226 f.write(result)
2227 f.close()
2228
Andrew Boie6acbe632015-07-17 12:03:52 -07002229 def testcase_report(self, filename):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002230 if self.goals is None:
2231 raise SanityRuntimeError("execute() hasn't been run!")
Andrew Boie6acbe632015-07-17 12:03:52 -07002232
Andrew Boie08ce5a52016-02-22 13:28:10 -08002233 with open(filename, "wt") as csvfile:
Andrew Boie6acbe632015-07-17 12:03:52 -07002234 fieldnames = ["test", "arch", "platform", "passed", "status",
Anas Nashifc8390f12017-11-25 17:14:12 -05002235 "extra_args", "qemu", "handler_time", "ram_size",
Andrew Boie6acbe632015-07-17 12:03:52 -07002236 "rom_size"]
2237 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2238 cw.writeheader()
Andrew Boie08ce5a52016-02-22 13:28:10 -08002239 for name, goal in self.goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002240 i = self.instances[name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002241 rowdict = {"test": i.test.name,
2242 "arch": i.platform.arch,
2243 "platform": i.platform.name,
2244 "extra_args": " ".join(i.test.extra_args),
2245 "qemu": i.platform.qemu_support}
Andrew Boie6acbe632015-07-17 12:03:52 -07002246 if goal.failed:
2247 rowdict["passed"] = False
2248 rowdict["status"] = goal.reason
2249 else:
2250 rowdict["passed"] = True
Anas Nashif4d25b502017-11-25 17:37:17 -05002251 if goal.handler:
Anas Nashifc8390f12017-11-25 17:14:12 -05002252 rowdict["handler_time"] = goal.metrics["handler_time"]
Andrew Boie6acbe632015-07-17 12:03:52 -07002253 rowdict["ram_size"] = goal.metrics["ram_size"]
2254 rowdict["rom_size"] = goal.metrics["rom_size"]
2255 cw.writerow(rowdict)
2256
2257
2258def parse_arguments():
2259
Anas Nashif3ba1d432017-12-05 15:28:44 -05002260 parser = argparse.ArgumentParser(
2261 description=__doc__,
2262 formatter_class=argparse.RawDescriptionHelpFormatter)
Genaro Saucedo Tejada28bba922016-10-24 18:00:58 -05002263 parser.fromfile_prefix_chars = "+"
Andrew Boie6acbe632015-07-17 12:03:52 -07002264
Anas Nashif3ba1d432017-12-05 15:28:44 -05002265 parser.add_argument(
2266 "-p", "--platform", action="append",
2267 help="Platform filter for testing. This option may be used multiple "
2268 "times. Testcases will only be built/run on the platforms "
2269 "specified. If this option is not used, then platforms marked "
2270 "as default in the platform metadata file will be chosen "
2271 "to build and test. ")
2272 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002273 "-a", "--arch", action="append",
2274 help="Arch filter for testing. Takes precedence over --platform. "
2275 "If unspecified, test all arches. Multiple invocations "
2276 "are treated as a logical 'or' relationship")
2277 parser.add_argument(
2278 "-t", "--tag", action="append",
2279 help="Specify tags to restrict which tests to run by tag value. "
2280 "Default is to not do any tag filtering. Multiple invocations "
2281 "are treated as a logical 'or' relationship")
Anas Nashifdfa86e22016-10-24 17:08:56 -04002282 parser.add_argument("-e", "--exclude-tag", action="append",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002283 help="Specify tags of tests that should not run. "
2284 "Default is to run all tests with all tags.")
2285 parser.add_argument(
2286 "-f",
2287 "--only-failed",
2288 action="store_true",
2289 help="Run only those tests that failed the previous sanity check "
2290 "invocation.")
2291 parser.add_argument(
2292 "-c", "--config", action="append",
2293 help="Specify platform configuration values filtering. This can be "
2294 "specified two ways: <config>=<value> or just <config>. The "
2295 "defconfig for all platforms will be "
2296 "checked. For the <config>=<value> case, only match defconfig "
2297 "that have that value defined. For the <config> case, match "
2298 "defconfig that have that value assigned to any value. "
2299 "Prepend a '!' to invert the match.")
2300 parser.add_argument(
2301 "-s", "--test", action="append",
2302 help="Run only the specified test cases. These are named by "
2303 "<path to test project relative to "
2304 "--testcase-root>/<testcase.yaml section name>")
2305 parser.add_argument(
2306 "-l", "--all", action="store_true",
2307 help="Build/test on all platforms. Any --platform arguments "
2308 "ignored.")
Andrew Boie6acbe632015-07-17 12:03:52 -07002309
Anas Nashif3ba1d432017-12-05 15:28:44 -05002310 parser.add_argument(
2311 "-o", "--testcase-report",
2312 help="Output a CSV spreadsheet containing results of the test run")
2313 parser.add_argument(
2314 "-d", "--discard-report",
2315 help="Output a CSV spreadsheet showing tests that were skipped "
2316 "and why")
Daniel Leung7f850102016-04-08 11:07:32 -07002317 parser.add_argument("--compare-report",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002318 help="Use this report file for size comparison")
Daniel Leung7f850102016-04-08 11:07:32 -07002319
Anas Nashif3ba1d432017-12-05 15:28:44 -05002320 parser.add_argument(
2321 "-B", "--subset",
2322 help="Only run a subset of the tests, 1/4 for running the first 25%%, "
2323 "3/5 means run the 3rd fifth of the total. "
2324 "This option is useful when running a large number of tests on "
2325 "different hosts to speed up execution time.")
Anas Nashifa8a13882017-12-30 13:01:06 -05002326
2327 parser.add_argument(
2328 "-N", "--ninja", action="store_true",
Anas Nashif25f6ab62018-03-06 07:15:11 -06002329 help="Use the Ninja generator with CMake")
Anas Nashifa8a13882017-12-30 13:01:06 -05002330
Anas Nashif3ba1d432017-12-05 15:28:44 -05002331 parser.add_argument(
2332 "-y", "--dry-run", action="store_true",
2333 help="Create the filtered list of test cases, but don't actually "
2334 "run them. Useful if you're just interested in "
2335 "--discard-report")
Andrew Boie6acbe632015-07-17 12:03:52 -07002336
Anas Nashif75547e22018-02-24 08:32:14 -06002337 parser.add_argument("--list-tags", action="store_true",
2338 help="list all tags in selected tests")
2339
Anas Nashifc0149cc2018-04-14 23:12:58 -05002340 parser.add_argument("--list-tests", action="store_true",
2341 help="list all tests.")
2342
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002343 parser.add_argument("--export-tests", action="store",
2344 metavar="FILENAME",
2345 help="Export tests case meta-data to a file in CSV format.")
2346
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002347 parser.add_argument("--detailed-report",
2348 action="store",
2349 metavar="FILENAME",
2350 help="Generate a junit report with detailed testcase results.")
2351
Anas Nashif3ba1d432017-12-05 15:28:44 -05002352 parser.add_argument(
2353 "-r", "--release", action="store_true",
2354 help="Update the benchmark database with the results of this test "
2355 "run. Intended to be run by CI when tagging an official "
2356 "release. This database is used as a basis for comparison "
2357 "when looking for deltas in metrics such as footprint")
Andrew Boie6acbe632015-07-17 12:03:52 -07002358 parser.add_argument("-w", "--warnings-as-errors", action="store_true",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002359 help="Treat warning conditions as errors")
2360 parser.add_argument(
2361 "-v",
2362 "--verbose",
2363 action="count",
2364 default=0,
2365 help="Emit debugging information, call multiple times to increase "
2366 "verbosity")
2367 parser.add_argument(
2368 "-i", "--inline-logs", action="store_true",
2369 help="Upon test failure, print relevant log data to stdout "
2370 "instead of just a path to it")
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002371 parser.add_argument("--log-file", metavar="FILENAME", action="store",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002372 help="log also to file")
2373 parser.add_argument(
2374 "-m", "--last-metrics", action="store_true",
2375 help="Instead of comparing metrics from the last --release, "
2376 "compare with the results of the previous sanity check "
2377 "invocation")
2378 parser.add_argument(
2379 "-u",
2380 "--no-update",
2381 action="store_true",
2382 help="do not update the results of the last run of the sanity "
2383 "checks")
2384 parser.add_argument(
2385 "-F",
2386 "--load-tests",
2387 metavar="FILENAME",
2388 action="store",
2389 help="Load list of tests to be run from file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002390
Anas Nashif3ba1d432017-12-05 15:28:44 -05002391 parser.add_argument(
2392 "-E",
2393 "--save-tests",
2394 metavar="FILENAME",
2395 action="store",
2396 help="Save list of tests to be run to file.")
Anas Nashifbd166f42017-09-02 12:32:08 -04002397
Anas Nashif3ba1d432017-12-05 15:28:44 -05002398 parser.add_argument(
2399 "-b", "--build-only", action="store_true",
2400 help="Only build the code, do not execute any of it in QEMU")
2401 parser.add_argument(
2402 "-j", "--jobs", type=int,
2403 help="Number of cores to use when building, defaults to "
2404 "number of CPUs * 2")
Anas Nashif73440ea2018-02-19 10:57:03 -06002405
2406 parser.add_argument(
2407 "--device-testing", action="store_true",
Anas Nashif333a3152018-05-24 14:35:33 -05002408 help="Test on device directly. Specify the serial device to "
2409 "use with the --device-serial option.")
Anas Nashif73440ea2018-02-19 10:57:03 -06002410 parser.add_argument(
2411 "--device-serial",
Anas Nashif333a3152018-05-24 14:35:33 -05002412 help="Serial device for accessing the board (e.g., /dev/ttyACM0)")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002413 parser.add_argument(
Anas Nashif424a3db2018-02-20 08:37:24 -06002414 "--show-footprint", action="store_true",
2415 help="Show footprint statistics and deltas since last release."
2416 )
2417 parser.add_argument(
Anas Nashif3ba1d432017-12-05 15:28:44 -05002418 "-H", "--footprint-threshold", type=float, default=5,
2419 help="When checking test case footprint sizes, warn the user if "
2420 "the new app size is greater then the specified percentage "
2421 "from the last release. Default is 5. 0 to warn on any "
2422 "increase on app size")
2423 parser.add_argument(
2424 "-D", "--all-deltas", action="store_true",
2425 help="Show all footprint deltas, positive or negative. Implies "
2426 "--footprint-threshold=0")
Håkon Øye Amundsende223cc2018-04-25 06:24:25 +00002427 parser.add_argument(
2428 "-O", "--outdir",
2429 default="%s/sanity-out" % ZEPHYR_BASE,
2430 help="Output directory for logs and binaries. "
2431 "This directory will be deleted unless '--no-clean' is set.")
Anas Nashif3ba1d432017-12-05 15:28:44 -05002432 parser.add_argument(
2433 "-n", "--no-clean", action="store_true",
2434 help="Do not delete the outdir before building. Will result in "
2435 "faster compilation since builds will be incremental")
2436 parser.add_argument(
2437 "-T", "--testcase-root", action="append", default=[],
2438 help="Base directory to recursively search for test cases. All "
2439 "testcase.yaml files under here will be processed. May be "
2440 "called multiple times. Defaults to the 'samples' and "
2441 "'tests' directories in the Zephyr tree.")
2442 board_root_list = ["%s/boards" % ZEPHYR_BASE,
2443 "%s/scripts/sanity_chk/boards" % ZEPHYR_BASE]
2444 parser.add_argument(
2445 "-A", "--board-root", action="append", default=board_root_list,
2446 help="Directory to search for board configuration files. All .yaml "
2447 "files in the directory will be processed.")
2448 parser.add_argument(
2449 "-z", "--size", action="append",
2450 help="Don't run sanity checks. Instead, produce a report to "
2451 "stdout detailing RAM/ROM sizes on the specified filenames. "
2452 "All other command line arguments ignored.")
2453 parser.add_argument(
2454 "-S", "--enable-slow", action="store_true",
2455 help="Execute time-consuming test cases that have been marked "
2456 "as 'slow' in testcase.yaml. Normally these are only built.")
Andrew Boie55121052016-07-20 11:52:04 -07002457 parser.add_argument("-R", "--enable-asserts", action="store_true",
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002458 default=True,
Andrew Boie29599f62018-05-24 13:33:09 -07002459 help="deprecated, left for compatibility")
Kumar Gala0d48cbe2018-01-26 10:22:20 -06002460 parser.add_argument("--disable-asserts", action="store_false",
2461 dest="enable_asserts",
Andrew Boie29599f62018-05-24 13:33:09 -07002462 help="deprecated, left for compatibility")
Anas Nashife3febe92016-11-30 14:25:44 -05002463 parser.add_argument("-Q", "--error-on-deprecations", action="store_false",
Anas Nashif3ba1d432017-12-05 15:28:44 -05002464 help="Error on deprecation warnings.")
Sebastian Bøec2182612017-11-09 12:25:02 +01002465
2466 parser.add_argument(
2467 "-x", "--extra-args", action="append", default=[],
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002468 help="""Extra CMake cache entries to define when building test cases.
2469 May be called multiple times. The key-value entries will be
Sebastian Bøec2182612017-11-09 12:25:02 +01002470 prefixed with -D before being passed to CMake.
2471
2472 E.g
2473 "sanitycheck -x=USE_CCACHE=0"
2474 will translate to
2475 "cmake -DUSE_CCACHE=0"
2476
2477 which will ultimately disable ccache.
2478 """
2479 )
2480
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002481 parser.add_argument("--enable-coverage", action="store_true",
2482 help="Enable code coverage when building unit tests and"
2483 " when targeting the native_posix board")
2484
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002485 parser.add_argument("-C", "--coverage", action="store_true",
Alberto Escolar Piedras25e06362018-02-10 17:40:33 +01002486 help="Generate coverage report for unit tests, and"
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002487 " tests and samples run in native_posix. Implies"
2488 " --enable_coverage")
Andrew Boie6acbe632015-07-17 12:03:52 -07002489
2490 return parser.parse_args()
2491
Anas Nashif3ba1d432017-12-05 15:28:44 -05002492
Andrew Boie6acbe632015-07-17 12:03:52 -07002493def log_info(filename):
Mazen NEIFER8f1dd3a2017-02-04 14:32:04 +01002494 filename = os.path.relpath(os.path.realpath(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002495 if INLINE_LOGS:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002496 info("{:-^100}".format(filename))
Andrew Boied01535c2017-01-10 13:15:02 -08002497
2498 try:
2499 with open(filename) as fp:
2500 data = fp.read()
2501 except Exception as e:
2502 data = "Unable to read log data (%s)\n" % (str(e))
2503
2504 sys.stdout.write(data)
2505 if log_file:
2506 log_file.write(data)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002507 info("{:-^100}".format(filename))
Andrew Boie6acbe632015-07-17 12:03:52 -07002508 else:
Andrew Boie08ce5a52016-02-22 13:28:10 -08002509 info("\tsee: " + COLOR_YELLOW + filename + COLOR_NORMAL)
Andrew Boie6acbe632015-07-17 12:03:52 -07002510
Anas Nashif3ba1d432017-12-05 15:28:44 -05002511
Andrew Boie6acbe632015-07-17 12:03:52 -07002512def terse_test_cb(instances, goals, goal):
2513 total_tests = len(goals)
2514 total_done = 0
2515 total_failed = 0
2516
Andrew Boie08ce5a52016-02-22 13:28:10 -08002517 for k, g in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002518 if g.finished:
2519 total_done += 1
2520 if g.failed:
2521 total_failed += 1
2522
2523 if goal.failed:
2524 i = instances[goal.name]
Anas Nashif3ba1d432017-12-05 15:28:44 -05002525 info(
2526 "\n\n{:<25} {:<50} {}FAILED{}: {}".format(
2527 i.platform.name,
2528 i.test.name,
2529 COLOR_RED,
2530 COLOR_NORMAL,
2531 goal.reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002532 log_info(goal.get_error_log())
2533 info("")
2534
Anas Nashif3ba1d432017-12-05 15:28:44 -05002535 sys.stdout.write(
2536 "\rtotal complete: %s%4d/%4d%s %2d%% failed: %s%4d%s" %
2537 (COLOR_GREEN, total_done, total_tests, COLOR_NORMAL,
2538 int((float(total_done) / total_tests) * 100),
2539 COLOR_RED if total_failed > 0 else COLOR_NORMAL, total_failed,
2540 COLOR_NORMAL))
Andrew Boie6acbe632015-07-17 12:03:52 -07002541 sys.stdout.flush()
2542
Anas Nashif3ba1d432017-12-05 15:28:44 -05002543
Andrew Boie6acbe632015-07-17 12:03:52 -07002544def chatty_test_cb(instances, goals, goal):
2545 i = instances[goal.name]
2546
2547 if VERBOSE < 2 and not goal.finished:
2548 return
2549
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002550 total_tests = len(goals)
2551 total_tests_width = len(str(total_tests))
2552 total_done = 0
2553
2554 for k, g in goals.items():
2555 if g.finished:
2556 total_done += 1
2557
Andrew Boie6acbe632015-07-17 12:03:52 -07002558 if goal.failed:
2559 status = COLOR_RED + "FAILED" + COLOR_NORMAL + ": " + goal.reason
2560 elif goal.finished:
2561 status = COLOR_GREEN + "PASSED" + COLOR_NORMAL
2562 else:
2563 status = goal.make_state
2564
Ruslan Mstoi33fa63e2018-05-29 14:35:34 +03002565 info("{:>{}}/{} {:<25} {:<50} {}".format(
2566 total_done, total_tests_width, total_tests, i.platform.name,
2567 i.test.name, status))
Andrew Boie6acbe632015-07-17 12:03:52 -07002568 if goal.failed:
2569 log_info(goal.get_error_log())
2570
Andrew Boiebbd670c2015-08-17 13:16:11 -07002571
2572def size_report(sc):
2573 info(sc.filename)
Andrew Boie73b4ee62015-10-07 11:33:22 -07002574 info("SECTION NAME VMA LMA SIZE HEX SZ TYPE")
Andrew Boie9882dcd2015-10-07 14:25:51 -07002575 for i in range(len(sc.sections)):
2576 v = sc.sections[i]
2577
Andrew Boie73b4ee62015-10-07 11:33:22 -07002578 info("%-17s 0x%08x 0x%08x %8d 0x%05x %-7s" %
2579 (v["name"], v["virt_addr"], v["load_addr"], v["size"], v["size"],
2580 v["type"]))
Andrew Boie9882dcd2015-10-07 14:25:51 -07002581
Andrew Boie73b4ee62015-10-07 11:33:22 -07002582 info("Totals: %d bytes (ROM), %d bytes (RAM)" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002583 (sc.rom_size, sc.ram_size))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002584 info("")
2585
Anas Nashif3ba1d432017-12-05 15:28:44 -05002586
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002587def generate_coverage(outdir, ignores):
2588 with open(os.path.join(outdir, "coverage.log"), "a") as coveragelog:
2589 coveragefile = os.path.join(outdir, "coverage.info")
2590 ztestfile = os.path.join(outdir, "ztest.info")
2591 subprocess.call(["lcov", "--capture", "--directory", outdir,
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002592 "--rc", "lcov_branch_coverage=1",
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002593 "--output-file", coveragefile], stdout=coveragelog)
2594 # We want to remove tests/* and tests/ztest/test/* but save tests/ztest
2595 subprocess.call(["lcov", "--extract", coveragefile,
Anas Nashif3ba1d432017-12-05 15:28:44 -05002596 os.path.join(ZEPHYR_BASE, "tests", "ztest", "*"),
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002597 "--output-file", ztestfile,
2598 "--rc", "lcov_branch_coverage=1"], stdout=coveragelog)
2599
2600 if os.path.getsize(ztestfile) > 0:
2601 subprocess.call(["lcov", "--remove", ztestfile,
2602 os.path.join(ZEPHYR_BASE, "tests/ztest/test/*"),
2603 "--output-file", ztestfile,
2604 "--rc", "lcov_branch_coverage=1"],
2605 stdout=coveragelog)
2606 files = [coveragefile, ztestfile];
2607 else:
2608 files = [coveragefile];
2609
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002610 for i in ignores:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002611 subprocess.call(
2612 ["lcov", "--remove", coveragefile, i, "--output-file",
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002613 coveragefile, "--rc", "lcov_branch_coverage=1"],
Anas Nashif3ba1d432017-12-05 15:28:44 -05002614 stdout=coveragelog)
Alberto Escolar Piedrasbc101c52018-02-11 09:33:55 +01002615
2616 ret = subprocess.call(["genhtml", "--legend", "--branch-coverage",
2617 "-output-directory",
2618 os.path.join(outdir, "coverage")] + files,
2619 stdout=coveragelog)
2620 if ret==0:
2621 info("HTML report generated: %s"%
2622 os.path.join(outdir, "coverage","index.html"));
Anas Nashif3ba1d432017-12-05 15:28:44 -05002623
Andrew Boiebbd670c2015-08-17 13:16:11 -07002624
Andrew Boie6acbe632015-07-17 12:03:52 -07002625def main():
Andrew Boie4b182472015-07-31 12:25:22 -07002626 start_time = time.time()
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002627 global VERBOSE, INLINE_LOGS, CPU_COUNTS, log_file
Anas Nashife10b6512017-12-30 13:01:45 -05002628 global options
2629 options = parse_arguments()
Andrew Boiebbd670c2015-08-17 13:16:11 -07002630
Alberto Escolar Piedrasc026c2e2018-06-21 09:30:20 +02002631 if options.coverage:
2632 options.enable_coverage = True
2633
Anas Nashife10b6512017-12-30 13:01:45 -05002634 if options.size:
2635 for fn in options.size:
Andrew Boie52fef672016-11-29 12:21:59 -08002636 size_report(SizeCalculator(fn, []))
Andrew Boiebbd670c2015-08-17 13:16:11 -07002637 sys.exit(0)
2638
Anas Nashif73440ea2018-02-19 10:57:03 -06002639
2640 if options.device_testing:
2641 if options.device_serial is None or len(options.platform) != 1:
2642 sys.exit(1)
2643
Anas Nashife10b6512017-12-30 13:01:45 -05002644 VERBOSE += options.verbose
2645 INLINE_LOGS = options.inline_logs
2646 if options.log_file:
2647 log_file = open(options.log_file, "w")
2648 if options.jobs:
2649 CPU_COUNTS = options.jobs
Andrew Boie6acbe632015-07-17 12:03:52 -07002650
Anas Nashife10b6512017-12-30 13:01:45 -05002651 if options.subset:
2652 subset, sets = options.subset.split("/")
Anas Nashif035799f2017-05-13 21:31:53 -04002653 if int(subset) > 0 and int(sets) >= int(subset):
Anas Nashif3ba1d432017-12-05 15:28:44 -05002654 info("Running only a subset: %s/%s" % (subset, sets))
Anas Nashif035799f2017-05-13 21:31:53 -04002655 else:
Anas Nashife10b6512017-12-30 13:01:45 -05002656 error("You have provided a wrong subset value: %s." % options.subset)
Anas Nashif035799f2017-05-13 21:31:53 -04002657 return
2658
Anas Nashife10b6512017-12-30 13:01:45 -05002659 if os.path.exists(options.outdir) and not options.no_clean:
2660 info("Cleaning output directory " + options.outdir)
2661 shutil.rmtree(options.outdir)
Andrew Boie6acbe632015-07-17 12:03:52 -07002662
Anas Nashife10b6512017-12-30 13:01:45 -05002663 if not options.testcase_root:
2664 options.testcase_root = [os.path.join(ZEPHYR_BASE, "tests"),
Andrew Boie3d348712016-04-08 11:52:13 -07002665 os.path.join(ZEPHYR_BASE, "samples")]
2666
Anas Nashif37f9dc52018-02-23 08:53:46 -06002667 ts = TestSuite(options.board_root, options.testcase_root, options.outdir)
Anas Nashifbd166f42017-09-02 12:32:08 -04002668
Kumar Galac84235e2018-04-10 13:32:51 -05002669 if ts.load_errors:
2670 sys.exit(1)
2671
Anas Nashif75547e22018-02-24 08:32:14 -06002672 if options.list_tags:
2673 tags = set()
2674 for n,tc in ts.testcases.items():
2675 tags = tags.union(tc.tags)
2676
2677 for t in tags:
2678 print("- {}".format(t))
2679
2680 return
2681
Anas Nashif5d6e7eb2018-06-01 09:51:08 -05002682
2683 def export_tests(filename, tests):
2684 with open(filename, "wt") as csvfile:
2685 fieldnames = ['section', 'subsection', 'title', 'reference']
2686 cw = csv.DictWriter(csvfile, fieldnames, lineterminator=os.linesep)
2687 for test in tests:
2688 data = test.split(".")
2689 subsec = " ".join(data[1].split("_")).title()
2690 rowdict = {
2691 "section": data[0].capitalize(),
2692 "subsection": subsec,
2693 "title": test,
2694 "reference": test
2695 }
2696 cw.writerow(rowdict)
2697
2698 if options.export_tests:
2699 cnt = 0
2700 unq = []
2701 for n,tc in ts.testcases.items():
2702 for c in tc.cases:
2703 unq.append(c)
2704
2705 tests = sorted(set(unq))
2706 export_tests(options.export_tests, tests)
2707 return
2708
2709
Anas Nashifc0149cc2018-04-14 23:12:58 -05002710 if options.list_tests:
2711 cnt = 0
Anas Nashifa3abe962018-05-05 19:10:22 -05002712 unq = []
Anas Nashifc0149cc2018-04-14 23:12:58 -05002713 for n,tc in ts.testcases.items():
2714 for c in tc.cases:
Anas Nashifa3abe962018-05-05 19:10:22 -05002715 unq.append(c)
Anas Nashifc0149cc2018-04-14 23:12:58 -05002716
Anas Nashifa3abe962018-05-05 19:10:22 -05002717 for u in sorted(set(unq)):
2718 cnt = cnt + 1
2719 print(" - {}".format(u))
Anas Nashifc0149cc2018-04-14 23:12:58 -05002720 print("{} total.".format(cnt))
2721 return
2722
Anas Nashifbd166f42017-09-02 12:32:08 -04002723 discards = []
Anas Nashife10b6512017-12-30 13:01:45 -05002724 if options.load_tests:
2725 ts.load_from_file(options.load_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002726 else:
Anas Nashif4f028882017-12-30 11:48:43 -05002727 discards = ts.apply_filters()
Andrew Boie6acbe632015-07-17 12:03:52 -07002728
Anas Nashife10b6512017-12-30 13:01:45 -05002729 if options.discard_report:
2730 ts.discard_report(options.discard_report)
Andrew Boie6acbe632015-07-17 12:03:52 -07002731
Anas Nashif30551f42018-01-12 21:56:59 -05002732 if VERBOSE > 1 and discards:
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002733 # if we are using command line platform filter, no need to list every
2734 # other platform as excluded, we know that already.
2735 # Show only the discards that apply to the selected platforms on the
2736 # command line
2737
Andrew Boie08ce5a52016-02-22 13:28:10 -08002738 for i, reason in discards.items():
Anas Nashiff18e2ab2018-01-13 07:57:42 -05002739 if options.platform and i.platform.name not in options.platform:
2740 continue
Anas Nashif3ba1d432017-12-05 15:28:44 -05002741 debug(
2742 "{:<25} {:<50} {}SKIPPED{}: {}".format(
2743 i.platform.name,
2744 i.test.name,
2745 COLOR_YELLOW,
2746 COLOR_NORMAL,
2747 reason))
Andrew Boie6acbe632015-07-17 12:03:52 -07002748
Anas Nashif1a5bba72018-01-05 08:07:45 -05002749
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002750 def native_posix_and_unit_first(a, b):
2751 if a[0].startswith('native_posix') or a[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002752 return -1
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002753 if b[0].startswith('native_posix') or b[0].startswith('unit_testing'):
Anas Nashif1a5bba72018-01-05 08:07:45 -05002754 return 1
2755 return (a > b) - (a < b)
2756
2757 ts.instances = OrderedDict(sorted(ts.instances.items(),
Alberto Escolar Piedrasc6524ab2018-02-08 21:35:55 +01002758 key=cmp_to_key(native_posix_and_unit_first)))
Anas Nashifbd166f42017-09-02 12:32:08 -04002759
Anas Nashife10b6512017-12-30 13:01:45 -05002760 if options.save_tests:
2761 ts.run_report(options.save_tests)
Anas Nashifbd166f42017-09-02 12:32:08 -04002762 return
2763
Anas Nashife10b6512017-12-30 13:01:45 -05002764 if options.subset:
Anas Nashif1a5bba72018-01-05 08:07:45 -05002765
Anas Nashife10b6512017-12-30 13:01:45 -05002766 subset, sets = options.subset.split("/")
Anas Nashifbd166f42017-09-02 12:32:08 -04002767 total = len(ts.instances)
Anas Nashif035799f2017-05-13 21:31:53 -04002768 per_set = round(total / int(sets))
Anas Nashif3ba1d432017-12-05 15:28:44 -05002769 start = (int(subset) - 1) * per_set
Anas Nashif035799f2017-05-13 21:31:53 -04002770 if subset == sets:
2771 end = total
2772 else:
2773 end = start + per_set
2774
Anas Nashif3ba1d432017-12-05 15:28:44 -05002775 sliced_instances = islice(ts.instances.items(), start, end)
Anas Nashif035799f2017-05-13 21:31:53 -04002776 ts.instances = OrderedDict(sliced_instances)
2777
Andrew Boie6acbe632015-07-17 12:03:52 -07002778 info("%d tests selected, %d tests discarded due to filters" %
2779 (len(ts.instances), len(discards)))
2780
Anas Nashife10b6512017-12-30 13:01:45 -05002781 if options.dry_run:
Andrew Boie6acbe632015-07-17 12:03:52 -07002782 return
2783
2784 if VERBOSE or not TERMINAL:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002785 goals = ts.execute(
2786 chatty_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002787 ts.instances)
Andrew Boie6acbe632015-07-17 12:03:52 -07002788 else:
Anas Nashif3ba1d432017-12-05 15:28:44 -05002789 goals = ts.execute(
2790 terse_test_cb,
Anas Nashif37f9dc52018-02-23 08:53:46 -06002791 ts.instances)
Andrew Boie08ce5a52016-02-22 13:28:10 -08002792 info("")
Andrew Boie6acbe632015-07-17 12:03:52 -07002793
Anas Nashife0a6a0b2018-02-15 20:07:24 -06002794 if options.detailed_report:
2795 ts.testcase_target_report(options.detailed_report)
2796
Daniel Leung7f850102016-04-08 11:07:32 -07002797 # figure out which report to use for size comparison
Anas Nashife10b6512017-12-30 13:01:45 -05002798 if options.compare_report:
2799 report_to_use = options.compare_report
2800 elif options.last_metrics:
Daniel Leung7f850102016-04-08 11:07:32 -07002801 report_to_use = LAST_SANITY
2802 else:
2803 report_to_use = RELEASE_DATA
2804
2805 deltas = ts.compare_metrics(report_to_use)
Andrew Boie6acbe632015-07-17 12:03:52 -07002806 warnings = 0
Anas Nashif424a3db2018-02-20 08:37:24 -06002807 if deltas and options.show_footprint:
Andrew Boieea7928f2015-08-14 14:27:38 -07002808 for i, metric, value, delta, lower_better in deltas:
Anas Nashife10b6512017-12-30 13:01:45 -05002809 if not options.all_deltas and ((delta < 0 and lower_better) or
Andrew Boieea7928f2015-08-14 14:27:38 -07002810 (delta > 0 and not lower_better)):
Andrew Boie6acbe632015-07-17 12:03:52 -07002811 continue
2812
Andrew Boieea7928f2015-08-14 14:27:38 -07002813 percentage = (float(delta) / float(value - delta))
Anas Nashife10b6512017-12-30 13:01:45 -05002814 if not options.all_deltas and (percentage <
2815 (options.footprint_threshold / 100.0)):
Andrew Boieea7928f2015-08-14 14:27:38 -07002816 continue
2817
Daniel Leung00525c22016-04-11 10:27:56 -07002818 info("{:<25} {:<60} {}{}{}: {} {:<+4}, is now {:6} {:+.2%}".format(
Andrew Boieea7928f2015-08-14 14:27:38 -07002819 i.platform.name, i.test.name, COLOR_YELLOW,
Anas Nashife10b6512017-12-30 13:01:45 -05002820 "INFO" if options.all_deltas else "WARNING", COLOR_NORMAL,
Andrew Boie829c0562015-10-13 09:44:19 -07002821 metric, delta, value, percentage))
Andrew Boie6acbe632015-07-17 12:03:52 -07002822 warnings += 1
2823
2824 if warnings:
2825 info("Deltas based on metrics from last %s" %
Anas Nashife10b6512017-12-30 13:01:45 -05002826 ("release" if not options.last_metrics else "run"))
Andrew Boie6acbe632015-07-17 12:03:52 -07002827
2828 failed = 0
Andrew Boie08ce5a52016-02-22 13:28:10 -08002829 for name, goal in goals.items():
Andrew Boie6acbe632015-07-17 12:03:52 -07002830 if goal.failed:
2831 failed += 1
Jaakko Hannikainenca505f82016-08-22 15:03:46 +03002832 elif goal.metrics.get("unrecognized"):
Andrew Boie73b4ee62015-10-07 11:33:22 -07002833 info("%sFAILED%s: %s has unrecognized binary sections: %s" %
2834 (COLOR_RED, COLOR_NORMAL, goal.name,
2835 str(goal.metrics["unrecognized"])))
2836 failed += 1
Andrew Boie6acbe632015-07-17 12:03:52 -07002837
Anas Nashife10b6512017-12-30 13:01:45 -05002838 if options.coverage:
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002839 info("Generating coverage files...")
Anas Nashife10b6512017-12-30 13:01:45 -05002840 generate_coverage(options.outdir, ["tests/*", "samples/*"])
Jaakko Hannikainen54c90bc2016-08-31 14:17:03 +03002841
Anas Nashif0605fa32017-05-07 08:51:02 -04002842 duration = time.time() - start_time
Andrew Boie4b182472015-07-31 12:25:22 -07002843 info("%s%d of %d%s tests passed with %s%d%s warnings in %d seconds" %
Anas Nashif3ba1d432017-12-05 15:28:44 -05002844 (COLOR_RED if failed else COLOR_GREEN, len(goals) - failed,
2845 len(goals), COLOR_NORMAL, COLOR_YELLOW if warnings else COLOR_NORMAL,
2846 warnings, COLOR_NORMAL, duration))
Andrew Boie6acbe632015-07-17 12:03:52 -07002847
Anas Nashife10b6512017-12-30 13:01:45 -05002848 if options.testcase_report:
2849 ts.testcase_report(options.testcase_report)
2850 if not options.no_update:
Anas Nashif4f028882017-12-30 11:48:43 -05002851 ts.testcase_xunit_report(LAST_SANITY_XUNIT, duration)
Andrew Boie6acbe632015-07-17 12:03:52 -07002852 ts.testcase_report(LAST_SANITY)
Anas Nashife10b6512017-12-30 13:01:45 -05002853 if options.release:
Andrew Boie6acbe632015-07-17 12:03:52 -07002854 ts.testcase_report(RELEASE_DATA)
Inaky Perez-Gonzalez9a36cb62016-11-29 10:43:40 -08002855 if log_file:
2856 log_file.close()
Anas Nashife10b6512017-12-30 13:01:45 -05002857 if failed or (warnings and options.warnings_as_errors):
Andrew Boie6acbe632015-07-17 12:03:52 -07002858 sys.exit(1)
2859
Anas Nashif3ba1d432017-12-05 15:28:44 -05002860
Andrew Boie6acbe632015-07-17 12:03:52 -07002861if __name__ == "__main__":
2862 main()