1#!/usr/bin/env python3 2 3# devtool stress tester 4# 5# Written by: Paul Eggleton <paul.eggleton@linux.intel.com> 6# 7# Copyright 2015 Intel Corporation 8# 9# SPDX-License-Identifier: GPL-2.0-only 10# 11 12import sys 13import os 14import os.path 15import subprocess 16import re 17import argparse 18import logging 19import tempfile 20import shutil 21import signal 22import fnmatch 23 24scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib')) 25sys.path.insert(0, scripts_lib_path) 26import scriptutils 27import argparse_oe 28logger = scriptutils.logger_create('devtool-stress') 29 30def select_recipes(args): 31 import bb.tinfoil 32 tinfoil = bb.tinfoil.Tinfoil() 33 tinfoil.prepare(False) 34 35 pkg_pn = tinfoil.cooker.recipecaches[''].pkg_pn 36 (latest_versions, preferred_versions) = bb.providers.findProviders(tinfoil.config_data, tinfoil.cooker.recipecaches[''], pkg_pn) 37 38 skip_classes = args.skip_classes.split(',') 39 40 recipelist = [] 41 for pn in sorted(pkg_pn): 42 pref = preferred_versions[pn] 43 inherits = [os.path.splitext(os.path.basename(f))[0] for f in tinfoil.cooker.recipecaches[''].inherits[pref[1]]] 44 for cls in skip_classes: 45 if cls in inherits: 46 break 47 else: 48 recipelist.append(pn) 49 50 tinfoil.shutdown() 51 52 resume_from = args.resume_from 53 if resume_from: 54 if not resume_from in recipelist: 55 print('%s is not a testable recipe' % resume_from) 56 return 1 57 if args.only: 58 only = args.only.split(',') 59 for onlyitem in only: 60 for pn in recipelist: 61 if fnmatch.fnmatch(pn, onlyitem): 62 break 63 else: 64 print('%s does not match any testable recipe' % onlyitem) 65 return 1 66 else: 67 only = None 68 if args.skip: 69 skip = args.skip.split(',') 70 else: 71 skip = [] 72 73 recipes = [] 74 for pn in recipelist: 75 if resume_from: 76 if pn == resume_from: 77 resume_from = None 78 else: 79 continue 80 81 if args.only: 82 for item in only: 83 if fnmatch.fnmatch(pn, item): 84 break 85 else: 86 continue 87 88 skipit = False 89 for item in skip: 90 if fnmatch.fnmatch(pn, item): 91 skipit = True 92 if skipit: 93 continue 94 95 recipes.append(pn) 96 97 return recipes 98 99 100def stress_extract(args): 101 import bb.process 102 103 recipes = select_recipes(args) 104 105 failures = 0 106 tmpdir = tempfile.mkdtemp() 107 os.setpgrp() 108 try: 109 for pn in recipes: 110 sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.')) 111 sys.stdout.flush() 112 failed = False 113 skipped = None 114 115 srctree = os.path.join(tmpdir, pn) 116 try: 117 bb.process.run('devtool extract %s %s' % (pn, srctree)) 118 except bb.process.ExecutionError as exc: 119 if exc.exitcode == 4: 120 skipped = 'incompatible' 121 else: 122 failed = True 123 with open('stress_%s_extract.log' % pn, 'w') as f: 124 f.write(str(exc)) 125 126 if os.path.exists(srctree): 127 shutil.rmtree(srctree) 128 129 if failed: 130 print('failed') 131 failures += 1 132 elif skipped: 133 print('skipped (%s)' % skipped) 134 else: 135 print('ok') 136 except KeyboardInterrupt: 137 # We want any child processes killed. This is crude, but effective. 138 os.killpg(0, signal.SIGTERM) 139 140 if failures: 141 return 1 142 else: 143 return 0 144 145 146def stress_modify(args): 147 import bb.process 148 149 recipes = select_recipes(args) 150 151 failures = 0 152 tmpdir = tempfile.mkdtemp() 153 os.setpgrp() 154 try: 155 for pn in recipes: 156 sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.')) 157 sys.stdout.flush() 158 failed = False 159 reset = True 160 skipped = None 161 162 srctree = os.path.join(tmpdir, pn) 163 try: 164 bb.process.run('devtool modify -x %s %s' % (pn, srctree)) 165 except bb.process.ExecutionError as exc: 166 if exc.exitcode == 4: 167 skipped = 'incompatible' 168 else: 169 with open('stress_%s_modify.log' % pn, 'w') as f: 170 f.write(str(exc)) 171 failed = 'modify' 172 reset = False 173 174 if not skipped: 175 if not failed: 176 try: 177 bb.process.run('bitbake -c install %s' % pn) 178 except bb.process.CmdError as exc: 179 with open('stress_%s_install.log' % pn, 'w') as f: 180 f.write(str(exc)) 181 failed = 'build' 182 if reset: 183 try: 184 bb.process.run('devtool reset %s' % pn) 185 except bb.process.CmdError as exc: 186 print('devtool reset failed: %s' % str(exc)) 187 break 188 189 if os.path.exists(srctree): 190 shutil.rmtree(srctree) 191 192 if failed: 193 print('failed (%s)' % failed) 194 failures += 1 195 elif skipped: 196 print('skipped (%s)' % skipped) 197 else: 198 print('ok') 199 except KeyboardInterrupt: 200 # We want any child processes killed. This is crude, but effective. 201 os.killpg(0, signal.SIGTERM) 202 203 if failures: 204 return 1 205 else: 206 return 0 207 208 209def main(): 210 parser = argparse_oe.ArgumentParser(description="devtool stress tester", 211 epilog="Use %(prog)s <subcommand> --help to get help on a specific command") 212 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') 213 parser.add_argument('-r', '--resume-from', help='Resume from specified recipe', metavar='PN') 214 parser.add_argument('-o', '--only', help='Only test specified recipes (comma-separated without spaces, wildcards allowed)', metavar='PNLIST') 215 parser.add_argument('-s', '--skip', help='Skip specified recipes (comma-separated without spaces, wildcards allowed)', metavar='PNLIST', default='gcc-source-*,kernel-devsrc,package-index,perf,meta-world-pkgdata,glibc-locale,glibc-mtrace,glibc-scripts,os-release') 216 parser.add_argument('-c', '--skip-classes', help='Skip recipes inheriting specified classes (comma-separated) - default %(default)s', metavar='CLASSLIST', default='native,nativesdk,cross,cross-canadian,image,populate_sdk,meta,packagegroup') 217 subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') 218 subparsers.required = True 219 220 parser_modify = subparsers.add_parser('modify', 221 help='Run "devtool modify" followed by a build with bitbake on matching recipes', 222 description='Runs "devtool modify" followed by a build with bitbake on matching recipes') 223 parser_modify.set_defaults(func=stress_modify) 224 225 parser_extract = subparsers.add_parser('extract', 226 help='Run "devtool extract" on matching recipes', 227 description='Runs "devtool extract" on matching recipes') 228 parser_extract.set_defaults(func=stress_extract) 229 230 args = parser.parse_args() 231 232 if args.debug: 233 logger.setLevel(logging.DEBUG) 234 235 import scriptpath 236 bitbakepath = scriptpath.add_bitbake_lib_path() 237 if not bitbakepath: 238 logger.error("Unable to find bitbake by searching parent directory of this script or PATH") 239 return 1 240 logger.debug('Found bitbake path: %s' % bitbakepath) 241 242 ret = args.func(args) 243 244if __name__ == "__main__": 245 main() 246