1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-01-28 07:03:00 +08:00
lvgl/tests/main.py
Chris Mumford b1bf7fd29a
test(ci) build and run tests in parallel. (#2515)
Create one GitHub workflow job for each build option build
or test execution. This allows each of the five builds to
be executed in parallel as well as making it easier to
navigate to the test output as it now resides in its own
job.

This change **does** change the command-line arguments to
the test execution script. This is required to allow the
build options name to be passed in.

New `tests/main.py` command-line help:

```
usage: main.py [-h] [--build-options BUILD_OPTIONS] [--clean]
               [--report]
               [{build,test} [{build,test} ...]]

Build and/or run LVGL tests.

positional arguments:
  {build,test}          build: compile build tests, test:
                        compile/run executable tests.

optional arguments:
  -h, --help            show this help message and exit
  --build-options BUILD_OPTIONS
                        the build option name to build or run.
                        When omitted all build configurations
                        are used.
  --clean               clean existing build artifacts before
                        operation.
  --report              generate code coverage report for
                        tests.

This program builds and optionally runs the LVGL test programs.
There are two types of LVGL tests: "build", and "test". The
build-only tests, as their name suggests, only verify that the
program successfully compiles and links (with various build
options). There are also a set of tests that execute to verify
correct LVGL library behavior.
```
2021-09-01 11:58:14 +02:00

208 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import errno
import glob
import shutil
import subprocess
import sys
import os
lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
# Key values must match variable names in CMakeLists.txt.
build_only_options = {
'OPTIONS_MINIMAL_MONOCHROME': 'Minimal config monochrome',
'OPTIONS_NORMAL_8BIT': 'Normal config, 8 bit color depth',
'OPTIONS_16BIT': 'Minimal config, 16 bit color depth',
'OPTIONS_16BIT_SWAP': 'Normal config, 16 bit color depth swapped',
'OPTIONS_FULL_32BIT': 'Full config, 32 bit color depth',
}
test_options = {
'OPTIONS_TEST': 'Test config, 32 bit color depth',
}
def is_valid_option_name(option_name):
return option_name in build_only_options or option_name in test_options
def get_option_description(option_name):
if option_name in build_only_options:
return build_only_options[option_name]
return test_options[option_name]
def delete_dir_ignore_missing(dir_path):
'''Recursively delete a directory and ignore if missing.'''
try:
shutil.rmtree(dir_path)
except FileNotFoundError:
pass
def generate_test_runners():
'''Generate the test runner source code.'''
global lvgl_test_dir
os.chdir(lvgl_test_dir)
delete_dir_ignore_missing('src/test_runners')
os.mkdir('src/test_runners')
# TODO: Intermediate files should be in the build folders, not alongside
# the other repo source.
for f in glob.glob("./src/test_cases/test_*.c"):
r = f[:-2] + "_Runner.c"
r = r.replace("/test_cases/", "/test_runners/")
subprocess.check_call(['ruby', 'unity/generate_test_runner.rb',
f, r, 'config.yml'])
def options_abbrev(options_name):
'''Return an abbreviated version of the option name.'''
prefix = 'OPTIONS_'
assert options_name.startswith(prefix)
return options_name[len(prefix):].lower()
def get_base_buid_dir(options_name):
'''Given the build options name, return the build directory name.
Does not return the full path to the directory - just the base name.'''
return 'build_%s' % options_abbrev(options_name)
def get_build_dir(options_name):
'''Given the build options name, return the build directory name.
Returns absolute path to the build directory.'''
global lvgl_test_dir
return os.path.join(lvgl_test_dir, get_base_buid_dir(options_name))
def build_tests(options_name, build_type, clean):
'''Build all tests for the specified options name.'''
global lvgl_test_dir
print()
print()
label = 'Building: %s: %s' % (options_abbrev(
options_name), get_option_description(options_name))
print('=' * len(label))
print(label)
print('=' * len(label))
print(flush=True)
build_dir = get_build_dir(options_name)
if clean:
delete_dir_ignore_missing(build_dir)
os.chdir(lvgl_test_dir)
created_build_dir = False
if not os.path.isdir(build_dir):
os.mkdir(build_dir)
created_build_dir = True
os.chdir(build_dir)
if created_build_dir:
subprocess.check_call(['cmake', '-DCMAKE_BUILD_TYPE=%s' % build_type,
'-D%s=1' % options_name, '..'])
subprocess.check_call(['cmake', '--build', build_dir,
'--parallel', str(os.cpu_count())])
def run_tests(options_name):
'''Run the tests for the given options name.'''
print()
print()
label = 'Running tests for %s' % options_abbrev(options_name)
print('=' * len(label))
print(label)
print('=' * len(label), flush=True)
os.chdir(get_build_dir(options_name))
subprocess.check_call(
['ctest', '--parallel', str(os.cpu_count()), '--output-on-failure'])
def generate_code_coverage_report():
'''Produce code coverage test reports for the test execution.'''
global lvgl_test_dir
print()
print()
label = 'Generating code coverage reports'
print('=' * len(label))
print(label)
print('=' * len(label))
print(flush=True)
os.chdir(lvgl_test_dir)
delete_dir_ignore_missing('report')
os.mkdir('report')
root_dir = os.pardir
html_report_file = 'report/index.html'
cmd = ['gcovr', '--root', root_dir, '--html-details', '--output',
html_report_file, '--xml', 'report/coverage.xml',
'-j', str(os.cpu_count()), '--print-summary',
'--html-title', 'LVGL Test Coverage']
for d in ('.*\\bexamples/.*', '\\bsrc/test_.*', '\\bsrc/lv_test.*', '\\bunity\\b'):
cmd.extend(['--exclude', d])
subprocess.check_call(cmd)
print("Done: See %s" % html_report_file, flush=True)
if __name__ == "__main__":
epilog = '''This program builds and optionally runs the LVGL test programs.
There are two types of LVGL tests: "build", and "test". The build-only
tests, as their name suggests, only verify that the program successfully
compiles and links (with various build options). There are also a set of
tests that execute to verify correct LVGL library behavior.
'''
parser = argparse.ArgumentParser(
description='Build and/or run LVGL tests.', epilog=epilog)
parser.add_argument('--build-options', nargs=1,
help='''the build option name to build or run. When
omitted all build configurations are used.
''')
parser.add_argument('--clean', action='store_true', default=False,
help='clean existing build artifacts before operation.')
parser.add_argument('--report', action='store_true',
help='generate code coverage report for tests.')
parser.add_argument('actions', nargs='*', choices=['build', 'test'],
help='build: compile build tests, test: compile/run executable tests.')
args = parser.parse_args()
if args.build_options:
options_to_build = args.build_options
else:
if 'build' in args.actions:
if 'test' in args.actions:
options_to_build = {**build_only_options, **test_options}
else:
options_to_build = build_only_options
else:
options_to_build = test_options
for opt in options_to_build:
if not is_valid_option_name(opt):
print('Invalid build option "%s"' % opt, file=sys.stderr)
sys.exit(errno.EINVAL)
generate_test_runners()
for options_name in options_to_build:
is_test = options_name in test_options
build_type = 'Release' if is_test else 'Debug'
build_tests(options_name, build_type, args.clean)
if is_test:
try:
run_tests(options_name)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
if args.report:
generate_code_coverage_report()