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.
```
This commit is contained in:
Chris Mumford
2021-09-01 02:58:14 -07:00
committed by GitHub
parent effd87eae0
commit b1bf7fd29a
4 changed files with 103 additions and 51 deletions

View File

@@ -191,17 +191,17 @@ set(LVGL_TEST_OPTIONS_FULL_32BIT
)
if (OPTIONS_MINIMAL_MONOCHROME)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_MINIMAL_MONOCHROME})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_MINIMAL_MONOCHROME})
elseif (OPTIONS_NORMAL_8BIT)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_NORMAL_8BIT})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_NORMAL_8BIT})
elseif (OPTIONS_16BIT)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_16BIT})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_16BIT})
elseif (OPTIONS_16BIT_SWAP)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_16BIT_SWAP})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_16BIT_SWAP})
elseif (OPTIONS_FULL_32BIT)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_FULL_32BIT})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_FULL_32BIT})
elseif (OPTIONS_TEST)
set (TEST_OPTIONS ${LVGL_TEST_OPTIONS_TEST})
set (BUILD_OPTIONS ${LVGL_TEST_OPTIONS_TEST})
set (TEST_LIBS --coverage)
else()
message(FATAL_ERROR "Must provide an options value.")
@@ -235,7 +235,7 @@ set(COMPILE_OPTIONS
-Wundef
-Wuninitialized
-Wunreachable-code
${TEST_OPTIONS}
${BUILD_OPTIONS}
)
get_filename_component(LVGL_DIR ${LVGL_TEST_DIR} DIRECTORY)

View File

@@ -13,16 +13,13 @@ scripts/install-prerequisites.sh
```
### Run test
1. Enter `lvgl/tests/`
2. Run the tests with `./main.py [OPTIONS]`. The options are
- `report` Create a html page in the `report` folder with the coverage report.
- `test` Build and run only test. Without this option LVGL will be built with various configurations.
- `noclean` Do not clean the project before building. Useful while writing test to save some times.
For example:
- `./main.py` Run all the test as they run in the CI.
- `./main.py report test noclean` Run only the test, should be used when writing tests.
1. Run all executable tests with `./tests/main.py test`.
2. Build all build-only tests with `./tests/main.py build`.
3. Clean prior test build, build all build-only tests,
run executable tests, and generate code coverage
report `./tests/main.py --clean --report build test`.
For full information on running tests run: `./tests/main.py --help`.
## Running automatically

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env python3
import argparse
import errno
import glob
import shutil
import subprocess
@@ -9,18 +11,27 @@ import os
lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
# Key values must match variable names in CMakeLists.txt.
options = {
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',
}
# Most test configurations only build - this is the one that also
# executes.
run_tests_option_name = 'OPTIONS_TEST'
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):
@@ -69,29 +80,24 @@ def get_build_dir(options_name):
return os.path.join(lvgl_test_dir, get_base_buid_dir(options_name))
def delete_build_dir(options_name):
'''Recursively delete the build directory for the given options name.'''
delete_dir_ignore_missing(get_build_dir(options_name))
def build_tests(options_name, build_type):
def build_tests(options_name, build_type, clean):
'''Build all tests for the specified options name.'''
global options, lvgl_test_dir, test_noclean
global lvgl_test_dir
print()
print()
label = 'Building: %s: %s' % (options_abbrev(
options_name), options[options_name])
options_name), get_option_description(options_name))
print('=' * len(label))
print(label)
print('=' * len(label))
print(flush=True)
if not test_noclean:
delete_build_dir(options_name)
build_dir = get_build_dir(options_name)
if clean:
delete_dir_ignore_missing(build_dir)
os.chdir(lvgl_test_dir)
build_dir = get_build_dir(options_name)
created_build_dir = False
if not os.path.isdir(build_dir):
os.mkdir(build_dir)
@@ -147,24 +153,55 @@ def generate_code_coverage_report():
print("Done: See %s" % html_report_file, flush=True)
run_test_only = "test" in sys.argv
generate_gcov_report = "report" in sys.argv
test_noclean = "noclean" in sys.argv
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.')
generate_test_runners()
args = parser.parse_args()
for options_name in options.keys():
is_test = options_name == run_tests_option_name
build_type = 'Release' if is_test else 'Debug'
if is_test or not run_test_only:
build_tests(options_name, build_type)
if options_name == run_tests_option_name:
try:
run_tests(options_name)
except subprocess.CalledProcessError as e:
sys.exit(e.returncode)
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)
if generate_gcov_report:
generate_code_coverage_report()
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()