blob: d9ffd40e8c4caa734cd490d77304cc600cc75b73 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#!/usr/bin/env python3
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002
3# Copyright (c) 2013 Intel Corporation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18# DESCRIPTION
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050019# This script runs tests defined in meta/lib/oeqa/selftest/
Patrick Williamsc124f4f2015-09-15 14:41:29 -050020# It's purpose is to automate the testing of different bitbake tools.
21# To use it you just need to source your build environment setup script and
22# add the meta-selftest layer to your BBLAYERS.
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050023# Call the script as: "oe-selftest -a" to run all the tests in meta/lib/oeqa/selftest/
24# Call the script as: "oe-selftest -r <module>.<Class>.<method>" to run just a single test
25# E.g: "oe-selftest -r bblayers.BitbakeLayers" will run just the BitbakeLayers class from meta/lib/oeqa/selftest/bblayers.py
Patrick Williamsc124f4f2015-09-15 14:41:29 -050026
27
28import os
29import sys
30import unittest
31import logging
32import argparse
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050033import subprocess
34import time as t
35import re
36import fnmatch
Patrick Williamsc0f7c042017-02-23 20:41:17 -060037import collections
38import imp
Patrick Williamsc124f4f2015-09-15 14:41:29 -050039
40sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)) + '/lib')
41import scriptpath
42scriptpath.add_bitbake_lib_path()
43scriptpath.add_oe_lib_path()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050044import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050045
46import oeqa.selftest
47import oeqa.utils.ftools as ftools
48from oeqa.utils.commands import runCmd, get_bb_var, get_test_layer
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050049from oeqa.selftest.base import oeSelfTest, get_available_machines
Patrick Williamsc124f4f2015-09-15 14:41:29 -050050
Patrick Williamsc0f7c042017-02-23 20:41:17 -060051try:
52 import xmlrunner
53 from xmlrunner.result import _XMLTestResult as TestResult
54 from xmlrunner import XMLTestRunner as _TestRunner
55except ImportError:
56 # use the base runner instead
57 from unittest import TextTestResult as TestResult
58 from unittest import TextTestRunner as _TestRunner
59
60log_prefix = "oe-selftest-" + t.strftime("%Y%m%d-%H%M%S")
61
Patrick Williamsc124f4f2015-09-15 14:41:29 -050062def logger_create():
Patrick Williamsc0f7c042017-02-23 20:41:17 -060063 log_file = log_prefix + ".log"
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050064 if os.path.exists("oe-selftest.log"): os.remove("oe-selftest.log")
65 os.symlink(log_file, "oe-selftest.log")
66
Patrick Williamsc124f4f2015-09-15 14:41:29 -050067 log = logging.getLogger("selftest")
68 log.setLevel(logging.DEBUG)
69
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050070 fh = logging.FileHandler(filename=log_file, mode='w')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050071 fh.setLevel(logging.DEBUG)
72
73 ch = logging.StreamHandler(sys.stdout)
74 ch.setLevel(logging.INFO)
75
76 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
77 fh.setFormatter(formatter)
78 ch.setFormatter(formatter)
79
80 log.addHandler(fh)
81 log.addHandler(ch)
82
83 return log
84
85log = logger_create()
86
87def get_args_parser():
88 description = "Script that runs unit tests agains bitbake and other Yocto related tools. The goal is to validate tools functionality and metadata integrity. Refer to https://wiki.yoctoproject.org/wiki/Oe-selftest for more information."
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050089 parser = argparse_oe.ArgumentParser(description=description)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090 group = parser.add_mutually_exclusive_group(required=True)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050091 group.add_argument('-r', '--run-tests', required=False, action='store', nargs='*', dest="run_tests", default=None, help='Select what tests to run (modules, classes or test methods). Format should be: <module>.<class>.<test_method>')
92 group.add_argument('-a', '--run-all-tests', required=False, action="store_true", dest="run_all_tests", default=False, help='Run all (unhidden) tests')
93 group.add_argument('-m', '--list-modules', required=False, action="store_true", dest="list_modules", default=False, help='List all available test modules.')
Patrick Williamsc124f4f2015-09-15 14:41:29 -050094 group.add_argument('--list-classes', required=False, action="store_true", dest="list_allclasses", default=False, help='List all available test classes.')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050095 parser.add_argument('--coverage', action="store_true", help="Run code coverage when testing")
96 parser.add_argument('--coverage-source', dest="coverage_source", nargs="+", help="Specifiy the directories to take coverage from")
97 parser.add_argument('--coverage-include', dest="coverage_include", nargs="+", help="Specify extra patterns to include into the coverage measurement")
98 parser.add_argument('--coverage-omit', dest="coverage_omit", nargs="+", help="Specify with extra patterns to exclude from the coverage measurement")
99 group.add_argument('--run-tests-by', required=False, dest='run_tests_by', default=False, nargs='*',
100 help='run-tests-by <name|class|module|id|tag> <list of tests|classes|modules|ids|tags>')
101 group.add_argument('--list-tests-by', required=False, dest='list_tests_by', default=False, nargs='*',
102 help='list-tests-by <name|class|module|id|tag> <list of tests|classes|modules|ids|tags>')
103 group.add_argument('-l', '--list-tests', required=False, action="store_true", dest="list_tests", default=False,
104 help='List all available tests.')
105 group.add_argument('--list-tags', required=False, dest='list_tags', default=False, action="store_true",
106 help='List all tags that have been set to test cases.')
107 parser.add_argument('--machine', required=False, dest='machine', choices=['random', 'all'], default=None,
108 help='Run tests on different machines (random/all).')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500109 return parser
110
111
112def preflight_check():
113
114 log.info("Checking that everything is in order before running the tests")
115
116 if not os.environ.get("BUILDDIR"):
117 log.error("BUILDDIR isn't set. Did you forget to source your build environment setup script?")
118 return False
119
120 builddir = os.environ.get("BUILDDIR")
121 if os.getcwd() != builddir:
122 log.info("Changing cwd to %s" % builddir)
123 os.chdir(builddir)
124
125 if not "meta-selftest" in get_bb_var("BBLAYERS"):
126 log.error("You don't seem to have the meta-selftest layer in BBLAYERS")
127 return False
128
129 log.info("Running bitbake -p")
130 runCmd("bitbake -p")
131
132 return True
133
134def add_include():
135 builddir = os.environ.get("BUILDDIR")
136 if "#include added by oe-selftest.py" \
137 not in ftools.read_file(os.path.join(builddir, "conf/local.conf")):
138 log.info("Adding: \"include selftest.inc\" in local.conf")
139 ftools.append_file(os.path.join(builddir, "conf/local.conf"), \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500140 "\n#include added by oe-selftest.py\ninclude machine.inc\ninclude selftest.inc")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141
142 if "#include added by oe-selftest.py" \
143 not in ftools.read_file(os.path.join(builddir, "conf/bblayers.conf")):
144 log.info("Adding: \"include bblayers.inc\" in bblayers.conf")
145 ftools.append_file(os.path.join(builddir, "conf/bblayers.conf"), \
146 "\n#include added by oe-selftest.py\ninclude bblayers.inc")
147
148def remove_include():
149 builddir = os.environ.get("BUILDDIR")
150 if builddir is None:
151 return
152 if "#include added by oe-selftest.py" \
153 in ftools.read_file(os.path.join(builddir, "conf/local.conf")):
154 log.info("Removing the include from local.conf")
155 ftools.remove_from_file(os.path.join(builddir, "conf/local.conf"), \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500156 "\n#include added by oe-selftest.py\ninclude machine.inc\ninclude selftest.inc")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157
158 if "#include added by oe-selftest.py" \
159 in ftools.read_file(os.path.join(builddir, "conf/bblayers.conf")):
160 log.info("Removing the include from bblayers.conf")
161 ftools.remove_from_file(os.path.join(builddir, "conf/bblayers.conf"), \
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500162 "\n#include added by oe-selftest.py\ninclude bblayers.inc")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163
164def remove_inc_files():
165 try:
166 os.remove(os.path.join(os.environ.get("BUILDDIR"), "conf/selftest.inc"))
167 for root, _, files in os.walk(get_test_layer()):
168 for f in files:
169 if f == 'test_recipe.inc':
170 os.remove(os.path.join(root, f))
171 except (AttributeError, OSError,) as e: # AttributeError may happen if BUILDDIR is not set
172 pass
173
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500174 for incl_file in ['conf/bblayers.inc', 'conf/machine.inc']:
175 try:
176 os.remove(os.path.join(os.environ.get("BUILDDIR"), incl_file))
177 except:
178 pass
179
180
181def get_tests_modules(include_hidden=False):
182 modules_list = list()
183 for modules_path in oeqa.selftest.__path__:
184 for (p, d, f) in os.walk(modules_path):
185 files = sorted([f for f in os.listdir(p) if f.endswith('.py') and not (f.startswith('_') and not include_hidden) and not f.startswith('__') and f != 'base.py'])
186 for f in files:
187 submodules = p.split("selftest")[-1]
188 module = ""
189 if submodules:
190 module = 'oeqa.selftest' + submodules.replace("/",".") + "." + f.split('.py')[0]
191 else:
192 module = 'oeqa.selftest.' + f.split('.py')[0]
193 if module not in modules_list:
194 modules_list.append(module)
195 return modules_list
196
197
198def get_tests(exclusive_modules=[], include_hidden=False):
199 test_modules = list()
200 for x in exclusive_modules:
201 test_modules.append('oeqa.selftest.' + x)
202 if not test_modules:
203 inc_hidden = include_hidden
204 test_modules = get_tests_modules(inc_hidden)
205
206 return test_modules
207
208
209class Tc:
210 def __init__(self, tcname, tcclass, tcmodule, tcid=None, tctag=None):
211 self.tcname = tcname
212 self.tcclass = tcclass
213 self.tcmodule = tcmodule
214 self.tcid = tcid
215 # A test case can have multiple tags (as tuples) otherwise str will suffice
216 self.tctag = tctag
217 self.fullpath = '.'.join(['oeqa', 'selftest', tcmodule, tcclass, tcname])
218
219
220def get_tests_from_module(tmod):
221 tlist = []
222 prefix = 'oeqa.selftest.'
223
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500224 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500225 import importlib
226 modlib = importlib.import_module(tmod)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600227 for mod in list(vars(modlib).values()):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500228 if isinstance(mod, type(oeSelfTest)) and issubclass(mod, oeSelfTest) and mod is not oeSelfTest:
229 for test in dir(mod):
230 if test.startswith('test_') and hasattr(vars(mod)[test], '__call__'):
231 # Get test case id and feature tag
232 # NOTE: if testcase decorator or feature tag not set will throw error
233 try:
234 tid = vars(mod)[test].test_case
235 except:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600236 print('DEBUG: tc id missing for ' + str(test))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500237 tid = None
238 try:
239 ttag = vars(mod)[test].tag__feature
240 except:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600241 # print('DEBUG: feature tag missing for ' + str(test))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500242 ttag = None
243
244 # NOTE: for some reason lstrip() doesn't work for mod.__module__
245 tlist.append(Tc(test, mod.__name__, mod.__module__.replace(prefix, ''), tid, ttag))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500246 except:
247 pass
248
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500249 return tlist
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500250
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500251
252def get_all_tests():
253 # Get all the test modules (except the hidden ones)
254 testlist = []
255 tests_modules = get_tests_modules()
256 # Get all the tests from modules
257 for tmod in sorted(tests_modules):
258 testlist += get_tests_from_module(tmod)
259 return testlist
260
261
262def get_testsuite_by(criteria, keyword):
263 # Get a testsuite based on 'keyword'
264 # criteria: name, class, module, id, tag
265 # keyword: a list of tests, classes, modules, ids, tags
266
267 ts = []
268 all_tests = get_all_tests()
269
270 def get_matches(values):
271 # Get an item and return the ones that match with keyword(s)
272 # values: the list of items (names, modules, classes...)
273 result = []
274 remaining = values[:]
275 for key in keyword:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600276 found = False
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500277 if key in remaining:
278 # Regular matching of exact item
279 result.append(key)
280 remaining.remove(key)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600281 found = True
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500282 else:
283 # Wildcard matching
284 pattern = re.compile(fnmatch.translate(r"%s" % key))
285 added = [x for x in remaining if pattern.match(x)]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600286 if added:
287 result.extend(added)
288 remaining = [x for x in remaining if x not in added]
289 found = True
290 if not found:
291 log.error("Failed to find test: %s" % key)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500292
293 return result
294
295 if criteria == 'name':
296 names = get_matches([ tc.tcname for tc in all_tests ])
297 ts = [ tc for tc in all_tests if tc.tcname in names ]
298
299 elif criteria == 'class':
300 classes = get_matches([ tc.tcclass for tc in all_tests ])
301 ts = [ tc for tc in all_tests if tc.tcclass in classes ]
302
303 elif criteria == 'module':
304 modules = get_matches([ tc.tcmodule for tc in all_tests ])
305 ts = [ tc for tc in all_tests if tc.tcmodule in modules ]
306
307 elif criteria == 'id':
308 ids = get_matches([ str(tc.tcid) for tc in all_tests ])
309 ts = [ tc for tc in all_tests if str(tc.tcid) in ids ]
310
311 elif criteria == 'tag':
312 values = set()
313 for tc in all_tests:
314 # tc can have multiple tags (as tuple) otherwise str will suffice
315 if isinstance(tc.tctag, tuple):
316 values |= { str(tag) for tag in tc.tctag }
317 else:
318 values.add(str(tc.tctag))
319
320 tags = get_matches(list(values))
321
322 for tc in all_tests:
323 for tag in tags:
324 if isinstance(tc.tctag, tuple) and tag in tc.tctag:
325 ts.append(tc)
326 elif tag == tc.tctag:
327 ts.append(tc)
328
329 # Remove duplicates from the list
330 ts = list(set(ts))
331
332 return ts
333
334
335def list_testsuite_by(criteria, keyword):
336 # Get a testsuite based on 'keyword'
337 # criteria: name, class, module, id, tag
338 # keyword: a list of tests, classes, modules, ids, tags
339
340 ts = sorted([ (tc.tcid, tc.tctag, tc.tcname, tc.tcclass, tc.tcmodule) for tc in get_testsuite_by(criteria, keyword) ])
341
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600342 print('%-4s\t%-20s\t%-60s\t%-25s\t%-20s' % ('id', 'tag', 'name', 'class', 'module'))
343 print('_' * 150)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500344 for t in ts:
345 if isinstance(t[1], (tuple, list)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600346 print('%-4s\t%-20s\t%-60s\t%-25s\t%-20s' % (t[0], ', '.join(t[1]), t[2], t[3], t[4]))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500347 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600348 print('%-4s\t%-20s\t%-60s\t%-25s\t%-20s' % t)
349 print('_' * 150)
350 print('Filtering by:\t %s' % criteria)
351 print('Looking for:\t %s' % ', '.join(str(x) for x in keyword))
352 print('Total found:\t %s' % len(ts))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500353
354
355def list_tests():
356 # List all available oe-selftest tests
357
358 ts = get_all_tests()
359
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600360 print('%-4s\t%-10s\t%-50s' % ('id', 'tag', 'test'))
361 print('_' * 80)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500362 for t in ts:
363 if isinstance(t.tctag, (tuple, list)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600364 print('%-4s\t%-10s\t%-50s' % (t.tcid, ', '.join(t.tctag), '.'.join([t.tcmodule, t.tcclass, t.tcname])))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500365 else:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600366 print('%-4s\t%-10s\t%-50s' % (t.tcid, t.tctag, '.'.join([t.tcmodule, t.tcclass, t.tcname])))
367 print('_' * 80)
368 print('Total found:\t %s' % len(ts))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500369
370def list_tags():
371 # Get all tags set to test cases
372 # This is useful when setting tags to test cases
373 # The list of tags should be kept as minimal as possible
374 tags = set()
375 all_tests = get_all_tests()
376
377 for tc in all_tests:
378 if isinstance(tc.tctag, (tuple, list)):
379 tags.update(set(tc.tctag))
380 else:
381 tags.add(tc.tctag)
382
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600383 print('Tags:\t%s' % ', '.join(str(x) for x in tags))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500384
385def coverage_setup(coverage_source, coverage_include, coverage_omit):
386 """ Set up the coverage measurement for the testcases to be run """
387 import datetime
388 import subprocess
389 builddir = os.environ.get("BUILDDIR")
390 pokydir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600391 curcommit= subprocess.check_output(["git", "--git-dir", os.path.join(pokydir, ".git"), "rev-parse", "HEAD"]).decode('utf-8')
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500392 coveragerc = "%s/.coveragerc" % builddir
393 data_file = "%s/.coverage." % builddir
394 data_file += datetime.datetime.now().strftime('%Y%m%dT%H%M%S')
395 if os.path.isfile(data_file):
396 os.remove(data_file)
397 with open(coveragerc, 'w') as cps:
398 cps.write("# Generated with command '%s'\n" % " ".join(sys.argv))
399 cps.write("# HEAD commit %s\n" % curcommit.strip())
400 cps.write("[run]\n")
401 cps.write("data_file = %s\n" % data_file)
402 cps.write("branch = True\n")
403 # Measure just BBLAYERS, scripts and bitbake folders
404 cps.write("source = \n")
405 if coverage_source:
406 for directory in coverage_source:
407 if not os.path.isdir(directory):
408 log.warn("Directory %s is not valid.", directory)
409 cps.write(" %s\n" % directory)
410 else:
411 for layer in get_bb_var('BBLAYERS').split():
412 cps.write(" %s\n" % layer)
413 cps.write(" %s\n" % os.path.dirname(os.path.realpath(__file__)))
414 cps.write(" %s\n" % os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))),'bitbake'))
415
416 if coverage_include:
417 cps.write("include = \n")
418 for pattern in coverage_include:
419 cps.write(" %s\n" % pattern)
420 if coverage_omit:
421 cps.write("omit = \n")
422 for pattern in coverage_omit:
423 cps.write(" %s\n" % pattern)
424
425 return coveragerc
426
427def coverage_report():
428 """ Loads the coverage data gathered and reports it back """
429 try:
430 # Coverage4 uses coverage.Coverage
431 from coverage import Coverage
432 except:
433 # Coverage under version 4 uses coverage.coverage
434 from coverage import coverage as Coverage
435
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600436 import io as StringIO
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500437 from coverage.misc import CoverageException
438
439 cov_output = StringIO.StringIO()
440 # Creating the coverage data with the setting from the configuration file
441 cov = Coverage(config_file = os.environ.get('COVERAGE_PROCESS_START'))
442 try:
443 # Load data from the data file specified in the configuration
444 cov.load()
445 # Store report data in a StringIO variable
446 cov.report(file = cov_output, show_missing=False)
447 log.info("\n%s" % cov_output.getvalue())
448 except CoverageException as e:
449 # Show problems with the reporting. Since Coverage4 not finding any data to report raises an exception
450 log.warn("%s" % str(e))
451 finally:
452 cov_output.close()
453
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500454
455def main():
456 parser = get_args_parser()
457 args = parser.parse_args()
458
459 # Add <layer>/lib to sys.path, so layers can add selftests
460 log.info("Running bitbake -e to get BBPATH")
461 bbpath = get_bb_var('BBPATH').split(':')
462 layer_libdirs = [p for p in (os.path.join(l, 'lib') for l in bbpath) if os.path.exists(p)]
463 sys.path.extend(layer_libdirs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600464 imp.reload(oeqa.selftest)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500466 if args.run_tests_by and len(args.run_tests_by) >= 2:
467 valid_options = ['name', 'class', 'module', 'id', 'tag']
468 if args.run_tests_by[0] not in valid_options:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600469 print('--run-tests-by %s not a valid option. Choose one of <name|class|module|id|tag>.' % args.run_tests_by[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500470 return 1
471 else:
472 criteria = args.run_tests_by[0]
473 keyword = args.run_tests_by[1:]
474 ts = sorted([ tc.fullpath for tc in get_testsuite_by(criteria, keyword) ])
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600475 if not ts:
476 return 1
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500477
478 if args.list_tests_by and len(args.list_tests_by) >= 2:
479 valid_options = ['name', 'class', 'module', 'id', 'tag']
480 if args.list_tests_by[0] not in valid_options:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600481 print('--list-tests-by %s not a valid option. Choose one of <name|class|module|id|tag>.' % args.list_tests_by[0])
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500482 return 1
483 else:
484 criteria = args.list_tests_by[0]
485 keyword = args.list_tests_by[1:]
486 list_testsuite_by(criteria, keyword)
487
488 if args.list_tests:
489 list_tests()
490
491 if args.list_tags:
492 list_tags()
493
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500494 if args.list_allclasses:
495 args.list_modules = True
496
497 if args.list_modules:
498 log.info('Listing all available test modules:')
499 testslist = get_tests(include_hidden=True)
500 for test in testslist:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500501 module = test.split('oeqa.selftest.')[-1]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500502 info = ''
503 if module.startswith('_'):
504 info = ' (hidden)'
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600505 print(module + info)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500506 if args.list_allclasses:
507 try:
508 import importlib
509 modlib = importlib.import_module(test)
510 for v in vars(modlib):
511 t = vars(modlib)[v]
512 if isinstance(t, type(oeSelfTest)) and issubclass(t, oeSelfTest) and t!=oeSelfTest:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600513 print(" --", v)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500514 for method in dir(t):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600515 if method.startswith("test_") and isinstance(vars(t)[method], collections.Callable):
516 print(" -- --", method)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500517
518 except (AttributeError, ImportError) as e:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600519 print(e)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500520 pass
521
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500522 if args.run_tests or args.run_all_tests or args.run_tests_by:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500523 if not preflight_check():
524 return 1
525
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500526 if args.run_tests_by:
527 testslist = ts
528 else:
529 testslist = get_tests(exclusive_modules=(args.run_tests or []), include_hidden=False)
530
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500531 suite = unittest.TestSuite()
532 loader = unittest.TestLoader()
533 loader.sortTestMethodsUsing = None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600534 runner = TestRunner(verbosity=2,
535 resultclass=buildResultClass(args))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500536 # we need to do this here, otherwise just loading the tests
537 # will take 2 minutes (bitbake -e calls)
538 oeSelfTest.testlayer_path = get_test_layer()
539 for test in testslist:
540 log.info("Loading tests from: %s" % test)
541 try:
542 suite.addTests(loader.loadTestsFromName(test))
543 except AttributeError as e:
544 log.error("Failed to import %s" % test)
545 log.error(e)
546 return 1
547 add_include()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500548
549 if args.machine:
550 # Custom machine sets only weak default values (??=) for MACHINE in machine.inc
551 # This let test cases that require a specific MACHINE to be able to override it, using (?= or =)
552 log.info('Custom machine mode enabled. MACHINE set to %s' % args.machine)
553 if args.machine == 'random':
554 os.environ['CUSTOMMACHINE'] = 'random'
555 result = runner.run(suite)
556 else: # all
557 machines = get_available_machines()
558 for m in machines:
559 log.info('Run tests with custom MACHINE set to: %s' % m)
560 os.environ['CUSTOMMACHINE'] = m
561 result = runner.run(suite)
562 else:
563 result = runner.run(suite)
564
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565 log.info("Finished")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500566
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500567 if result.wasSuccessful():
568 return 0
569 else:
570 return 1
571
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500572def buildResultClass(args):
573 """Build a Result Class to use in the testcase execution"""
574 import site
575
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600576 class StampedResult(TestResult):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500577 """
578 Custom TestResult that prints the time when a test starts. As oe-selftest
579 can take a long time (ie a few hours) to run, timestamps help us understand
580 what tests are taking a long time to execute.
581 If coverage is required, this class executes the coverage setup and reporting.
582 """
583 def startTest(self, test):
584 import time
585 self.stream.write(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + " - ")
586 super(StampedResult, self).startTest(test)
587
588 def startTestRun(self):
589 """ Setup coverage before running any testcase """
590
591 # variable holding the coverage configuration file allowing subprocess to be measured
592 self.coveragepth = None
593
594 # indicates the system if coverage is currently installed
595 self.coverage_installed = True
596
597 if args.coverage or args.coverage_source or args.coverage_include or args.coverage_omit:
598 try:
599 # check if user can do coverage
600 import coverage
601 except:
602 log.warn("python coverage is not installed. More info on https://pypi.python.org/pypi/coverage")
603 self.coverage_installed = False
604
605 if self.coverage_installed:
606 log.info("Coverage is enabled")
607
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600608 major_version = int(coverage.version.__version__[0])
609 if major_version < 4:
610 log.error("python coverage %s installed. Require version 4 or greater." % coverage.version.__version__)
611 self.stop()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500612 # In case the user has not set the variable COVERAGE_PROCESS_START,
613 # create a default one and export it. The COVERAGE_PROCESS_START
614 # value indicates where the coverage configuration file resides
615 # More info on https://pypi.python.org/pypi/coverage
616 if not os.environ.get('COVERAGE_PROCESS_START'):
617 os.environ['COVERAGE_PROCESS_START'] = coverage_setup(args.coverage_source, args.coverage_include, args.coverage_omit)
618
619 # Use default site.USER_SITE and write corresponding config file
620 site.ENABLE_USER_SITE = True
621 if not os.path.exists(site.USER_SITE):
622 os.makedirs(site.USER_SITE)
623 self.coveragepth = os.path.join(site.USER_SITE, "coverage.pth")
624 with open(self.coveragepth, 'w') as cps:
625 cps.write('import sys,site; sys.path.extend(site.getsitepackages()); import coverage; coverage.process_startup();')
626
627 def stopTestRun(self):
628 """ Report coverage data after the testcases are run """
629
630 if args.coverage or args.coverage_source or args.coverage_include or args.coverage_omit:
631 if self.coverage_installed:
632 with open(os.environ['COVERAGE_PROCESS_START']) as ccf:
633 log.info("Coverage configuration file (%s)" % os.environ.get('COVERAGE_PROCESS_START'))
634 log.info("===========================")
635 log.info("\n%s" % "".join(ccf.readlines()))
636
637 log.info("Coverage Report")
638 log.info("===============")
639 try:
640 coverage_report()
641 finally:
642 # remove the pth file
643 try:
644 os.remove(self.coveragepth)
645 except OSError:
646 log.warn("Expected temporal file from coverage is missing, ignoring removal.")
647
648 return StampedResult
649
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600650class TestRunner(_TestRunner):
651 """Test runner class aware of exporting tests."""
652 def __init__(self, *args, **kwargs):
653 try:
654 exportdir = os.path.join(os.getcwd(), log_prefix)
655 kwargsx = dict(**kwargs)
656 # argument specific to XMLTestRunner, if adding a new runner then
657 # also add logic to use other runner's args.
658 kwargsx['output'] = exportdir
659 kwargsx['descriptions'] = False
660 # done for the case where telling the runner where to export
661 super(TestRunner, self).__init__(*args, **kwargsx)
662 except TypeError:
663 log.info("test runner init'ed like unittest")
664 super(TestRunner, self).__init__(*args, **kwargs)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500665
666if __name__ == "__main__":
667 try:
668 ret = main()
669 except Exception:
670 ret = 1
671 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500672 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500673 finally:
674 remove_include()
675 remove_inc_files()
676 sys.exit(ret)