mirror of
https://github.com/lvgl/lvgl.git
synced 2025-01-28 07:03:00 +08:00
b1bf7fd29a
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. ```
208 lines
6.8 KiB
Python
Executable File
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()
|