| #!/usr/bin/env python3 |
| |
| # devtool stress tester |
| # |
| # Written by: Paul Eggleton <paul.eggleton@linux.intel.com> |
| # |
| # Copyright 2015 Intel Corporation |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License version 2 as |
| # published by the Free Software Foundation. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License along |
| # with this program; if not, write to the Free Software Foundation, Inc., |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| # |
| |
| import sys |
| import os |
| import os.path |
| import subprocess |
| import re |
| import argparse |
| import logging |
| import tempfile |
| import shutil |
| import signal |
| import fnmatch |
| |
| scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib')) |
| sys.path.insert(0, scripts_lib_path) |
| import scriptutils |
| import argparse_oe |
| logger = scriptutils.logger_create('devtool-stress') |
| |
| def select_recipes(args): |
| import bb.tinfoil |
| tinfoil = bb.tinfoil.Tinfoil() |
| tinfoil.prepare(False) |
| |
| pkg_pn = tinfoil.cooker.recipecaches[''].pkg_pn |
| (latest_versions, preferred_versions) = bb.providers.findProviders(tinfoil.config_data, tinfoil.cooker.recipecaches[''], pkg_pn) |
| |
| skip_classes = args.skip_classes.split(',') |
| |
| recipelist = [] |
| for pn in sorted(pkg_pn): |
| pref = preferred_versions[pn] |
| inherits = [os.path.splitext(os.path.basename(f))[0] for f in tinfoil.cooker.recipecaches[''].inherits[pref[1]]] |
| for cls in skip_classes: |
| if cls in inherits: |
| break |
| else: |
| recipelist.append(pn) |
| |
| tinfoil.shutdown() |
| |
| resume_from = args.resume_from |
| if resume_from: |
| if not resume_from in recipelist: |
| print('%s is not a testable recipe' % resume_from) |
| return 1 |
| if args.only: |
| only = args.only.split(',') |
| for onlyitem in only: |
| for pn in recipelist: |
| if fnmatch.fnmatch(pn, onlyitem): |
| break |
| else: |
| print('%s does not match any testable recipe' % onlyitem) |
| return 1 |
| else: |
| only = None |
| if args.skip: |
| skip = args.skip.split(',') |
| else: |
| skip = [] |
| |
| recipes = [] |
| for pn in recipelist: |
| if resume_from: |
| if pn == resume_from: |
| resume_from = None |
| else: |
| continue |
| |
| if args.only: |
| for item in only: |
| if fnmatch.fnmatch(pn, item): |
| break |
| else: |
| continue |
| |
| skipit = False |
| for item in skip: |
| if fnmatch.fnmatch(pn, item): |
| skipit = True |
| if skipit: |
| continue |
| |
| recipes.append(pn) |
| |
| return recipes |
| |
| |
| def stress_extract(args): |
| import bb.process |
| |
| recipes = select_recipes(args) |
| |
| failures = 0 |
| tmpdir = tempfile.mkdtemp() |
| os.setpgrp() |
| try: |
| for pn in recipes: |
| sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.')) |
| sys.stdout.flush() |
| failed = False |
| skipped = None |
| |
| srctree = os.path.join(tmpdir, pn) |
| try: |
| bb.process.run('devtool extract %s %s' % (pn, srctree)) |
| except bb.process.ExecutionError as exc: |
| if exc.exitcode == 4: |
| skipped = 'incompatible' |
| else: |
| failed = True |
| with open('stress_%s_extract.log' % pn, 'w') as f: |
| f.write(str(exc)) |
| |
| if os.path.exists(srctree): |
| shutil.rmtree(srctree) |
| |
| if failed: |
| print('failed') |
| failures += 1 |
| elif skipped: |
| print('skipped (%s)' % skipped) |
| else: |
| print('ok') |
| except KeyboardInterrupt: |
| # We want any child processes killed. This is crude, but effective. |
| os.killpg(0, signal.SIGTERM) |
| |
| if failures: |
| return 1 |
| else: |
| return 0 |
| |
| |
| def stress_modify(args): |
| import bb.process |
| |
| recipes = select_recipes(args) |
| |
| failures = 0 |
| tmpdir = tempfile.mkdtemp() |
| os.setpgrp() |
| try: |
| for pn in recipes: |
| sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.')) |
| sys.stdout.flush() |
| failed = False |
| reset = True |
| skipped = None |
| |
| srctree = os.path.join(tmpdir, pn) |
| try: |
| bb.process.run('devtool modify -x %s %s' % (pn, srctree)) |
| except bb.process.ExecutionError as exc: |
| if exc.exitcode == 4: |
| skipped = 'incompatible' |
| else: |
| with open('stress_%s_modify.log' % pn, 'w') as f: |
| f.write(str(exc)) |
| failed = 'modify' |
| reset = False |
| |
| if not skipped: |
| if not failed: |
| try: |
| bb.process.run('bitbake -c install %s' % pn) |
| except bb.process.CmdError as exc: |
| with open('stress_%s_install.log' % pn, 'w') as f: |
| f.write(str(exc)) |
| failed = 'build' |
| if reset: |
| try: |
| bb.process.run('devtool reset %s' % pn) |
| except bb.process.CmdError as exc: |
| print('devtool reset failed: %s' % str(exc)) |
| break |
| |
| if os.path.exists(srctree): |
| shutil.rmtree(srctree) |
| |
| if failed: |
| print('failed (%s)' % failed) |
| failures += 1 |
| elif skipped: |
| print('skipped (%s)' % skipped) |
| else: |
| print('ok') |
| except KeyboardInterrupt: |
| # We want any child processes killed. This is crude, but effective. |
| os.killpg(0, signal.SIGTERM) |
| |
| if failures: |
| return 1 |
| else: |
| return 0 |
| |
| |
| def main(): |
| parser = argparse_oe.ArgumentParser(description="devtool stress tester", |
| epilog="Use %(prog)s <subcommand> --help to get help on a specific command") |
| parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true') |
| parser.add_argument('-r', '--resume-from', help='Resume from specified recipe', metavar='PN') |
| parser.add_argument('-o', '--only', help='Only test specified recipes (comma-separated without spaces, wildcards allowed)', metavar='PNLIST') |
| 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') |
| 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') |
| subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>') |
| subparsers.required = True |
| |
| parser_modify = subparsers.add_parser('modify', |
| help='Run "devtool modify" followed by a build with bitbake on matching recipes', |
| description='Runs "devtool modify" followed by a build with bitbake on matching recipes') |
| parser_modify.set_defaults(func=stress_modify) |
| |
| parser_extract = subparsers.add_parser('extract', |
| help='Run "devtool extract" on matching recipes', |
| description='Runs "devtool extract" on matching recipes') |
| parser_extract.set_defaults(func=stress_extract) |
| |
| args = parser.parse_args() |
| |
| if args.debug: |
| logger.setLevel(logging.DEBUG) |
| |
| import scriptpath |
| bitbakepath = scriptpath.add_bitbake_lib_path() |
| if not bitbakepath: |
| logger.error("Unable to find bitbake by searching parent directory of this script or PATH") |
| return 1 |
| logger.debug('Found bitbake path: %s' % bitbakepath) |
| |
| ret = args.func(args) |
| |
| if __name__ == "__main__": |
| main() |