#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2020, Google Inc. # # Author: Paul Elder # # Test the V4L2 compatibility layer import argparse import glob import os from packaging import version import re import shutil import signal import subprocess import sys MIN_V4L_UTILS_VERSION = version.parse("1.21.0") TestPass = 0 TestFail = -1 TestSkip = 77 supported_pipelines = [ 'bcm2835-isp', 'uvcvideo', 'vimc', ] def grep(exp, arr): return [s for s in arr if re.search(exp, s)] def run_with_stdout(*args, env={}): try: with open(os.devnull, 'w') as devnull: output = subprocess.check_output(args, env=env, stderr=devnull) ret = 0 except subprocess.CalledProcessError as err: output = err.output ret = err.returncode return ret, output.decode('utf-8').split('\n') def extract_result(result): res = result.split(', ') ret = {} ret['total'] = int(res[0].split(': ')[-1]) ret['succeeded'] = int(res[1].split(': ')[-1]) ret['failed'] = int(res[2].split(': ')[-1]) ret['warnings'] = int(res[3].split(': ')[-1]) ret['device'] = res[0].split()[4].strip(':') ret['driver'] = res[0].split()[2] return ret def test_v4l2_compliance(v4l2_compliance, ld_preload, device, base_driver): ret, output = run_with_stdout(v4l2_compliance, '-s', '-d', device, env={'LD_PRELOAD': ld_preload}) if ret < 0: output.append(f'Test for {device} terminated due to signal {signal.Signals(-ret).name}') return TestFail, output result = extract_result(output[-2]) if result['failed'] == 0: return TestPass, output # vimc will fail s_fmt because it only supports framesizes that are # multiples of 3 if base_driver == 'vimc' and result['failed'] == 1: failures = grep('fail', output) if re.search('S_FMT cannot handle an invalid format', failures[0]) is None: return TestFail, output return TestPass, output return TestFail, output def main(argv): parser = argparse.ArgumentParser() parser.add_argument('-a', '--all', action='store_true', help='Test all available cameras') parser.add_argument('-s', '--sanitizer', type=str, help='Path to the address sanitizer (ASan) runtime') parser.add_argument('-v', '--verbose', action='store_true', help='Make the output verbose') parser.add_argument('v4l2_compat', type=str, help='Path to v4l2-compat.so') args = parser.parse_args(argv[1:]) # Compute the LD_PRELOAD value by first loading ASan (if specified) and # then the V4L2 compat layer. ld_preload = [] if args.sanitizer: ld_preload.append(args.sanitizer) ld_preload.append(args.v4l2_compat) ld_preload = ':'.join(ld_preload) v4l2_compliance = shutil.which('v4l2-compliance') if v4l2_compliance is None: print('v4l2-compliance is not available') return TestSkip ret, out = run_with_stdout(v4l2_compliance, '--version') if ret != 0 or version.parse(out[0].split()[1].replace(',', '')) < MIN_V4L_UTILS_VERSION: print('v4l2-compliance version >= 1.21.0 required') return TestSkip v4l2_ctl = shutil.which('v4l2-ctl') if v4l2_ctl is None: print('v4l2-ctl is not available') return TestSkip ret, out = run_with_stdout(v4l2_ctl, '--version') if ret != 0 or version.parse(out[0].split()[-1]) < MIN_V4L_UTILS_VERSION: print('v4l2-ctl version >= 1.21.0 required') return TestSkip dev_nodes = glob.glob('/dev/video*') if len(dev_nodes) == 0: print('no video nodes available to test with') return TestSkip failed = [] drivers_tested = {} for device in dev_nodes: ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device, env={'LD_PRELOAD': ld_preload}) if ret < 0: failed.append(device) print(f'v4l2-ctl failed on {device} with v4l2-compat') continue driver = grep('Driver name', out)[0].split(':')[-1].strip() if driver != "libcamera": continue ret, out = run_with_stdout(v4l2_ctl, '-D', '-d', device) if ret < 0: failed.append(device) print(f'v4l2-ctl failed on {device} without v4l2-compat') continue driver = grep('Driver name', out)[0].split(':')[-1].strip() if driver not in supported_pipelines: continue # TODO: Add kernel version check when vimc supports scaling if driver == "vimc": continue if not args.all and driver in drivers_tested: continue print(f'Testing {device} with {driver} driver... ', end='') ret, msg = test_v4l2_compliance(v4l2_compliance, ld_preload, device, driver) if ret == TestFail: failed.append(device) print('failed') else: print('success') if ret == TestFail or args.verbose: print('\n'.join(msg)) drivers_tested[driver] = True if len(drivers_tested) == 0: print(f'No compatible drivers found') return TestSkip if len(failed) > 0: print(f'Failed {len(failed)} tests:') for device in failed: print(f'- {device}') return TestPass if not failed else TestFail if __name__ == '__main__': sys.exit(main(sys.argv)) 1' href='#n71'>71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162