123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- #!/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_SYSHEAP': 'Test config, system heap, 32 bit color depth',
- 'OPTIONS_TEST_DEFHEAP': 'Test config, LVGL heap, 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', '--timeout', '30', '--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 = '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()
|