Merge pull request #16905 from aabadie/pr/tools/compile_and_test_with_black

tools/compile_and_test_for_board: apply black automatic code formatter + add format checker in tox
This commit is contained in:
Alexandre Abadie 2021-10-22 12:57:42 +02:00 committed by GitHub
commit 1fc45d888b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 243 additions and 173 deletions

View File

@ -1,5 +1,6 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# pylint: disable=line-too-long
""" """
This script handles building all applications and tests for one board and also This script handles building all applications and tests for one board and also
execute tests if they are available. execute tests if they are available.
@ -103,9 +104,9 @@ except ImportError:
LOG_HANDLER = logging.StreamHandler() LOG_HANDLER = logging.StreamHandler()
LOG_HANDLER.setFormatter(logging.Formatter(logging.BASIC_FORMAT)) LOG_HANDLER.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
LOG_LEVELS = ('debug', 'info', 'warning', 'error', 'fatal', 'critical') LOG_LEVELS = ("debug", "info", "warning", "error", "fatal", "critical")
MAKE = os.environ.get('MAKE', 'make') MAKE = os.environ.get("MAKE", "make")
class ErrorInTest(Exception): class ErrorInTest(Exception):
@ -114,6 +115,7 @@ class ErrorInTest(Exception):
It contains the step that failed in 'message', the 'application' and the It contains the step that failed in 'message', the 'application' and the
'errorfile' path to the execution error. 'errorfile' path to the execution error.
""" """
def __init__(self, message, application, errorfile): def __init__(self, message, application, errorfile):
super().__init__(message) super().__init__(message)
self.application = application self.application = application
@ -168,10 +170,10 @@ def apps_directories(riotdir, apps_dirs=None, apps_dirs_skip=None):
def _riot_applications_dirs(riotdir): def _riot_applications_dirs(riotdir):
"""Applications directories in the RIOT repository with relative path.""" """Applications directories in the RIOT repository with relative path."""
cmd = [MAKE, 'info-applications'] cmd = [MAKE, "info-applications"]
out = subprocess.check_output(cmd, cwd=riotdir) out = subprocess.check_output(cmd, cwd=riotdir)
out = out.decode('utf-8', errors='replace') out = out.decode("utf-8", errors="replace")
return out.split() return out.split()
@ -181,9 +183,9 @@ def check_is_board(riotdir, board):
:raises ValueError: on invalid board :raises ValueError: on invalid board
:returns: board name :returns: board name
""" """
if board == 'common': if board == "common":
raise ValueError(f"'{board}' is not a board") raise ValueError(f"'{board}' is not a board")
board_dir = os.path.join(riotdir, 'boards', board) board_dir = os.path.join(riotdir, "boards", board)
if not os.path.isdir(board_dir): if not os.path.isdir(board_dir):
raise ValueError(f"Cannot find '{board}' in {riotdir}/boards") raise ValueError(f"Cannot find '{board}' in {riotdir}/boards")
return board return board
@ -220,7 +222,7 @@ def is_in_directory(path, directory):
return path.startswith(directory) return path.startswith(directory)
class RIOTApplication(): class RIOTApplication:
"""RIOT Application representation. """RIOT Application representation.
Allows calling make commands on an application for a board. Allows calling make commands on an application for a board.
@ -232,12 +234,15 @@ class RIOTApplication():
:param junit: track application in JUnit XML :param junit: track application in JUnit XML
""" """
MAKEFLAGS = ('RIOT_CI_BUILD=1', 'CC_NOCOLOR=1', '--no-print-directory') MAKEFLAGS = ("RIOT_CI_BUILD=1", "CC_NOCOLOR=1", "--no-print-directory")
COMPILE_TARGETS = ('clean', 'all',) COMPILE_TARGETS = (
FLASH_TARGETS = ('flash-only',) "clean",
TEST_TARGETS = ('test',) "all",
TEST_AVAILABLE_TARGETS = ('test/available',) )
FLASH_TARGETS = ("flash-only",)
TEST_TARGETS = ("test",)
TEST_AVAILABLE_TARGETS = ("test/available",)
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, board, riotdir, appdir, resultdir, junit=False): def __init__(self, board, riotdir, appdir, resultdir, junit=False):
@ -248,24 +253,23 @@ class RIOTApplication():
if junit: if junit:
if not junit_xml: if not junit_xml:
raise ImportError("`junit-xml` required for --report-xml") raise ImportError("`junit-xml` required for --report-xml")
self.testcase = junit_xml.TestCase(name=self.appdir, self.testcase = junit_xml.TestCase(name=self.appdir, stdout="", stderr="")
stdout='', stderr='')
self.log_stream = io.StringIO() self.log_stream = io.StringIO()
logging.basicConfig(stream=self.log_stream) logging.basicConfig(stream=self.log_stream)
else: else:
self.testcase = None self.testcase = None
self.logger = logging.getLogger(f'{board}.{appdir}') self.logger = logging.getLogger(f"{board}.{appdir}")
# Currently not handling absolute directories or outside of RIOT # Currently not handling absolute directories or outside of RIOT
assert is_in_directory(self.resultdir, resultdir), \ assert is_in_directory(
"Application result directory is outside main result directory" self.resultdir, resultdir
), "Application result directory is outside main result directory"
# Extract values from make # Extract values from make
def name(self): def name(self):
"""Get application name.""" """Get application name."""
appname = self.make(['info-debug-variable-APPLICATION'], appname = self.make(["info-debug-variable-APPLICATION"], log_error=True).strip()
log_error=True).strip() self.logger.debug("APPLICATION: %s", appname)
self.logger.debug('APPLICATION: %s', appname)
return appname return appname
def has_test(self): def has_test(self):
@ -279,48 +283,49 @@ class RIOTApplication():
has_test = False has_test = False
else: else:
has_test = True has_test = True
self.logger.info('Application has test: %s', has_test) self.logger.info("Application has test: %s", has_test)
return has_test return has_test
def board_is_supported(self): def board_is_supported(self):
"""Return if current board is supported.""" """Return if current board is supported."""
env = {'BOARDS': self.board} env = {"BOARDS": self.board}
cmd = ['info-boards-supported'] cmd = ["info-boards-supported"]
ret = self.make(cmd, env=env, log_error=True).strip() ret = self.make(cmd, env=env, log_error=True).strip()
supported = ret == self.board supported = ret == self.board
self.logger.info('Board supported: %s', supported) self.logger.info("Board supported: %s", supported)
return supported return supported
def board_has_enough_memory(self): def board_has_enough_memory(self):
"""Return if current board has enough memory.""" """Return if current board has enough memory."""
cmd = ['info-debug-variable-BOARD_INSUFFICIENT_MEMORY'] cmd = ["info-debug-variable-BOARD_INSUFFICIENT_MEMORY"]
boards = self.make(cmd, log_error=True).strip().split() boards = self.make(cmd, log_error=True).strip().split()
has_enough_memory = self.board not in boards has_enough_memory = self.board not in boards
self.logger.info('Board has enough memory: %s', has_enough_memory) self.logger.info("Board has enough memory: %s", has_enough_memory)
return has_enough_memory return has_enough_memory
def clean(self): def clean(self):
"""Clean build and packages.""" """Clean build and packages."""
try: try:
cmd = ['clean', 'clean-pkg'] cmd = ["clean", "clean-pkg"]
self.make(cmd) self.make(cmd)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
if self.testcase: if self.testcase:
self.testcase.stderr += err.output + '\n' self.testcase.stderr += err.output + "\n"
self.logger.warning('Got an error during clean, ignore: %r', err) self.logger.warning("Got an error during clean, ignore: %r", err)
def clean_intermediates(self): def clean_intermediates(self):
"""Clean intermediates only.""" """Clean intermediates only."""
try: try:
cmd = ['clean-intermediates'] cmd = ["clean-intermediates"]
self.make(cmd) self.make(cmd)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
if self.testcase: if self.testcase:
self.testcase.stderr += err.output + '\n' self.testcase.stderr += err.output + "\n"
self.logger.warning('Got an error during clean-intermediates,' self.logger.warning(
' ignore: %r', err) "Got an error during clean-intermediates," " ignore: %r", err
)
def run_compilation_and_test(self, **test_kwargs): def run_compilation_and_test(self, **test_kwargs):
"""Same as `compilation_and_test` but handles exception. """Same as `compilation_and_test` but handles exception.
@ -333,7 +338,7 @@ class RIOTApplication():
self.compilation_and_test(**test_kwargs) self.compilation_and_test(**test_kwargs)
res = None res = None
except ErrorInTest as err: except ErrorInTest as err:
self.logger.error('Failed during: %s', err) self.logger.error("Failed during: %s", err)
res = (str(err), err.application.appdir, err.errorfile) res = (str(err), err.application.appdir, err.errorfile)
if self.testcase: if self.testcase:
self.testcase.elapsed_sec = time.time() - self.testcase.timestamp self.testcase.elapsed_sec = time.time() - self.testcase.timestamp
@ -350,11 +355,16 @@ class RIOTApplication():
skip_reason_details if skip_reason_details else skip_reason, skip_reason_details if skip_reason_details else skip_reason,
output, output,
) )
self._write_resultfile('skip', skip_reason) self._write_resultfile("skip", skip_reason)
def compilation_and_test(self, clean_after=False, runtest=True, def compilation_and_test(
incremental=False, jobs=False, self,
with_test_only=False): clean_after=False,
runtest=True,
incremental=False,
jobs=False,
with_test_only=False,
):
# pylint:disable=too-many-arguments # pylint:disable=too-many-arguments
"""Compile and execute test if available. """Compile and execute test if available.
@ -375,14 +385,14 @@ class RIOTApplication():
# Ignore incompatible APPS # Ignore incompatible APPS
if not self.board_is_supported(): if not self.board_is_supported():
create_directory(self.resultdir, clean=True) create_directory(self.resultdir, clean=True)
self._skip('not_supported', 'Board not supported') self._skip("not_supported", "Board not supported")
return return
if not self.board_has_enough_memory(): if not self.board_has_enough_memory():
create_directory(self.resultdir, clean=True) create_directory(self.resultdir, clean=True)
self._skip( self._skip(
'not_enough_memory', "not_enough_memory",
'Board has not enough memory to carry application', "Board has not enough memory to carry application",
) )
return return
@ -390,10 +400,7 @@ class RIOTApplication():
if with_test_only and not has_test: if with_test_only and not has_test:
create_directory(self.resultdir, clean=True) create_directory(self.resultdir, clean=True)
self._skip( self._skip("disabled_has_no_tests", f"{self.appdir} has no tests")
'disabled_has_no_tests',
f"{self.appdir} has no tests"
)
return return
# Normal case for supported apps # Normal case for supported apps
@ -404,60 +411,55 @@ class RIOTApplication():
compilation_cmd = list(self.COMPILE_TARGETS) compilation_cmd = list(self.COMPILE_TARGETS)
if jobs is not None: if jobs is not None:
compilation_cmd += ['--jobs'] compilation_cmd += ["--jobs"]
if jobs: if jobs:
compilation_cmd += [str(jobs)] compilation_cmd += [str(jobs)]
self.make_with_outfile('compilation', compilation_cmd) self.make_with_outfile("compilation", compilation_cmd)
if clean_after: if clean_after:
self.clean_intermediates() self.clean_intermediates()
if runtest: if runtest:
if has_test: if has_test:
setuptasks = collections.OrderedDict( setuptasks = collections.OrderedDict([("flash", self.FLASH_TARGETS)])
[('flash', self.FLASH_TARGETS)]) self.make_with_outfile(
self.make_with_outfile('test', self.TEST_TARGETS, "test", self.TEST_TARGETS, save_output=True, setuptasks=setuptasks
save_output=True, setuptasks=setuptasks) )
if clean_after: if clean_after:
self.clean() self.clean()
else: else:
self._skip( self._skip("skip.no_test", f"{self.appdir} has no tests")
'skip.no_test',
f"{self.appdir} has no tests"
)
self.logger.info('Success') self.logger.info("Success")
def make(self, args, env=None, log_error=False): def make(self, args, env=None, log_error=False):
"""Run make command in appdir.""" """Run make command in appdir."""
env = env or {} env = env or {}
# HACK: BOARD should be set for make in environment and not command # HACK: BOARD should be set for make in environment and not command
# line either it break the `BOARD=none` for global commands # line either it break the `BOARD=none` for global commands
env['BOARD'] = self.board env["BOARD"] = self.board
full_env = os.environ.copy() full_env = os.environ.copy()
full_env.update(env) full_env.update(env)
cmd = [MAKE] cmd = [MAKE]
cmd.extend(self.MAKEFLAGS) cmd.extend(self.MAKEFLAGS)
cmd.extend(['-C', os.path.join(self.riotdir, self.appdir)]) cmd.extend(["-C", os.path.join(self.riotdir, self.appdir)])
cmd.extend(args) cmd.extend(args)
self.logger.debug('%r ENV %s', cmd, env) self.logger.debug("%r ENV %s", cmd, env)
# Call without 'universal_newlines' to have bytes and handle decoding # Call without 'universal_newlines' to have bytes and handle decoding
# (encoding and errors are only supported after python 3.6) # (encoding and errors are only supported after python 3.6)
try: try:
out = subprocess.check_output(cmd, env=full_env, out = subprocess.check_output(cmd, env=full_env, stderr=subprocess.STDOUT)
stderr=subprocess.STDOUT) out = out.decode("utf-8", errors="replace")
out = out.decode('utf-8', errors='replace')
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
err.output = err.output.decode('utf-8', errors='replace') err.output = err.output.decode("utf-8", errors="replace")
if log_error: if log_error:
self.logger.error('Error during command: \n%s', err.output) self.logger.error("Error during command: \n%s", err.output)
raise err raise err
return out return out
def make_with_outfile(self, name, args, save_output=False, def make_with_outfile(self, name, args, save_output=False, setuptasks=None):
setuptasks=None):
"""Run make but save result in an outfile. """Run make but save result in an outfile.
It will be saved in `self.resultdir/name.[success|failure]`. It will be saved in `self.resultdir/name.[success|failure]`.
@ -467,20 +469,20 @@ class RIOTApplication():
if not, return an empty string. if not, return an empty string.
:param setuptasks: OrderedDict of tasks to run before the main one :param setuptasks: OrderedDict of tasks to run before the main one
""" """
self.logger.info('Run %s', name) self.logger.info("Run %s", name)
setuptasks = setuptasks or {} setuptasks = setuptasks or {}
# Do not re-run if success # Do not re-run if success
output = self._make_get_previous_output(name) output = self._make_get_previous_output(name)
if output is not None: if output is not None:
if self.testcase: if self.testcase:
self.testcase.stdout += output + '\n' self.testcase.stdout += output + "\n"
return return
# Run setup-tasks, output is only kept in case of error # Run setup-tasks, output is only kept in case of error
for taskname, taskargs in setuptasks.items(): for taskname, taskargs in setuptasks.items():
taskname = f'{name}.{taskname}' taskname = f"{name}.{taskname}"
self.logger.info('Run %s', taskname) self.logger.info("Run %s", taskname)
try: try:
self.make(taskargs) self.make(taskargs)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
@ -490,10 +492,10 @@ class RIOTApplication():
try: try:
output = self.make(args) output = self.make(args)
if not save_output: if not save_output:
output = '' output = ""
if self.testcase: if self.testcase:
self.testcase.stdout += output + '\n' self.testcase.stdout += output + "\n"
self._write_resultfile(name, 'success', output) self._write_resultfile(name, "success", output)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
self._make_handle_error(name, err) self._make_handle_error(name, err)
@ -503,9 +505,8 @@ class RIOTApplication():
Returns `output` if it is there, None if not. Returns `output` if it is there, None if not.
""" """
try: try:
with open(self._outfile(f'{name}.success'), with open(self._outfile(f"{name}.success"), encoding="utf-8") as outputfd:
encoding='utf-8') as outputfd: self.logger.info("Nothing to be done for %s", name)
self.logger.info('Nothing to be done for %s', name)
return outputfd.read() return outputfd.read()
except OSError: except OSError:
pass pass
@ -513,31 +514,29 @@ class RIOTApplication():
def _make_handle_error(self, name, err): def _make_handle_error(self, name, err):
"""Handle exception during make step `name`.""" """Handle exception during make step `name`."""
output = ' '.join(err.cmd) + '\n' output = " ".join(err.cmd) + "\n"
output += err.output + '\n' output += err.output + "\n"
output += f'Return value: {err.returncode}\n' output += f"Return value: {err.returncode}\n"
outfile = self._write_resultfile(name, 'failed', output) outfile = self._write_resultfile(name, "failed", output)
self.logger.warning(output) self.logger.warning(output)
self.logger.error('Error during %s, writing to %s', name, outfile) self.logger.error("Error during %s, writing to %s", name, outfile)
if self.testcase: if self.testcase:
self.testcase.stderr += err.output + '\n' self.testcase.stderr += err.output + "\n"
if name == "test": if name == "test":
self.testcase.add_failure_info(f"{err.cmd} failed", self.testcase.add_failure_info(f"{err.cmd} failed", err.output)
err.output)
else: else:
self.testcase.add_error_info(f"{err.cmd} had an error", self.testcase.add_error_info(f"{err.cmd} had an error", err.output)
err.output)
raise ErrorInTest(name, self, outfile) raise ErrorInTest(name, self, outfile)
def _write_resultfile(self, name, status, body=''): def _write_resultfile(self, name, status, body=""):
"""Write `body` to result file `name.status`. """Write `body` to result file `name.status`.
It also deletes other `name.*` files before. It also deletes other `name.*` files before.
""" """
# Delete previous status files # Delete previous status files
resultfiles = glob.glob(self._outfile(f'{name}.*')) resultfiles = glob.glob(self._outfile(f"{name}.*"))
for resultfile in resultfiles: for resultfile in resultfiles:
try: try:
os.remove(resultfile) os.remove(resultfile)
@ -545,11 +544,10 @@ class RIOTApplication():
pass pass
# Create new file # Create new file
filename = f'{name}.{status}' filename = f"{name}.{status}"
outfile = self._outfile(filename) outfile = self._outfile(filename)
with open(outfile, 'w+', encoding='utf-8', with open(outfile, "w+", encoding="utf-8", errors="replace") as outfd:
errors='replace') as outfd:
outfd.write(body) outfd.write(body)
outfd.flush() outfd.flush()
return outfile return outfile
@ -559,7 +557,7 @@ class RIOTApplication():
return os.path.join(self.resultdir, filename) return os.path.join(self.resultdir, filename)
TOOLCHAIN_SCRIPT = 'dist/tools/ci/print_toolchain_versions.sh' TOOLCHAIN_SCRIPT = "dist/tools/ci/print_toolchain_versions.sh"
def print_toolchain(riotdir): def print_toolchain(riotdir):
@ -569,23 +567,23 @@ def print_toolchain(riotdir):
""" """
toolchain_script = os.path.join(riotdir, TOOLCHAIN_SCRIPT) toolchain_script = os.path.join(riotdir, TOOLCHAIN_SCRIPT)
out = subprocess.check_output([toolchain_script]) out = subprocess.check_output([toolchain_script])
return out.decode('utf-8', errors='replace') return out.decode("utf-8", errors="replace")
def save_toolchain(riotdir, resultdir): def save_toolchain(riotdir, resultdir):
"""Save toolchain in 'resultdir/toolchain'.""" """Save toolchain in 'resultdir/toolchain'."""
outfile = os.path.join(resultdir, 'toolchain') outfile = os.path.join(resultdir, "toolchain")
create_directory(resultdir) create_directory(resultdir)
toolchain = print_toolchain(riotdir) toolchain = print_toolchain(riotdir)
with open(outfile, 'w+', encoding='utf-8', errors='replace') as outputfd: with open(outfile, "w+", encoding="utf-8", errors="replace") as outputfd:
outputfd.write(toolchain) outputfd.write(toolchain)
def _test_failed_summary(errors, relpathstart=None): def _test_failed_summary(errors, relpathstart=None):
"""Generate a test summary for failures.""" """Generate a test summary for failures."""
if not errors: if not errors:
return '' return ""
errors_dict = {} errors_dict = {}
for step, appdir, errorfile in errors: for step, appdir, errorfile in errors:
@ -593,13 +591,13 @@ def _test_failed_summary(errors, relpathstart=None):
errorfile = os.path.relpath(errorfile, relpathstart) errorfile = os.path.relpath(errorfile, relpathstart)
errors_dict.setdefault(step, []).append((appdir, errorfile)) errors_dict.setdefault(step, []).append((appdir, errorfile))
summary = '' summary = ""
for step, errs in sorted(errors_dict.items()): for step, errs in sorted(errors_dict.items()):
summary += f'Failures during {step}:\n' summary += f"Failures during {step}:\n"
for appdir, errorfile in errs: for appdir, errorfile in errs:
summary += f'- [{appdir}]({errorfile})\n' summary += f"- [{appdir}]({errorfile})\n"
# Separate sections with a new line # Separate sections with a new line
summary += '\n' summary += "\n"
# Remove last new line # Remove last new line
summary = summary[:-1] summary = summary[:-1]
@ -608,9 +606,9 @@ def _test_failed_summary(errors, relpathstart=None):
def save_failure_summary(resultdir, summary): def save_failure_summary(resultdir, summary):
"""Save test summary in 'resultdir/board/failuresummary'.""" """Save test summary in 'resultdir/board/failuresummary'."""
outfile = os.path.join(resultdir, 'failuresummary.md') outfile = os.path.join(resultdir, "failuresummary.md")
with open(outfile, 'w+', encoding='utf-8', errors='replace') as outputfd: with open(outfile, "w+", encoding="utf-8", errors="replace") as outputfd:
outputfd.write(summary) outputfd.write(summary)
@ -633,7 +631,7 @@ def list_from_string(list_str=None):
>>> list_from_string("a b c") >>> list_from_string("a b c")
['a', 'b', 'c'] ['a', 'b', 'c']
""" """
value = (list_str or '').split(' ') value = (list_str or "").split(" ")
return [v for v in value if v] return [v for v in value if v]
@ -642,57 +640,96 @@ def _strip_board_equal(board):
Increase RIOT compatibility. Increase RIOT compatibility.
""" """
if board.startswith('BOARD='): if board.startswith("BOARD="):
board = board.replace('BOARD=', '') board = board.replace("BOARD=", "")
return board return board
PARSER = argparse.ArgumentParser( PARSER = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
formatter_class=argparse.ArgumentDefaultsHelpFormatter) PARSER.add_argument("riot_directory", help="RIOT directory to test")
PARSER.add_argument('riot_directory', help='RIOT directory to test') PARSER.add_argument("board", help="Board to test", type=_strip_board_equal)
PARSER.add_argument('board', help='Board to test', type=_strip_board_equal)
PARSER.add_argument('result_directory', nargs='?', default='results',
help='Result directory')
PARSER.add_argument( PARSER.add_argument(
'--applications', type=list_from_string, "result_directory", nargs="?", default="results", help="Result directory"
help=('List of applications to test, overwrites default configuration of'
' testing all applications'),
) )
PARSER.add_argument( PARSER.add_argument(
'--applications-exclude', type=list_from_string, "--applications",
help=('List of applications to exclude from tested applications.' type=list_from_string,
' Also applied after "--applications".'), help=(
"List of applications to test, overwrites default configuration of"
" testing all applications"
),
)
PARSER.add_argument(
"--applications-exclude",
type=list_from_string,
help=(
"List of applications to exclude from tested applications."
' Also applied after "--applications".'
),
)
PARSER.add_argument(
"--no-test", action="store_true", default=False, help="Disable executing tests"
)
PARSER.add_argument(
"--with-test-only",
action="store_true",
default=False,
help="Only compile applications that have a test",
)
PARSER.add_argument(
"--loglevel", choices=LOG_LEVELS, default="info", help="Python logger log level"
)
PARSER.add_argument(
"--incremental",
action="store_true",
default=False,
help="Do not rerun successful compilation and tests",
)
PARSER.add_argument(
"--clean-after",
action="store_true",
default=False,
help="Clean after running each test",
) )
PARSER.add_argument('--no-test', action='store_true', default=False,
help='Disable executing tests')
PARSER.add_argument('--with-test-only', action='store_true', default=False,
help='Only compile applications that have a test')
PARSER.add_argument('--loglevel', choices=LOG_LEVELS, default='info',
help='Python logger log level')
PARSER.add_argument('--incremental', action='store_true', default=False,
help='Do not rerun successful compilation and tests')
PARSER.add_argument('--clean-after', action='store_true', default=False,
help='Clean after running each test')
PARSER.add_argument('--compile-targets', type=list_from_string,
default=' '.join(RIOTApplication.COMPILE_TARGETS),
help='List of make targets to compile')
PARSER.add_argument('--flash-targets', type=list_from_string,
default=' '.join(RIOTApplication.FLASH_TARGETS),
help='List of make targets to flash')
PARSER.add_argument('--test-targets', type=list_from_string,
default=' '.join(RIOTApplication.TEST_TARGETS),
help='List of make targets to run test')
PARSER.add_argument('--test-available-targets', type=list_from_string,
default=' '.join(RIOTApplication.TEST_AVAILABLE_TARGETS),
help='List of make targets to know if a test is present')
PARSER.add_argument('--report-xml', action='store_true', default=False,
help='Output results to report.xml in the '
'result_directory')
PARSER.add_argument( PARSER.add_argument(
'--jobs', '-j', type=int, default=None, "--compile-targets",
help="Parallel building (0 means not limit, like '--jobs')") type=list_from_string,
default=" ".join(RIOTApplication.COMPILE_TARGETS),
help="List of make targets to compile",
)
PARSER.add_argument(
"--flash-targets",
type=list_from_string,
default=" ".join(RIOTApplication.FLASH_TARGETS),
help="List of make targets to flash",
)
PARSER.add_argument(
"--test-targets",
type=list_from_string,
default=" ".join(RIOTApplication.TEST_TARGETS),
help="List of make targets to run test",
)
PARSER.add_argument(
"--test-available-targets",
type=list_from_string,
default=" ".join(RIOTApplication.TEST_AVAILABLE_TARGETS),
help="List of make targets to know if a test is present",
)
PARSER.add_argument(
"--report-xml",
action="store_true",
default=False,
help="Output results to report.xml in the " "result_directory",
)
PARSER.add_argument(
"--jobs",
"-j",
type=int,
default=None,
help="Parallel building (0 means not limit, like '--jobs')",
)
def main(args): def main(args):
@ -704,23 +741,24 @@ def main(args):
logger.addHandler(LOG_HANDLER) logger.addHandler(LOG_HANDLER)
logger.info('Saving toolchain') logger.info("Saving toolchain")
save_toolchain(args.riot_directory, args.result_directory) save_toolchain(args.riot_directory, args.result_directory)
board = check_is_board(args.riot_directory, args.board) board = check_is_board(args.riot_directory, args.board)
logger.debug('board: %s', board) logger.debug("board: %s", board)
# Expand application directories: allows use of glob in application names # Expand application directories: allows use of glob in application names
apps_dirs = _expand_apps_directories(args.applications, apps_dirs = _expand_apps_directories(args.applications, args.riot_directory)
args.riot_directory) apps_dirs_skip = _expand_apps_directories(
apps_dirs_skip = _expand_apps_directories(args.applications_exclude, args.applications_exclude, args.riot_directory, skip=True
args.riot_directory, skip=True) )
app_dirs = apps_directories(args.riot_directory, apps_dirs=apps_dirs, app_dirs = apps_directories(
apps_dirs_skip=apps_dirs_skip) args.riot_directory, apps_dirs=apps_dirs, apps_dirs_skip=apps_dirs_skip
)
logger.debug('app_dirs: %s', app_dirs) logger.debug("app_dirs: %s", app_dirs)
logger.debug('resultdir: %s', args.result_directory) logger.debug("resultdir: %s", args.result_directory)
board_result_directory = os.path.join(args.result_directory, args.board) board_result_directory = os.path.join(args.result_directory, args.board)
# Overwrite the compile/test targets from command line arguments # Overwrite the compile/test targets from command line arguments
@ -730,18 +768,28 @@ def main(args):
RIOTApplication.TEST_AVAILABLE_TARGETS = args.test_available_targets RIOTApplication.TEST_AVAILABLE_TARGETS = args.test_available_targets
# List of applications for board # List of applications for board
applications = [RIOTApplication(board, args.riot_directory, app_dir, applications = [
board_result_directory, RIOTApplication(
junit=args.report_xml) board,
for app_dir in app_dirs] args.riot_directory,
app_dir,
board_result_directory,
junit=args.report_xml,
)
for app_dir in app_dirs
]
# Execute tests # Execute tests
errors = [app.run_compilation_and_test(clean_after=args.clean_after, errors = [
runtest=not args.no_test, app.run_compilation_and_test(
incremental=args.incremental, clean_after=args.clean_after,
jobs=args.jobs, runtest=not args.no_test,
with_test_only=args.with_test_only) incremental=args.incremental,
for app in applications] jobs=args.jobs,
with_test_only=args.with_test_only,
)
for app in applications
]
errors = [e for e in errors if e is not None] errors = [e for e in errors if e is not None]
num_errors = len(errors) num_errors = len(errors)
@ -755,16 +803,20 @@ def main(args):
with open(report_file, "w+", encoding="utf-8") as report: with open(report_file, "w+", encoding="utf-8") as report:
junit_xml.TestSuite.to_file( junit_xml.TestSuite.to_file(
report, report,
[junit_xml.TestSuite(f'compile_and_test_for_{board}', [
[app.testcase for app in applications])] junit_xml.TestSuite(
f"compile_and_test_for_{board}",
[app.testcase for app in applications],
)
],
) )
if num_errors: if num_errors:
logger.error('Tests failed: %d', num_errors) logger.error("Tests failed: %d", num_errors)
print(summary, end='') print(summary, end="")
else: else:
logger.info('Tests successful') logger.info("Tests successful")
sys.exit(num_errors) sys.exit(num_errors)
if __name__ == '__main__': if __name__ == "__main__":
main(PARSER.parse_args()) main(PARSER.parse_args())

View File

@ -0,0 +1,12 @@
# Use black compatible configuration for flake8 and pylint
# flake8: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8
# pylint: https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#pylint
[flake8]
max-line-length = 88
extend-ignore = E203
[pylint]
max-line-length = 88
[pylint.messages_control]
disable = C0330, C0326

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = test,lint,flake8 envlist = test,lint,flake8,black
skipsdist = True skipsdist = True
[testenv] [testenv]
@ -10,6 +10,7 @@ commands =
test: {[testenv:test]commands} test: {[testenv:test]commands}
lint: {[testenv:lint]commands} lint: {[testenv:lint]commands}
flake8: {[testenv:flake8]commands} flake8: {[testenv:flake8]commands}
black: {[testenv:black]commands}
[testenv:test] [testenv:test]
deps = pytest deps = pytest
@ -19,9 +20,14 @@ commands =
[testenv:lint] [testenv:lint]
deps = pylint deps = pylint
commands = commands =
pylint {env:script} tests pylint --rcfile=setup.cfg {env:script} tests
[testenv:flake8] [testenv:flake8]
deps = flake8 deps = flake8
commands = commands =
flake8 {env:script} tests flake8 {env:script} tests
[testenv:black]
deps = black
commands =
black --check --diff {env:script} tests