blob: 4b35fc9d0e181c83c85f3787cdccebd21f6fd681 [file] [log] [blame]
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001#!/usr/bin/env python
2
3# devtool stress tester
4#
5# Written by: Paul Eggleton <paul.eggleton@linux.intel.com>
6#
7# Copyright 2015 Intel Corporation
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License version 2 as
11# published by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21#
22
23import sys
24import os
25import os.path
26import subprocess
27import re
28import argparse
29import logging
30import tempfile
31import shutil
32import signal
33import fnmatch
34
35scripts_lib_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'lib'))
36sys.path.insert(0, scripts_lib_path)
37import scriptutils
38logger = scriptutils.logger_create('devtool-stress')
39
40def select_recipes(args):
41 import bb.tinfoil
42 tinfoil = bb.tinfoil.Tinfoil()
43 tinfoil.prepare(False)
44
45 pkg_pn = tinfoil.cooker.recipecache.pkg_pn
46 (latest_versions, preferred_versions) = bb.providers.findProviders(tinfoil.config_data, tinfoil.cooker.recipecache, pkg_pn)
47
48 skip_classes = args.skip_classes.split(',')
49
50 recipelist = []
51 for pn in sorted(pkg_pn):
52 pref = preferred_versions[pn]
53 inherits = [os.path.splitext(os.path.basename(f))[0] for f in tinfoil.cooker.recipecache.inherits[pref[1]]]
54 for cls in skip_classes:
55 if cls in inherits:
56 break
57 else:
58 recipelist.append(pn)
59
60 tinfoil.shutdown()
61
62 resume_from = args.resume_from
63 if resume_from:
64 if not resume_from in recipelist:
65 print('%s is not a testable recipe' % resume_from)
66 return 1
67 if args.only:
68 only = args.only.split(',')
69 for onlyitem in only:
70 for pn in recipelist:
71 if fnmatch.fnmatch(pn, onlyitem):
72 break
73 else:
74 print('%s does not match any testable recipe' % onlyitem)
75 return 1
76 else:
77 only = None
78 if args.skip:
79 skip = args.skip.split(',')
80 else:
81 skip = []
82
83 recipes = []
84 for pn in recipelist:
85 if resume_from:
86 if pn == resume_from:
87 resume_from = None
88 else:
89 continue
90
91 if args.only:
92 for item in only:
93 if fnmatch.fnmatch(pn, item):
94 break
95 else:
96 continue
97
98 skipit = False
99 for item in skip:
100 if fnmatch.fnmatch(pn, item):
101 skipit = True
102 if skipit:
103 continue
104
105 recipes.append(pn)
106
107 return recipes
108
109
110def stress_extract(args):
111 import bb.process
112
113 recipes = select_recipes(args)
114
115 failures = 0
116 tmpdir = tempfile.mkdtemp()
117 os.setpgrp()
118 try:
119 for pn in recipes:
120 sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.'))
121 sys.stdout.flush()
122 failed = False
123
124 srctree = os.path.join(tmpdir, pn)
125 try:
126 bb.process.run('devtool extract %s %s' % (pn, srctree))
127 except bb.process.CmdError as exc:
128 failed = True
129 with open('stress_%s_extract.log' % pn, 'w') as f:
130 f.write(str(exc))
131
132 if os.path.exists(srctree):
133 shutil.rmtree(srctree)
134
135 if failed:
136 print('failed')
137 failures += 1
138 else:
139 print('ok')
140 except KeyboardInterrupt:
141 # We want any child processes killed. This is crude, but effective.
142 os.killpg(0, signal.SIGTERM)
143
144 if failures:
145 return 1
146 else:
147 return 0
148
149
150def stress_modify(args):
151 import bb.process
152
153 recipes = select_recipes(args)
154
155 failures = 0
156 tmpdir = tempfile.mkdtemp()
157 os.setpgrp()
158 try:
159 for pn in recipes:
160 sys.stdout.write('Testing %s ' % (pn + ' ').ljust(40, '.'))
161 sys.stdout.flush()
162 failed = False
163 reset = True
164
165 srctree = os.path.join(tmpdir, pn)
166 try:
167 bb.process.run('devtool modify -x %s %s' % (pn, srctree))
168 except bb.process.CmdError as exc:
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 failed:
175 try:
176 bb.process.run('bitbake -c install %s' % pn)
177 except bb.process.CmdError as exc:
178 with open('stress_%s_install.log' % pn, 'w') as f:
179 f.write(str(exc))
180 failed = 'build'
181 if reset:
182 try:
183 bb.process.run('devtool reset %s' % pn)
184 except bb.process.CmdError as exc:
185 print('devtool reset failed: %s' % str(exc))
186 break
187
188 if os.path.exists(srctree):
189 shutil.rmtree(srctree)
190
191 if failed:
192 print('failed (%s)' % failed)
193 failures += 1
194 else:
195 print('ok')
196 except KeyboardInterrupt:
197 # We want any child processes killed. This is crude, but effective.
198 os.killpg(0, signal.SIGTERM)
199
200 if failures:
201 return 1
202 else:
203 return 0
204
205
206def main():
207 parser = argparse.ArgumentParser(description="devtool stress tester",
208 epilog="Use %(prog)s <subcommand> --help to get help on a specific command")
209 parser.add_argument('-d', '--debug', help='Enable debug output', action='store_true')
210 parser.add_argument('-r', '--resume-from', help='Resume from specified recipe', metavar='PN')
211 parser.add_argument('-o', '--only', help='Only test specified recipes (comma-separated without spaces, wildcards allowed)', metavar='PNLIST')
212 parser.add_argument('-s', '--skip', help='Skip specified recipes (comma-separated without spaces, wildcards allowed)', metavar='PNLIST')
213 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')
214 subparsers = parser.add_subparsers(title='subcommands', metavar='<subcommand>')
215
216 parser_modify = subparsers.add_parser('modify',
217 help='Run "devtool modify" followed by a build with bitbake on matching recipes',
218 description='Runs "devtool modify" followed by a build with bitbake on matching recipes')
219 parser_modify.set_defaults(func=stress_modify)
220
221 parser_extract = subparsers.add_parser('extract',
222 help='Run "devtool extract" on matching recipes',
223 description='Runs "devtool extract" on matching recipes')
224 parser_extract.set_defaults(func=stress_extract)
225
226 args = parser.parse_args()
227
228 if args.debug:
229 logger.setLevel(logging.DEBUG)
230
231 import scriptpath
232 bitbakepath = scriptpath.add_bitbake_lib_path()
233 if not bitbakepath:
234 logger.error("Unable to find bitbake by searching parent directory of this script or PATH")
235 return 1
236 logger.debug('Found bitbake path: %s' % bitbakepath)
237
238 ret = args.func(args)
239
240if __name__ == "__main__":
241 main()