acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

main.py (6985B)

      1 #!/usr/bin/env python3
      2 
      3 import argparse
      4 import errno
      5 import glob
      6 import shutil
      7 import subprocess
      8 import sys
      9 import os
     10 
     11 lvgl_test_dir = os.path.dirname(os.path.realpath(__file__))
     12 
     13 # Key values must match variable names in CMakeLists.txt.
     14 build_only_options = {
     15     'OPTIONS_MINIMAL_MONOCHROME': 'Minimal config monochrome',
     16     'OPTIONS_NORMAL_8BIT': 'Normal config, 8 bit color depth',
     17     'OPTIONS_16BIT': 'Minimal config, 16 bit color depth',
     18     'OPTIONS_16BIT_SWAP': 'Normal config, 16 bit color depth swapped',
     19     'OPTIONS_FULL_32BIT': 'Full config, 32 bit color depth',
     20 }
     21 
     22 test_options = {
     23     'OPTIONS_TEST_SYSHEAP': 'Test config, system heap, 32 bit color depth',
     24     'OPTIONS_TEST_DEFHEAP': 'Test config, LVGL heap, 32 bit color depth',
     25 }
     26 
     27 
     28 def is_valid_option_name(option_name):
     29     return option_name in build_only_options or option_name in test_options
     30 
     31 
     32 def get_option_description(option_name):
     33     if option_name in build_only_options:
     34         return build_only_options[option_name]
     35     return test_options[option_name]
     36 
     37 
     38 def delete_dir_ignore_missing(dir_path):
     39     '''Recursively delete a directory and ignore if missing.'''
     40     try:
     41         shutil.rmtree(dir_path)
     42     except FileNotFoundError:
     43         pass
     44 
     45 
     46 def generate_test_runners():
     47     '''Generate the test runner source code.'''
     48     global lvgl_test_dir
     49     os.chdir(lvgl_test_dir)
     50     delete_dir_ignore_missing('src/test_runners')
     51     os.mkdir('src/test_runners')
     52 
     53     # TODO: Intermediate files should be in the build folders, not alongside
     54     #       the other repo source.
     55     for f in glob.glob("./src/test_cases/test_*.c"):
     56         r = f[:-2] + "_Runner.c"
     57         r = r.replace("/test_cases/", "/test_runners/")
     58         subprocess.check_call(['ruby', 'unity/generate_test_runner.rb',
     59                                f, r, 'config.yml'])
     60 
     61 
     62 def options_abbrev(options_name):
     63     '''Return an abbreviated version of the option name.'''
     64     prefix = 'OPTIONS_'
     65     assert options_name.startswith(prefix)
     66     return options_name[len(prefix):].lower()
     67 
     68 
     69 def get_base_buid_dir(options_name):
     70     '''Given the build options name, return the build directory name.
     71 
     72     Does not return the full path to the directory - just the base name.'''
     73     return 'build_%s' % options_abbrev(options_name)
     74 
     75 
     76 def get_build_dir(options_name):
     77     '''Given the build options name, return the build directory name.
     78 
     79     Returns absolute path to the build directory.'''
     80     global lvgl_test_dir
     81     return os.path.join(lvgl_test_dir, get_base_buid_dir(options_name))
     82 
     83 
     84 def build_tests(options_name, build_type, clean):
     85     '''Build all tests for the specified options name.'''
     86     global lvgl_test_dir
     87 
     88     print()
     89     print()
     90     label = 'Building: %s: %s' % (options_abbrev(
     91         options_name), get_option_description(options_name))
     92     print('=' * len(label))
     93     print(label)
     94     print('=' * len(label))
     95     print(flush=True)
     96 
     97     build_dir = get_build_dir(options_name)
     98     if clean:
     99         delete_dir_ignore_missing(build_dir)
    100 
    101     os.chdir(lvgl_test_dir)
    102     created_build_dir = False
    103     if not os.path.isdir(build_dir):
    104         os.mkdir(build_dir)
    105         created_build_dir = True
    106     os.chdir(build_dir)
    107     if created_build_dir:
    108         subprocess.check_call(['cmake', '-DCMAKE_BUILD_TYPE=%s' % build_type,
    109                                '-D%s=1' % options_name, '..'])
    110     subprocess.check_call(['cmake', '--build', build_dir,
    111                            '--parallel', str(os.cpu_count())])
    112 
    113 
    114 def run_tests(options_name):
    115     '''Run the tests for the given options name.'''
    116 
    117     print()
    118     print()
    119     label = 'Running tests for %s' % options_abbrev(options_name)
    120     print('=' * len(label))
    121     print(label)
    122     print('=' * len(label), flush=True)
    123 
    124     os.chdir(get_build_dir(options_name))
    125     subprocess.check_call(
    126         ['ctest', '--parallel', str(os.cpu_count()), '--output-on-failure'])
    127 
    128 
    129 def generate_code_coverage_report():
    130     '''Produce code coverage test reports for the test execution.'''
    131     global lvgl_test_dir
    132 
    133     print()
    134     print()
    135     label = 'Generating code coverage reports'
    136     print('=' * len(label))
    137     print(label)
    138     print('=' * len(label))
    139     print(flush=True)
    140 
    141     os.chdir(lvgl_test_dir)
    142     delete_dir_ignore_missing('report')
    143     os.mkdir('report')
    144     root_dir = os.pardir
    145     html_report_file = 'report/index.html'
    146     cmd = ['gcovr', '--root', root_dir, '--html-details', '--output',
    147            html_report_file, '--xml', 'report/coverage.xml',
    148            '-j', str(os.cpu_count()), '--print-summary',
    149            '--html-title', 'LVGL Test Coverage']
    150     for d in ('.*\\bexamples/.*', '\\bsrc/test_.*', '\\bsrc/lv_test.*', '\\bunity\\b'):
    151         cmd.extend(['--exclude', d])
    152 
    153     subprocess.check_call(cmd)
    154     print("Done: See %s" % html_report_file, flush=True)
    155 
    156 
    157 if __name__ == "__main__":
    158     epilog = '''This program builds and optionally runs the LVGL test programs.
    159     There are two types of LVGL tests: "build", and "test". The build-only
    160     tests, as their name suggests, only verify that the program successfully
    161     compiles and links (with various build options). There are also a set of
    162     tests that execute to verify correct LVGL library behavior.
    163     '''
    164     parser = argparse.ArgumentParser(
    165         description='Build and/or run LVGL tests.', epilog=epilog)
    166     parser.add_argument('--build-options', nargs=1,
    167                         help='''the build option name to build or run. When
    168                         omitted all build configurations are used.
    169                         ''')
    170     parser.add_argument('--clean', action='store_true', default=False,
    171                         help='clean existing build artifacts before operation.')
    172     parser.add_argument('--report', action='store_true',
    173                         help='generate code coverage report for tests.')
    174     parser.add_argument('actions', nargs='*', choices=['build', 'test'],
    175                         help='build: compile build tests, test: compile/run executable tests.')
    176 
    177     args = parser.parse_args()
    178 
    179     if args.build_options:
    180         options_to_build = args.build_options
    181     else:
    182         if 'build' in args.actions:
    183             if 'test' in args.actions:
    184                 options_to_build = {**build_only_options, **test_options}
    185             else:
    186                 options_to_build = build_only_options
    187         else:
    188             options_to_build = test_options
    189 
    190     for opt in options_to_build:
    191         if not is_valid_option_name(opt):
    192             print('Invalid build option "%s"' % opt, file=sys.stderr)
    193             sys.exit(errno.EINVAL)
    194 
    195     generate_test_runners()
    196 
    197     for options_name in options_to_build:
    198         is_test = options_name in test_options
    199         build_type = 'Debug'
    200         build_tests(options_name, build_type, args.clean)
    201         if is_test:
    202             try:
    203                 run_tests(options_name)
    204             except subprocess.CalledProcessError as e:
    205                 sys.exit(e.returncode)
    206 
    207     if args.report:
    208         generate_code_coverage_report()