summaryrefslogtreecommitdiff
path: root/scripts/contrib/devtool-stress.py
blob: d555c51a650b748ec483898c50d9314e75b5d6bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/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()