1# Copyright (C) 2017 Intel Corporation
2# Released under the MIT license (see COPYING.MIT)
3
4import os
5import time
6import glob
7import sys
8import imp
9import signal
10from shutil import copyfile
11from random import choice
12
13import oeqa
14
15from oeqa.core.context import OETestContext, OETestContextExecutor
16from oeqa.core.exception import OEQAPreRun, OEQATestNotFound
17
18from oeqa.utils.commands import runCmd, get_bb_vars, get_test_layer
19
20class OESelftestTestContext(OETestContext):
21    def __init__(self, td=None, logger=None, machines=None, config_paths=None):
22        super(OESelftestTestContext, self).__init__(td, logger)
23
24        self.machines = machines
25        self.custommachine = None
26        self.config_paths = config_paths
27
28    def runTests(self, machine=None, skips=[]):
29        if machine:
30            self.custommachine = machine
31            if machine == 'random':
32                self.custommachine = choice(self.machines)
33            self.logger.info('Run tests with custom MACHINE set to: %s' % \
34                    self.custommachine)
35        return super(OESelftestTestContext, self).runTests(skips)
36
37    def listTests(self, display_type, machine=None):
38        return super(OESelftestTestContext, self).listTests(display_type)
39
40class OESelftestTestContextExecutor(OETestContextExecutor):
41    _context_class = OESelftestTestContext
42    _script_executor = 'oe-selftest'
43
44    name = 'oe-selftest'
45    help = 'oe-selftest test component'
46    description = 'Executes selftest tests'
47
48    def register_commands(self, logger, parser):
49        group = parser.add_mutually_exclusive_group(required=True)
50
51        group.add_argument('-a', '--run-all-tests', default=False,
52                action="store_true", dest="run_all_tests",
53                help='Run all (unhidden) tests')
54        group.add_argument('-R', '--skip-tests', required=False, action='store',
55                nargs='+', dest="skips", default=None,
56                help='Run all (unhidden) tests except the ones specified. Format should be <module>[.<class>[.<test_method>]]')
57        group.add_argument('-r', '--run-tests', required=False, action='store',
58                nargs='+', dest="run_tests", default=None,
59                help='Select what tests to run (modules, classes or test methods). Format should be: <module>.<class>.<test_method>')
60
61        group.add_argument('-m', '--list-modules', required=False,
62                action="store_true", default=False,
63                help='List all available test modules.')
64        group.add_argument('--list-classes', required=False,
65                action="store_true", default=False,
66                help='List all available test classes.')
67        group.add_argument('-l', '--list-tests', required=False,
68                action="store_true", default=False,
69                help='List all available tests.')
70
71        parser.add_argument('--machine', required=False, choices=['random', 'all'],
72                            help='Run tests on different machines (random/all).')
73
74        parser.set_defaults(func=self.run)
75
76    def _get_available_machines(self):
77        machines = []
78
79        bbpath = self.tc_kwargs['init']['td']['BBPATH'].split(':')
80
81        for path in bbpath:
82            found_machines = glob.glob(os.path.join(path, 'conf', 'machine', '*.conf'))
83            if found_machines:
84                for i in found_machines:
85                    # eg: '/home/<user>/poky/meta-intel/conf/machine/intel-core2-32.conf'
86                    machines.append(os.path.splitext(os.path.basename(i))[0])
87
88        return machines
89
90    def _get_cases_paths(self, bbpath):
91        cases_paths = []
92        for layer in bbpath:
93            cases_dir = os.path.join(layer, 'lib', 'oeqa', 'selftest', 'cases')
94            if os.path.isdir(cases_dir):
95                cases_paths.append(cases_dir)
96        return cases_paths
97
98    def _process_args(self, logger, args):
99        args.output_log = '%s-results-%s.log' % (self.name,
100                time.strftime("%Y%m%d%H%M%S"))
101        args.test_data_file = None
102        args.CASES_PATHS = None
103
104        super(OESelftestTestContextExecutor, self)._process_args(logger, args)
105
106        if args.list_modules:
107            args.list_tests = 'module'
108        elif args.list_classes:
109            args.list_tests = 'class'
110        elif args.list_tests:
111            args.list_tests = 'name'
112
113        self.tc_kwargs['init']['td'] = get_bb_vars()
114        self.tc_kwargs['init']['machines'] = self._get_available_machines()
115
116        builddir = os.environ.get("BUILDDIR")
117        self.tc_kwargs['init']['config_paths'] = {}
118        self.tc_kwargs['init']['config_paths']['testlayer_path'] = \
119                get_test_layer()
120        self.tc_kwargs['init']['config_paths']['builddir'] = builddir
121        self.tc_kwargs['init']['config_paths']['localconf'] = \
122                os.path.join(builddir, "conf/local.conf")
123        self.tc_kwargs['init']['config_paths']['localconf_backup'] = \
124                os.path.join(builddir, "conf/local.conf.orig")
125        self.tc_kwargs['init']['config_paths']['localconf_class_backup'] = \
126                os.path.join(builddir, "conf/local.conf.bk")
127        self.tc_kwargs['init']['config_paths']['bblayers'] = \
128                os.path.join(builddir, "conf/bblayers.conf")
129        self.tc_kwargs['init']['config_paths']['bblayers_backup'] = \
130                os.path.join(builddir, "conf/bblayers.conf.orig")
131        self.tc_kwargs['init']['config_paths']['bblayers_class_backup'] = \
132                os.path.join(builddir, "conf/bblayers.conf.bk")
133
134        copyfile(self.tc_kwargs['init']['config_paths']['localconf'],
135                self.tc_kwargs['init']['config_paths']['localconf_backup'])
136        copyfile(self.tc_kwargs['init']['config_paths']['bblayers'],
137                self.tc_kwargs['init']['config_paths']['bblayers_backup'])
138
139        self.tc_kwargs['run']['skips'] = args.skips
140
141    def _pre_run(self):
142        def _check_required_env_variables(vars):
143            for var in vars:
144                if not os.environ.get(var):
145                    self.tc.logger.error("%s is not set. Did you forget to source your build environment setup script?" % var)
146                    raise OEQAPreRun
147
148        def _check_presence_meta_selftest():
149            builddir = os.environ.get("BUILDDIR")
150            if os.getcwd() != builddir:
151                self.tc.logger.info("Changing cwd to %s" % builddir)
152                os.chdir(builddir)
153
154            if not "meta-selftest" in self.tc.td["BBLAYERS"]:
155                self.tc.logger.warn("meta-selftest layer not found in BBLAYERS, adding it")
156                meta_selftestdir = os.path.join(
157                    self.tc.td["BBLAYERS_FETCH_DIR"], 'meta-selftest')
158                if os.path.isdir(meta_selftestdir):
159                    runCmd("bitbake-layers add-layer %s" %meta_selftestdir)
160                    # reload data is needed because a meta-selftest layer was add
161                    self.tc.td = get_bb_vars()
162                    self.tc.config_paths['testlayer_path'] = get_test_layer()
163                else:
164                    self.tc.logger.error("could not locate meta-selftest in:\n%s" % meta_selftestdir)
165                    raise OEQAPreRun
166
167        def _add_layer_libs():
168            bbpath = self.tc.td['BBPATH'].split(':')
169            layer_libdirs = [p for p in (os.path.join(l, 'lib') \
170                    for l in bbpath) if os.path.exists(p)]
171            if layer_libdirs:
172                self.tc.logger.info("Adding layer libraries:")
173                for l in layer_libdirs:
174                    self.tc.logger.info("\t%s" % l)
175
176                sys.path.extend(layer_libdirs)
177                imp.reload(oeqa.selftest)
178
179        _check_required_env_variables(["BUILDDIR"])
180        _check_presence_meta_selftest()
181
182        if "buildhistory.bbclass" in self.tc.td["BBINCLUDED"]:
183            self.tc.logger.error("You have buildhistory enabled already and this isn't recommended for selftest, please disable it first.")
184            raise OEQAPreRun
185
186        if "PRSERV_HOST" in self.tc.td:
187            self.tc.logger.error("Please unset PRSERV_HOST in order to run oe-selftest")
188            raise OEQAPreRun
189
190        if "SANITY_TESTED_DISTROS" in self.tc.td:
191            self.tc.logger.error("Please unset SANITY_TESTED_DISTROS in order to run oe-selftest")
192            raise OEQAPreRun
193
194        _add_layer_libs()
195
196        self.tc.logger.info("Running bitbake -p")
197        runCmd("bitbake -p")
198
199    def _internal_run(self, logger, args):
200        self.module_paths = self._get_cases_paths(
201                self.tc_kwargs['init']['td']['BBPATH'].split(':'))
202
203        self.tc = self._context_class(**self.tc_kwargs['init'])
204        try:
205            self.tc.loadTests(self.module_paths, **self.tc_kwargs['load'])
206        except OEQATestNotFound as ex:
207            logger.error(ex)
208            sys.exit(1)
209
210        if args.list_tests:
211            rc = self.tc.listTests(args.list_tests, **self.tc_kwargs['list'])
212        else:
213            self._pre_run()
214            rc = self.tc.runTests(**self.tc_kwargs['run'])
215            rc.logDetails()
216            rc.logSummary(self.name)
217
218        return rc
219
220    def _signal_clean_handler(self, signum, frame):
221        sys.exit(1)
222
223    def run(self, logger, args):
224        self._process_args(logger, args)
225
226        signal.signal(signal.SIGTERM, self._signal_clean_handler)
227
228        rc = None
229        try:
230            if args.machine:
231                logger.info('Custom machine mode enabled. MACHINE set to %s' %
232                        args.machine)
233
234                if args.machine == 'all':
235                    results = []
236                    for m in self.tc_kwargs['init']['machines']:
237                        self.tc_kwargs['run']['machine'] = m
238                        results.append(self._internal_run(logger, args))
239
240                        # XXX: the oe-selftest script only needs to know if one
241                        # machine run fails
242                        for r in results:
243                            rc = r
244                            if not r.wasSuccessful():
245                                break
246
247                else:
248                    self.tc_kwargs['run']['machine'] = args.machine
249                    return self._internal_run(logger, args)
250
251            else:
252                self.tc_kwargs['run']['machine'] = args.machine
253                rc = self._internal_run(logger, args)
254        finally:
255            config_paths = self.tc_kwargs['init']['config_paths']
256            if os.path.exists(config_paths['localconf_backup']):
257                copyfile(config_paths['localconf_backup'],
258                        config_paths['localconf'])
259                os.remove(config_paths['localconf_backup'])
260
261            if os.path.exists(config_paths['bblayers_backup']):
262                copyfile(config_paths['bblayers_backup'],
263                        config_paths['bblayers'])
264                os.remove(config_paths['bblayers_backup'])
265
266            if os.path.exists(config_paths['localconf_class_backup']):
267                os.remove(config_paths['localconf_class_backup'])
268            if os.path.exists(config_paths['bblayers_class_backup']):
269                os.remove(config_paths['bblayers_class_backup'])
270
271            output_link = os.path.join(os.path.dirname(args.output_log),
272                    "%s-results.log" % self.name)
273            if os.path.exists(output_link):
274                os.remove(output_link)
275            os.symlink(args.output_log, output_link)
276
277        return rc
278
279_executor_class = OESelftestTestContextExecutor
280