blob: 8fea915941fc26586f102016a90bf720d3b3dbb5 [file] [log] [blame]
Vishal Bhojdc338f62020-05-18 15:38:18 +05301#!/usr/bin/env python3
Chase Qi09edc7f2016-08-18 13:18:50 +08002import argparse
3import csv
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00004import cmd
Chase Qia158efe2017-11-17 12:35:11 +08005import copy
Chase Qi09edc7f2016-08-18 13:18:50 +08006import json
7import logging
Vishal Bhoje94da4a2020-05-15 12:22:40 +05308import netrc
Chase Qi09edc7f2016-08-18 13:18:50 +08009import os
Chase Qi09edc7f2016-08-18 13:18:50 +080010import re
Milosz Wasilewski682120e2017-03-13 13:37:18 +000011import shlex
Chase Qi09edc7f2016-08-18 13:18:50 +080012import shutil
Milosz Wasilewski970431b2016-11-25 14:10:08 +000013import subprocess
Chase Qi09edc7f2016-08-18 13:18:50 +080014import sys
Milosz Wasilewski259ba192017-07-27 10:59:25 +010015import textwrap
Chase Qi09edc7f2016-08-18 13:18:50 +080016import time
Chase Qi09edc7f2016-08-18 13:18:50 +080017from uuid import uuid4
Chase Qiea543352017-09-21 16:44:30 +080018from distutils.spawn import find_executable
Chase Qi09edc7f2016-08-18 13:18:50 +080019
20
Chase Qifaf7d282016-08-29 19:34:01 +080021try:
Vishal Bhoje94da4a2020-05-15 12:22:40 +053022 from squad_client.core.api import SquadApi
23 from squad_client.shortcuts import submit_results
24 from squad_client.core.models import Squad
25 from urllib.parse import urlparse
26except ImportError as e:
27 logger = logging.getLogger('RUNNER')
28 logger.warning('squad_client is needed if you want to upload to qa-reports')
29
30
31try:
Chase Qifaf7d282016-08-29 19:34:01 +080032 import pexpect
33 import yaml
34except ImportError as e:
35 print(e)
36 print('Please run the below command to install modules required')
Vishal Bhojdc338f62020-05-18 15:38:18 +053037 print('pip3 install -r ${REPO_PATH}/automated/utils/requirements.txt')
Chase Qifaf7d282016-08-29 19:34:01 +080038 sys.exit(1)
39
40
Milosz Wasilewski259ba192017-07-27 10:59:25 +010041class StoreDictKeyPair(argparse.Action):
42 def __init__(self, option_strings, dest, nargs=None, **kwargs):
43 self._nargs = nargs
44 super(StoreDictKeyPair, self).__init__(option_strings, dest, nargs=nargs, **kwargs)
45
46 def __call__(self, parser, namespace, values, option_string=None):
47 my_dict = {}
48 for kv in values:
Milosz Wasilewskif9c8c062017-08-02 16:10:40 +010049 if "=" in kv:
50 k, v = kv.split("=", 1)
51 my_dict[k] = v
52 else:
53 print("Invalid parameter: %s" % kv)
Milosz Wasilewski259ba192017-07-27 10:59:25 +010054 setattr(namespace, self.dest, my_dict)
55
56
Milosz Wasilewskidf71a762017-07-20 13:26:21 +010057# quit gracefully if the connection is closed by remote host
58SSH_PARAMS = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=5"
Milosz Wasilewski682120e2017-03-13 13:37:18 +000059
60
Dan Ruea9eb01c2017-06-07 16:29:09 -050061def run_command(command, target=None):
62 """ Run a shell command. If target is specified, ssh to the given target first. """
63
64 run = command
65 if target:
66 run = 'ssh {} {} "{}"'.format(SSH_PARAMS, target, command)
67
68 logger = logging.getLogger('RUNNER.run_command')
69 logger.debug(run)
Chase Qi3efa7692017-06-26 15:54:05 +080070 if sys.version_info[0] < 3:
71 return subprocess.check_output(shlex.split(run)).strip()
72 else:
73 return subprocess.check_output(shlex.split(run)).strip().decode('utf-8')
Milosz Wasilewski682120e2017-03-13 13:37:18 +000074
75
Chase Qi09edc7f2016-08-18 13:18:50 +080076class TestPlan(object):
77 """
78 Analysis args specified, then generate test plan.
79 """
80
81 def __init__(self, args):
Chase Qi09edc7f2016-08-18 13:18:50 +080082 self.test_def = args.test_def
83 self.test_plan = args.test_plan
84 self.timeout = args.timeout
85 self.skip_install = args.skip_install
86 self.logger = logging.getLogger('RUNNER.TestPlan')
Chase Qia158efe2017-11-17 12:35:11 +080087 self.overlay = args.overlay
88
89 def apply_overlay(self, test_list):
90 fixed_test_list = copy.deepcopy(test_list)
91 logger = logging.getLogger('RUNNER.TestPlan.Overlay')
92 with open(self.overlay) as f:
93 data = yaml.load(f)
94
95 if data.get('skip'):
96 skip_tests = data['skip']
97 for test in test_list:
98 for skip_test in skip_tests:
99 if test['path'] == skip_test['path'] and test['repository'] == skip_test['repository']:
100 fixed_test_list.remove(test)
101 logger.info("Skipped: {}".format(test))
102 else:
103 continue
104
105 if data.get('amend'):
106 amend_tests = data['amend']
107 for test in fixed_test_list:
108 for amend_test in amend_tests:
109 if test['path'] == amend_test['path'] and test['repository'] == skip_test['repository']:
110 if amend_test.get('parameters'):
111 if test.get('parameters'):
112 test['parameters'].update(amend_test['parameters'])
113 else:
114 test['parameters'] = amend_test['parameters']
115 logger.info('Updated: {}'.format(test))
116 else:
117 logger.warning("'parameters' not found in {}, nothing to amend.".format(amend_test))
118
119 if data.get('add'):
120 add_tests = data['add']
121 unique_add_tests = []
122 for test in add_tests:
123 if test not in unique_add_tests:
124 unique_add_tests.append(test)
125 else:
126 logger.warning("Skipping duplicate test {}".format(test))
127
128 for test in test_list:
129 del test['uuid']
130
131 for add_test in unique_add_tests:
132 if add_test in test_list:
133 logger.warning("{} already included in test plan, do nothing.".format(add_test))
134 else:
135 add_test['uuid'] = str(uuid4())
136 fixed_test_list.append(add_test)
137 logger.info("Added: {}".format(add_test))
138
139 return fixed_test_list
Chase Qi09edc7f2016-08-18 13:18:50 +0800140
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000141 def test_list(self, kind="automated"):
Chase Qi09edc7f2016-08-18 13:18:50 +0800142 if self.test_def:
143 if not os.path.exists(self.test_def):
144 self.logger.error(' %s NOT found, exiting...' % self.test_def)
145 sys.exit(1)
146
147 test_list = [{'path': self.test_def}]
148 test_list[0]['uuid'] = str(uuid4())
149 test_list[0]['timeout'] = self.timeout
150 test_list[0]['skip_install'] = self.skip_install
151 elif self.test_plan:
152 if not os.path.exists(self.test_plan):
153 self.logger.error(' %s NOT found, exiting...' % self.test_plan)
154 sys.exit(1)
155
156 with open(self.test_plan, 'r') as f:
157 test_plan = yaml.safe_load(f)
158 try:
Chase Qidca4fb62017-11-22 12:09:42 +0800159 plan_version = test_plan['metadata'].get('format')
160 self.logger.info('Test plan version: {}'.format(plan_version))
Milosz Wasilewski8d64bb22019-06-18 12:32:08 +0100161 tests = []
Chase Qidca4fb62017-11-22 12:09:42 +0800162 if plan_version == "Linaro Test Plan v2":
163 tests = test_plan['tests'][kind]
164 elif plan_version == "Linaro Test Plan v1" or plan_version is None:
Chase Qidca4fb62017-11-22 12:09:42 +0800165 for requirement in test_plan['requirements']:
166 if 'tests' in requirement.keys():
167 if requirement['tests'] and \
168 kind in requirement['tests'].keys() and \
169 requirement['tests'][kind]:
170 for test in requirement['tests'][kind]:
171 tests.append(test)
172
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000173 test_list = []
Dan Rued9d7b652017-05-26 14:00:10 -0500174 unique_tests = [] # List of test hashes
Chase Qidca4fb62017-11-22 12:09:42 +0800175 for test in tests:
176 test_hash = hash(json.dumps(test, sort_keys=True))
177 if test_hash in unique_tests:
178 # Test is already in the test_list; don't add it again.
179 self.logger.warning("Skipping duplicate test {}".format(test))
180 continue
181 unique_tests.append(test_hash)
182 test_list.append(test)
Chase Qi09edc7f2016-08-18 13:18:50 +0800183 for test in test_list:
184 test['uuid'] = str(uuid4())
185 except KeyError as e:
186 self.logger.error("%s is missing from test plan" % str(e))
187 sys.exit(1)
188 else:
189 self.logger.error('Plese specify a test or test plan.')
190 sys.exit(1)
191
Chase Qia158efe2017-11-17 12:35:11 +0800192 if self.overlay is None:
193 return test_list
194 else:
195 return self.apply_overlay(test_list)
Chase Qi09edc7f2016-08-18 13:18:50 +0800196
197
198class TestSetup(object):
199 """
200 Create directories required, then copy files needed to these directories.
201 """
202
203 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000204 self.test = test
205 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800206 self.logger = logging.getLogger('RUNNER.TestSetup')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000207 self.test_kind = args.kind
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000208 self.test_version = test.get('version', None)
Chase Qi09edc7f2016-08-18 13:18:50 +0800209
210 def validate_env(self):
211 # Inspect if environment set properly.
212 try:
213 self.repo_path = os.environ['REPO_PATH']
214 except KeyError:
215 self.logger.error('KeyError: REPO_PATH')
216 self.logger.error("Please run '. ./bin/setenv.sh' to setup test environment")
217 sys.exit(1)
218
219 def create_dir(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000220 if not os.path.exists(self.test['output']):
221 os.makedirs(self.test['output'])
222 self.logger.info('Output directory created: %s' % self.test['output'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800223
224 def copy_test_repo(self):
225 self.validate_env()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000226 shutil.rmtree(self.test['test_path'], ignore_errors=True)
227 if self.repo_path in self.test['test_path']:
Chase Qi33eb7652016-12-02 10:43:46 +0800228 self.logger.error("Cannot copy repository into itself. Please choose output directory outside repository path")
229 sys.exit(1)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000230 shutil.copytree(self.repo_path, self.test['test_path'], symlinks=True)
231 self.logger.info('Test repo copied to: %s' % self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800232
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000233 def checkout_version(self):
234 if self.test_version:
235 path = os.getcwd()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000236 os.chdir(self.test['test_path'])
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000237 subprocess.call("git checkout %s" % self.test_version, shell=True)
238 os.chdir(path)
239
Chase Qi09edc7f2016-08-18 13:18:50 +0800240 def create_uuid_file(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000241 with open('%s/uuid' % self.test['test_path'], 'w') as f:
242 f.write(self.test['uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800243
244
245class TestDefinition(object):
246 """
247 Convert test definition to testdef.yaml, testdef_metadata and run.sh.
248 """
249
250 def __init__(self, test, args):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000251 self.test = test
252 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800253 self.logger = logging.getLogger('RUNNER.TestDef')
254 self.skip_install = args.skip_install
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000255 self.is_manual = False
Chase Qi09edc7f2016-08-18 13:18:50 +0800256 if 'skip_install' in test:
257 self.skip_install = test['skip_install']
258 self.custom_params = None
259 if 'parameters' in test:
260 self.custom_params = test['parameters']
261 if 'params' in test:
262 self.custom_params = test['params']
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000263 self.exists = False
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000264 if os.path.isfile(self.test['path']):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000265 self.exists = True
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000266 with open(self.test['path'], 'r') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000267 self.testdef = yaml.safe_load(f)
268 if self.testdef['metadata']['format'].startswith("Manual Test Definition"):
269 self.is_manual = True
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100270 if self.is_manual:
271 self.runner = ManualTestRun(test, args)
Chase Qi43bb9122017-05-23 14:37:48 +0800272 elif self.args.target is not None:
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100273 self.runner = RemoteTestRun(test, args)
Chase Qi43bb9122017-05-23 14:37:48 +0800274 else:
275 self.runner = AutomatedTestRun(test, args)
Chase Qi09edc7f2016-08-18 13:18:50 +0800276
277 def definition(self):
Chase Qic69235d2017-05-23 14:56:47 +0800278 with open('%s/testdef.yaml' % self.test['test_path'], 'wb') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800279 f.write(yaml.dump(self.testdef, encoding='utf-8', allow_unicode=True))
280
281 def metadata(self):
Chase Qic69235d2017-05-23 14:56:47 +0800282 with open('%s/testdef_metadata' % self.test['test_path'], 'wb') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800283 f.write(yaml.dump(self.testdef['metadata'], encoding='utf-8', allow_unicode=True))
284
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100285 def mkrun(self):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000286 if not self.is_manual:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000287 with open('%s/run.sh' % self.test['test_path'], 'a') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000288 f.write('#!/bin/sh\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800289
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000290 self.parameters = self.handle_parameters()
291 if self.parameters:
292 for line in self.parameters:
293 f.write(line)
Chase Qi09edc7f2016-08-18 13:18:50 +0800294
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000295 f.write('set -e\n')
Chase Qif2762462017-03-28 17:01:10 +0800296 f.write('set -x\n')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000297 f.write('export TESTRUN_ID=%s\n' % self.testdef['metadata']['name'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000298 if self.args.target is None:
299 f.write('cd %s\n' % (self.test['test_path']))
300 else:
301 f.write('cd %s\n' % (self.test['target_test_path']))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000302 f.write('UUID=`cat uuid`\n')
303 f.write('echo "<STARTRUN $TESTRUN_ID $UUID>"\n')
Nicolas Dechesned67955a2017-03-14 09:41:04 +0100304 f.write('export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin\n')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000305 steps = self.testdef['run'].get('steps', [])
306 if steps:
Dan Rueb592da12017-06-07 16:32:43 -0500307 for step in steps:
308 command = step
309 if '--cmd' in step or '--shell' in step:
310 command = re.sub(r'\$(\d+)\b', r'\\$\1', step)
311 f.write('%s\n' % command)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000312 f.write('echo "<ENDRUN $TESTRUN_ID $UUID>"\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800313
Chase Qic69235d2017-05-23 14:56:47 +0800314 os.chmod('%s/run.sh' % self.test['test_path'], 0o755)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000315
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100316 def run(self):
317 self.runner.run()
Chase Qi09edc7f2016-08-18 13:18:50 +0800318
319 def handle_parameters(self):
320 ret_val = ['###default parameters from test definition###\n']
321
322 if 'params' in self.testdef:
323 for def_param_name, def_param_value in list(self.testdef['params'].items()):
324 # ?'yaml_line'
325 if def_param_name is 'yaml_line':
326 continue
327 ret_val.append('%s=\'%s\'\n' % (def_param_name, def_param_value))
328 elif 'parameters' in self.testdef:
329 for def_param_name, def_param_value in list(self.testdef['parameters'].items()):
330 if def_param_name is 'yaml_line':
331 continue
332 ret_val.append('%s=\'%s\'\n' % (def_param_name, def_param_value))
333 else:
334 return None
335
336 ret_val.append('######\n')
337
338 ret_val.append('###custom parameters from test plan###\n')
339 if self.custom_params:
340 for param_name, param_value in list(self.custom_params.items()):
341 if param_name is 'yaml_line':
342 continue
343 ret_val.append('%s=\'%s\'\n' % (param_name, param_value))
344
345 if self.skip_install:
346 ret_val.append('SKIP_INSTALL="True"\n')
347 ret_val.append('######\n')
348
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100349 ret_val.append('###custom parameters from command line###\n')
350 if self.args.test_def_params:
351 for param_name, param_value in self.args.test_def_params.items():
352 ret_val.append('%s=\'%s\'\n' % (param_name, param_value))
353 ret_val.append('######\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800354 return ret_val
355
356
357class TestRun(object):
358 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000359 self.test = test
360 self.args = args
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000361 self.logger = logging.getLogger('RUNNER.TestRun')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000362 self.test_timeout = self.args.timeout
Chase Qi09edc7f2016-08-18 13:18:50 +0800363 if 'timeout' in test:
364 self.test_timeout = test['timeout']
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000365
366 def run(self):
367 raise NotImplementedError
368
369 def check_result(self):
370 raise NotImplementedError
371
372
373class AutomatedTestRun(TestRun):
374 def run(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000375 self.logger.info('Executing %s/run.sh' % self.test['test_path'])
376 shell_cmd = '%s/run.sh 2>&1 | tee %s/stdout.log' % (self.test['test_path'], self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800377 self.child = pexpect.spawn('/bin/sh', ['-c', shell_cmd])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000378 self.check_result()
Chase Qi09edc7f2016-08-18 13:18:50 +0800379
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000380 def check_result(self):
Chase Qi09edc7f2016-08-18 13:18:50 +0800381 if self.test_timeout:
382 self.logger.info('Test timeout: %s' % self.test_timeout)
383 test_end = time.time() + self.test_timeout
384
385 while self.child.isalive():
386 if self.test_timeout and time.time() > test_end:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000387 self.logger.warning('%s test timed out, killing test process...' % self.test['test_uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800388 self.child.terminate(force=True)
389 break
390 try:
391 self.child.expect('\r\n')
Chase Qi3efa7692017-06-26 15:54:05 +0800392 if sys.version_info[0] < 3:
393 print(self.child.before)
394 else:
395 print(self.child.before.decode('utf-8'))
Chase Qi09edc7f2016-08-18 13:18:50 +0800396 except pexpect.TIMEOUT:
397 continue
398 except pexpect.EOF:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000399 self.logger.info('%s test finished.\n' % self.test['test_uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800400 break
401
402
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000403class RemoteTestRun(AutomatedTestRun):
404 def copy_to_target(self):
405 os.chdir(self.test['test_path'])
406 tarball_name = "target-test-files.tar"
Dan Ruea9eb01c2017-06-07 16:29:09 -0500407
408 self.logger.info("Archiving test files")
409 run_command(
410 'tar -caf %s run.sh uuid automated/lib automated/bin automated/utils %s' %
411 (tarball_name, self.test['tc_relative_dir']))
412
413 self.logger.info("Creating test path")
414 run_command("mkdir -p %s" % (self.test['target_test_path']), self.args.target)
415
416 self.logger.info("Copying test archive to target host")
417 run_command('scp %s ./%s %s:%s' % (SSH_PARAMS, tarball_name, self.args.target,
418 self.test['target_test_path']))
419
420 self.logger.info("Unarchiving test files on target")
421 run_command("cd %s && tar -xf %s" % (self.test['target_test_path'],
422 tarball_name), self.args.target)
423
424 self.logger.info("Removing test file archive from target")
425 run_command("rm %s/%s" % (self.test['target_test_path'],
426 tarball_name), self.args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000427
428 def run(self):
429 self.copy_to_target()
430 self.logger.info('Executing %s/run.sh remotely on %s' % (self.test['target_test_path'], self.args.target))
431 shell_cmd = 'ssh %s %s "%s/run.sh 2>&1"' % (SSH_PARAMS, self.args.target, self.test['target_test_path'])
432 self.logger.debug('shell_cmd: %s' % shell_cmd)
433 output = open("%s/stdout.log" % self.test['test_path'], "w")
434 self.child = pexpect.spawn(shell_cmd)
435 self.child.logfile = output
436 self.check_result()
437
438
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000439class ManualTestShell(cmd.Cmd):
440 def __init__(self, test_dict, result_path):
441 cmd.Cmd.__init__(self)
442 self.test_dict = test_dict
443 self.result_path = result_path
444 self.current_step_index = 0
445 self.steps = self.test_dict['run']['steps']
446 self.expected = self.test_dict['run']['expected']
447 self.prompt = "%s > " % self.test_dict['metadata']['name']
448 self.result = None
449 self.intro = """
450 Welcome to manual test executor. Type 'help' for available commands.
451 This shell is meant to be executed on your computer, not on the system
452 under test. Please execute the steps from the test case, compare to
453 expected result and record the test result as 'pass' or 'fail'. If there
454 is an issue that prevents from executing the step, please record the result
455 as 'skip'.
456 """
457
458 def do_quit(self, line):
459 """
460 Exit test execution
461 """
462 if self.result is not None:
463 return True
464 if line.find("-f") >= 0:
465 self._record_result("skip")
466 return True
Chase Qic69235d2017-05-23 14:56:47 +0800467 print("Test result not recorded. Use -f to force. Forced quit records result as 'skip'")
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000468
469 do_EOF = do_quit
470
471 def do_description(self, line):
472 """
473 Prints current test overall description
474 """
Chase Qic69235d2017-05-23 14:56:47 +0800475 print(self.test_dict['metadata']['description'])
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000476
477 def do_steps(self, line):
478 """
479 Prints all steps of the current test case
480 """
481 for index, step in enumerate(self.steps):
Chase Qic69235d2017-05-23 14:56:47 +0800482 print("%s. %s" % (index, step))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000483
484 def do_expected(self, line):
485 """
486 Prints all expected results of the current test case
487 """
488 for index, expected in enumerate(self.expected):
Chase Qic69235d2017-05-23 14:56:47 +0800489 print("%s. %s" % (index, expected))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000490
491 def do_current(self, line):
492 """
493 Prints current test step
494 """
495 self._print_step()
496
497 do_start = do_current
498
499 def do_next(self, line):
500 """
501 Prints next test step
502 """
503 if len(self.steps) > self.current_step_index + 1:
504 self.current_step_index += 1
505 self._print_step()
506
507 def _print_step(self):
Chase Qic69235d2017-05-23 14:56:47 +0800508 print("%s. %s" % (self.current_step_index, self.steps[self.current_step_index]))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000509
510 def _record_result(self, result):
Chase Qic69235d2017-05-23 14:56:47 +0800511 print("Recording %s in %s/stdout.log" % (result, self.result_path))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000512 with open("%s/stdout.log" % self.result_path, "a") as f:
513 f.write("<LAVA_SIGNAL_TESTCASE TEST_CASE_ID=%s RESULT=%s>" %
514 (self.test_dict['metadata']['name'], result))
515
516 def do_pass(self, line):
517 """
518 Records PASS as test result
519 """
520 self.result = "pass"
521 self._record_result(self.result)
522 return True
523
524 def do_fail(self, line):
525 """
526 Records FAIL as test result
527 """
528 self.result = "fail"
529 self._record_result(self.result)
530 return True
531
532 def do_skip(self, line):
533 """
534 Records SKIP as test result
535 """
536 self.result = "skip"
537 self._record_result(self.result)
538 return True
539
540
541class ManualTestRun(TestRun, cmd.Cmd):
542 def run(self):
Chase Qic69235d2017-05-23 14:56:47 +0800543 print(self.test['test_name'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000544 with open('%s/testdef.yaml' % self.test['test_path'], 'r') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000545 self.testdef = yaml.safe_load(f)
546
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000547 ManualTestShell(self.testdef, self.test['test_path']).cmdloop()
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000548
549 def check_result(self):
550 pass
551
552
Dan Ruea9eb01c2017-06-07 16:29:09 -0500553def get_packages(linux_distribution, target=None):
554 """ Return a list of installed packages with versions
555
556 linux_distribution is a string that may be 'debian',
557 'ubuntu', 'centos', or 'fedora'.
558
559 For example (ubuntu):
560 'packages': ['acl-2.2.52-2',
561 'adduser-3.113+nmu3',
562 ...
563 'zlib1g:amd64-1:1.2.8.dfsg-2+b1',
564 'zlib1g-dev:amd64-1:1.2.8.dfsg-2+b1']
565
566 (centos):
567 "packages": ["acl-2.2.51-12.el7",
568 "apr-1.4.8-3.el7",
569 ...
570 "zlib-1.2.7-17.el7",
571 "zlib-devel-1.2.7-17.el7"
572 ]
573 """
574
575 logger = logging.getLogger('RUNNER.get_packages')
576 packages = []
577 if linux_distribution in ['debian', 'ubuntu']:
578 # Debian (apt) based system
579 packages = run_command("dpkg-query -W -f '${package}-${version}\n'", target).splitlines()
580
581 elif linux_distribution in ['centos', 'fedora']:
582 # RedHat (rpm) based system
583 packages = run_command("rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\n'", target).splitlines()
584 else:
585 logger.warning("Unknown linux distribution '{}'; package list not populated.".format(linux_distribution))
586
587 packages.sort()
588 return packages
589
590
591def get_environment(target=None, skip_collection=False):
592 """ Return a dictionary with environmental information
593
594 target: optional ssh host string to gather environment remotely.
595 skip_collection: Skip data collection and return an empty dictionary.
596
597 For example (on a HiSilicon D03):
598 {
599 "bios_version": "Hisilicon D03 UEFI 16.12 Release",
600 "board_name": "D03",
601 "board_vendor": "Huawei",
602 "kernel": "4.9.0-20.gitedc2a1c.linaro.aarch64",
603 "linux_distribution": "centos",
604 "packages": [
605 "GeoIP-1.5.0-11.el7",
606 "NetworkManager-1.4.0-20.el7_3",
607 ...
608 "yum-plugin-fastestmirror-1.1.31-40.el7",
609 "zlib-1.2.7-17.el7"
610 ],
611 "uname": "Linux localhost.localdomain 4.9.0-20.gitedc2a1c.linaro.aarch64 #1 SMP Wed Dec 14 17:50:15 UTC 2016 aarch64 aarch64 aarch64 GNU/Linux"
612 }
613 """
614
615 environment = {}
616 if skip_collection:
617 return environment
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100618 try:
619 environment['linux_distribution'] = run_command(
620 "grep ^ID= /etc/os-release", target).split('=')[-1].strip('"').lower()
621 except subprocess.CalledProcessError:
622 environment['linux_distribution'] = ""
623
624 try:
625 environment['kernel'] = run_command("uname -r", target)
626 except subprocess.CalledProcessError:
627 environment['kernel'] = ""
628
629 try:
630 environment['uname'] = run_command("uname -a", target)
631 except subprocess.CalledProcessError:
632 environment['uname'] = ""
Dan Ruea9eb01c2017-06-07 16:29:09 -0500633
634 try:
635 environment['bios_version'] = run_command(
636 "cat /sys/devices/virtual/dmi/id/bios_version", target)
637 except subprocess.CalledProcessError:
638 environment['bios_version'] = ""
639
640 try:
641 environment['board_vendor'] = run_command(
642 "cat /sys/devices/virtual/dmi/id/board_vendor", target)
643 except subprocess.CalledProcessError:
644 environment['board_vendor'] = ""
645
646 try:
647 environment['board_name'] = run_command(
648 "cat /sys/devices/virtual/dmi/id/board_name", target)
649 except subprocess.CalledProcessError:
650 environment['board_name'] = ""
651
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100652 try:
653 environment['packages'] = get_packages(environment['linux_distribution'], target)
654 except subprocess.CalledProcessError:
655 environment['packages'] = []
Dan Ruea9eb01c2017-06-07 16:29:09 -0500656 return environment
657
658
Chase Qi09edc7f2016-08-18 13:18:50 +0800659class ResultParser(object):
660 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000661 self.test = test
662 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800663 self.metrics = []
664 self.results = {}
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000665 self.results['test'] = test['test_name']
666 self.results['id'] = test['test_uuid']
Dan Ruea9eb01c2017-06-07 16:29:09 -0500667 self.results['test_plan'] = args.test_plan
668 self.results['environment'] = get_environment(
669 target=self.args.target, skip_collection=self.args.skip_environment)
Chase Qi09edc7f2016-08-18 13:18:50 +0800670 self.logger = logging.getLogger('RUNNER.ResultParser')
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000671 self.results['params'] = {}
Chase Qie94ba522017-05-26 12:05:18 +0800672 self.pattern = None
673 self.fixup = None
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530674 self.qa_reports_server = args.qa_reports_server
675 if args.qa_reports_token is not None:
676 self.qa_reports_token = args.qa_reports_token
677 else:
678 self.qa_reports_token = os.environ.get("QA_REPORTS_TOKEN", get_token_from_netrc(self.qa_reports_server))
679 self.qa_reports_project = args.qa_reports_project
680 self.qa_reports_group = args.qa_reports_group
681 self.qa_reports_env = args.qa_reports_env
682 self.qa_reports_build_version = args.qa_reports_build_version
683
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000684 with open(os.path.join(self.test['test_path'], "testdef.yaml"), "r") as f:
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000685 self.testdef = yaml.safe_load(f)
Chase Qi0d75a6b2016-11-29 23:43:54 +0800686 self.results['name'] = ""
687 if 'metadata' in self.testdef.keys() and \
688 'name' in self.testdef['metadata'].keys():
689 self.results['name'] = self.testdef['metadata']['name']
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000690 if 'params' in self.testdef.keys():
691 self.results['params'] = self.testdef['params']
Nicolas Dechesnefaa343d2017-10-23 00:33:10 +0200692 if self.args.test_def_params:
693 for param_name, param_value in self.args.test_def_params.items():
694 self.results['params'][param_name] = param_value
Chase Qie94ba522017-05-26 12:05:18 +0800695 if 'parse' in self.testdef.keys() and 'pattern' in self.testdef['parse'].keys():
696 self.pattern = self.testdef['parse']['pattern']
697 self.logger.info("Enabling log parse pattern: %s" % self.pattern)
698 if 'fixupdict' in self.testdef['parse'].keys():
699 self.fixup = self.testdef['parse']['fixupdict']
700 self.logger.info("Enabling log parse pattern fixup: %s" % self.fixup)
Chase Qiae88be32016-11-23 20:32:21 +0800701 if 'parameters' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000702 self.results['params'].update(test['parameters'])
Chase Qiae88be32016-11-23 20:32:21 +0800703 if 'params' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000704 self.results['params'].update(test['params'])
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000705 if 'version' in test.keys():
706 self.results['version'] = test['version']
707 else:
708 path = os.getcwd()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000709 os.chdir(self.test['test_path'])
Chase Qi3efa7692017-06-26 15:54:05 +0800710 if sys.version_info[0] < 3:
711 test_version = subprocess.check_output("git rev-parse HEAD", shell=True)
712 else:
713 test_version = subprocess.check_output("git rev-parse HEAD", shell=True).decode('utf-8')
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000714 self.results['version'] = test_version.rstrip()
715 os.chdir(path)
Chase Qiea543352017-09-21 16:44:30 +0800716 self.lava_run = args.lava_run
717 if self.lava_run and not find_executable('lava-test-case'):
718 self.logger.info("lava-test-case not found, '-l' or '--lava_run' option ignored'")
719 self.lava_run = False
Chase Qi09edc7f2016-08-18 13:18:50 +0800720
721 def run(self):
722 self.parse_stdout()
Chase Qie94ba522017-05-26 12:05:18 +0800723 if self.pattern:
724 self.parse_pattern()
725 # If 'metrics' is empty, add 'no-result-found fail'.
726 if not self.metrics:
727 self.metrics = [{'test_case_id': 'no-result-found', 'result': 'fail', 'measurement': '', 'units': ''}]
728 self.results['metrics'] = self.metrics
Chase Qi09edc7f2016-08-18 13:18:50 +0800729 self.dict_to_json()
730 self.dict_to_csv()
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530731 self.send_to_qa_reports()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000732 self.logger.info('Result files saved to: %s' % self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800733 print('--- Printing result.csv ---')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000734 with open('%s/result.csv' % self.test['test_path']) as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800735 print(f.read())
736
737 def parse_stdout(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000738 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000739 test_case_re = re.compile("TEST_CASE_ID=(.*)")
740 result_re = re.compile("RESULT=(.*)")
741 measurement_re = re.compile("MEASUREMENT=(.*)")
742 units_re = re.compile("UNITS=(.*)")
Chase Qi09edc7f2016-08-18 13:18:50 +0800743 for line in f:
744 if re.match(r'\<(|LAVA_SIGNAL_TESTCASE )TEST_CASE_ID=.*', line):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000745 line = line.strip('\n').strip('\r').strip('<>').split(' ')
Chase Qi09edc7f2016-08-18 13:18:50 +0800746 data = {'test_case_id': '',
747 'result': '',
748 'measurement': '',
749 'units': ''}
750
751 for string in line:
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000752 test_case_match = test_case_re.match(string)
753 result_match = result_re.match(string)
754 measurement_match = measurement_re.match(string)
755 units_match = units_re.match(string)
756 if test_case_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000757 data['test_case_id'] = test_case_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000758 if result_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000759 data['result'] = result_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000760 if measurement_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000761 data['measurement'] = measurement_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000762 if units_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000763 data['units'] = units_match.group(1)
Chase Qi09edc7f2016-08-18 13:18:50 +0800764
765 self.metrics.append(data.copy())
766
Chase Qiea543352017-09-21 16:44:30 +0800767 if self.lava_run:
768 self.send_to_lava(data)
769
Chase Qie94ba522017-05-26 12:05:18 +0800770 def parse_pattern(self):
771 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
Aníbal Limónc36cb792017-11-29 11:54:30 -0600772 rex_pattern = re.compile(r'%s' % self.pattern)
Chase Qie94ba522017-05-26 12:05:18 +0800773 for line in f:
774 data = {}
Aníbal Limónc36cb792017-11-29 11:54:30 -0600775 m = rex_pattern.search(line)
Chase Qie94ba522017-05-26 12:05:18 +0800776 if m:
777 data = m.groupdict()
778 for x in ['measurement', 'units']:
779 if x not in data:
780 data[x] = ''
Aníbal Limónbe78bec2017-11-28 17:08:44 -0600781 if self.fixup and data['result'] in self.fixup:
Chase Qie94ba522017-05-26 12:05:18 +0800782 data['result'] = self.fixup[data['result']]
Chase Qi1f2a9a02017-03-09 15:45:04 +0100783
Chase Qie94ba522017-05-26 12:05:18 +0800784 self.metrics.append(data.copy())
Chase Qi09edc7f2016-08-18 13:18:50 +0800785
Chase Qiea543352017-09-21 16:44:30 +0800786 if self.lava_run:
787 self.send_to_lava(data)
788
789 def send_to_lava(self, data):
790 cmd = 'lava-test-case {} --result {}'.format(data['test_case_id'], data['result'])
791 if data['measurement']:
792 cmd = '{} --measurement {} --units {}'.format(cmd, data['measurement'], data['units'])
793 self.logger.debug('lava-run: cmd: {}'.format(cmd))
794 subprocess.call(shlex.split(cmd))
795
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530796 def send_to_qa_reports(self):
797 if None in (self.qa_reports_server, self.qa_reports_token, self.qa_reports_group, self.qa_reports_project, self.qa_reports_build_version, self.qa_reports_env):
798 self.logger.warning("All parameters for qa reports are not set, results will not be pushed to qa reports")
799 return
800
801 SquadApi.configure(
802 url=self.qa_reports_server, token=self.qa_reports_token
803 )
804 tests = {}
805 metrics = {}
806 for metric in self.metrics:
807 if metric['measurement'] != "":
808 metrics["{}/{}".format(self.test['test_name'], metric['test_case_id'])] = metric['measurement']
809 else:
810 tests["{}/{}".format(self.test['test_name'], metric['test_case_id'])] = metric['result']
811
812 with open("{}/stdout.log".format(self.test['test_path']), "r") as logfile:
813 log = logfile.read()
814
815 submit_results(
816 group_project_slug="{}/{}".format(self.qa_reports_group, self.qa_reports_project),
817 build_version=self.qa_reports_build_version,
818 env_slug=self.qa_reports_env,
819 tests=tests,
820 metrics=metrics,
821 log=log,
822 metadata=self.testdef['metadata'],
823 attachments=None,
824 )
825 self.logger.info("Results pushed to QA Reports")
826
Chase Qi09edc7f2016-08-18 13:18:50 +0800827 def dict_to_json(self):
Chase Qi87f4f402016-11-07 15:32:01 +0800828 # Save test results to output/test_id/result.json
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000829 with open('%s/result.json' % self.test['test_path'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800830 json.dump([self.results], f, indent=4)
831
832 # Collect test results of all tests in output/result.json
833 feeds = []
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000834 if os.path.isfile('%s/result.json' % self.test['output']):
835 with open('%s/result.json' % self.test['output'], 'r') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800836 feeds = json.load(f)
837
838 feeds.append(self.results)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000839 with open('%s/result.json' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800840 json.dump(feeds, f, indent=4)
Chase Qi09edc7f2016-08-18 13:18:50 +0800841
842 def dict_to_csv(self):
Chase Qica15cf52016-11-10 17:00:22 +0800843 # Convert dict self.results['params'] to a string.
844 test_params = ''
845 if self.results['params']:
846 params_dict = self.results['params']
Chase Qic69235d2017-05-23 14:56:47 +0800847 test_params = ';'.join(['%s=%s' % (k, v) for k, v in params_dict.items()])
Chase Qi09edc7f2016-08-18 13:18:50 +0800848
Chase Qica15cf52016-11-10 17:00:22 +0800849 for metric in self.results['metrics']:
Chase Qi0d75a6b2016-11-29 23:43:54 +0800850 metric['name'] = self.results['name']
Chase Qica15cf52016-11-10 17:00:22 +0800851 metric['test_params'] = test_params
852
853 # Save test results to output/test_id/result.csv
Chase Qi0d75a6b2016-11-29 23:43:54 +0800854 fieldnames = ['name', 'test_case_id', 'result', 'measurement', 'units', 'test_params']
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000855 with open('%s/result.csv' % self.test['test_path'], 'w') as f:
Chase Qica15cf52016-11-10 17:00:22 +0800856 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800857 writer.writeheader()
858 for metric in self.results['metrics']:
859 writer.writerow(metric)
860
Chase Qi87f4f402016-11-07 15:32:01 +0800861 # Collect test results of all tests in output/result.csv
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000862 if not os.path.isfile('%s/result.csv' % self.test['output']):
863 with open('%s/result.csv' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800864 writer = csv.DictWriter(f, fieldnames=fieldnames)
865 writer.writeheader()
866
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000867 with open('%s/result.csv' % self.test['output'], 'a') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800868 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800869 for metric in self.results['metrics']:
Chase Qi09edc7f2016-08-18 13:18:50 +0800870 writer.writerow(metric)
871
872
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530873def get_token_from_netrc(qa_reports_server):
874 if qa_reports_server is None:
875 return
876 parse = urlparse(qa_reports_server)
877 netrc_local = netrc.netrc()
878 authTokens = netrc_local.authenticators("{}".format(parse.netloc))
879 if authTokens is not None:
880 hostname, username, authToken = authTokens
881 return authToken
882 # Unable to find Token hence returning None
883 return
884
885
Chase Qi09edc7f2016-08-18 13:18:50 +0800886def get_args():
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100887 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
Milosz Wasilewski855acbe2017-07-20 13:28:58 +0100888 parser.add_argument('-o', '--output', default=os.getenv("HOME", "") + '/output', dest='output',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100889 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800890 specify a directory to store test and result files.
Nicolas Dechesnef6c4c212017-01-18 17:30:04 +0100891 Default: $HOME/output
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100892 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800893 parser.add_argument('-p', '--test_plan', default=None, dest='test_plan',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100894 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800895 specify an test plan file which has tests and related
896 params listed in yaml format.
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100897 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800898 parser.add_argument('-d', '--test_def', default=None, dest='test_def',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100899 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800900 base on test definition repo location, specify relative
901 path to the test definition to run.
902 Format example: "ubuntu/smoke-tests-basic.yaml"
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100903 '''))
904 parser.add_argument('-r', '--test_def_params', default={}, dest='test_def_params',
905 action=StoreDictKeyPair, nargs="+", metavar="KEY=VALUE",
906 help=textwrap.dedent('''\
907 Set additional parameters when using test definition without
908 a test plan. The name values are set similarily to environment
909 variables:
910 --test_def_params KEY1=VALUE1 KEY2=VALUE2 ...
911 '''))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000912 parser.add_argument('-k', '--kind', default="automated", dest='kind',
913 choices=['automated', 'manual'],
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100914 help=textwrap.dedent('''\
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000915 Selects type of tests to be executed from the test plan.
916 Possible options: automated, manual
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100917 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800918 parser.add_argument('-t', '--timeout', type=int, default=None,
919 dest='timeout', help='Specify test timeout')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000920 parser.add_argument('-g', '--target', default=None,
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100921 dest='target', help=textwrap.dedent('''\
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000922 Specify SSH target to execute tests.
923 Format: user@host
924 Note: ssh authentication must be paswordless
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100925 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800926 parser.add_argument('-s', '--skip_install', dest='skip_install',
927 default=False, action='store_true',
928 help='skip install section defined in test definition.')
Dan Ruea9eb01c2017-06-07 16:29:09 -0500929 parser.add_argument('-e', '--skip_environment', dest='skip_environment',
930 default=False, action='store_true',
931 help='skip environmental data collection (board name, distro, etc)')
Chase Qiea543352017-09-21 16:44:30 +0800932 parser.add_argument('-l', '--lava_run', dest='lava_run',
933 default=False, action='store_true',
934 help='send test result to LAVA with lava-test-case.')
Chase Qia158efe2017-11-17 12:35:11 +0800935 parser.add_argument('-O', '--overlay', default=None,
936 dest='overlay', help=textwrap.dedent('''\
937 Specify test plan ovelay file to:
938 * skip tests
939 * amend test parameters
940 * add new tests
941 '''))
Chase Qi0e9b36e2017-12-07 16:13:44 +0800942 parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
943 default=False, help='Set log level to DEBUG.')
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530944 parser.add_argument(
945 "--qa-reports-server",
946 dest="qa_reports_server",
947 default=None,
948 help="qa reports server where the results have to be sent",
949 )
950 parser.add_argument(
951 "--qa-reports-token",
952 dest="qa_reports_token",
953 default=None,
954 help="qa reports token to upload the results to qa_reports_server",
955 )
956 parser.add_argument(
957 "--qa-reports-project",
958 dest="qa_reports_project",
959 default=None,
960 help="qa reports projects to which the results have to be uploaded",
961 )
962 parser.add_argument(
963 "--qa-reports-group",
964 dest="qa_reports_group",
965 default=None,
966 help="qa reports group in which the results have to be stored",
967 )
968 parser.add_argument(
969 "--qa-reports-env",
970 dest="qa_reports_env",
971 default=None,
972 help="qa reports environment for the results that have to be stored",
973 )
974 parser.add_argument(
975 "--qa-reports-build-version",
976 dest="qa_reports_build_version",
977 default=None,
978 help="qa reports build id for the result set",
979 )
Chase Qi09edc7f2016-08-18 13:18:50 +0800980 args = parser.parse_args()
981 return args
982
983
984def main():
Chase Qi0e9b36e2017-12-07 16:13:44 +0800985 args = get_args()
986
Chase Qi09edc7f2016-08-18 13:18:50 +0800987 # Setup logger.
988 logger = logging.getLogger('RUNNER')
Chase Qi0e9b36e2017-12-07 16:13:44 +0800989 logger.setLevel(logging.INFO)
990 if args.verbose:
991 logger.setLevel(logging.DEBUG)
Chase Qi09edc7f2016-08-18 13:18:50 +0800992 ch = logging.StreamHandler()
993 ch.setLevel(logging.DEBUG)
994 formatter = logging.Formatter('%(asctime)s - %(name)s: %(levelname)s: %(message)s')
995 ch.setFormatter(formatter)
996 logger.addHandler(ch)
997
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000998 logger.debug('Test job arguments: %s' % args)
999 if args.kind != "manual" and args.target is None:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001000 if os.geteuid() != 0:
1001 logger.error("Sorry, you need to run this as root")
1002 sys.exit(1)
Chase Qi09edc7f2016-08-18 13:18:50 +08001003
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001004 # Validate target argument format and connectivity.
1005 if args.target:
1006 rex = re.compile('.+@.+')
1007 if not rex.match(args.target):
1008 logger.error('Usage: -g username@host')
1009 sys.exit(1)
1010 if pexpect.which('ssh') is None:
1011 logger.error('openssh client must be installed on the host.')
1012 sys.exit(1)
1013 try:
Dan Ruea9eb01c2017-06-07 16:29:09 -05001014 run_command("exit", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001015 except subprocess.CalledProcessError as e:
1016 logger.error('ssh login failed.')
1017 print(e)
1018 sys.exit(1)
1019
Chase Qi09edc7f2016-08-18 13:18:50 +08001020 # Generate test plan.
Chase Qi09edc7f2016-08-18 13:18:50 +08001021 test_plan = TestPlan(args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001022 test_list = test_plan.test_list(args.kind)
Chase Qi09edc7f2016-08-18 13:18:50 +08001023 logger.info('Tests to run:')
1024 for test in test_list:
1025 print(test)
1026
1027 # Run tests.
1028 for test in test_list:
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001029 # Set and save test params to test dictionary.
1030 test['test_name'] = os.path.splitext(test['path'].split('/')[-1])[0]
1031 test['test_uuid'] = '%s_%s' % (test['test_name'], test['uuid'])
1032 test['output'] = os.path.realpath(args.output)
1033 if args.target is not None and '-o' not in sys.argv:
1034 test['output'] = os.path.join(test['output'], args.target)
1035 test['test_path'] = os.path.join(test['output'], test['test_uuid'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001036 if args.target is not None:
Chase Qi204b5422017-04-06 11:01:58 +08001037 # Get relative directory path of yaml file for partial file copy.
1038 # '-d' takes any relative paths to the yaml file, so get the realpath first.
1039 tc_realpath = os.path.realpath(test['path'])
1040 tc_dirname = os.path.dirname(tc_realpath)
1041 test['tc_relative_dir'] = '%s%s' % (args.kind, tc_dirname.split(args.kind)[1])
Dan Ruea9eb01c2017-06-07 16:29:09 -05001042 target_user_home = run_command("echo $HOME", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001043 test['target_test_path'] = '%s/output/%s' % (target_user_home, test['test_uuid'])
1044 logger.debug('Test parameters: %s' % test)
1045
Chase Qi09edc7f2016-08-18 13:18:50 +08001046 # Create directories and copy files needed.
1047 setup = TestSetup(test, args)
1048 setup.create_dir()
1049 setup.copy_test_repo()
Milosz Wasilewski970431b2016-11-25 14:10:08 +00001050 setup.checkout_version()
Chase Qi09edc7f2016-08-18 13:18:50 +08001051 setup.create_uuid_file()
1052
1053 # Convert test definition.
1054 test_def = TestDefinition(test, args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001055 if test_def.exists:
1056 test_def.definition()
1057 test_def.metadata()
Nicolas Dechesne51b85a82017-02-04 00:45:48 +01001058 test_def.mkrun()
Chase Qi09edc7f2016-08-18 13:18:50 +08001059
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001060 # Run test.
Nicolas Dechesne51b85a82017-02-04 00:45:48 +01001061 test_def.run()
Chase Qi09edc7f2016-08-18 13:18:50 +08001062
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001063 # Parse test output, save results in json and csv format.
1064 result_parser = ResultParser(test, args)
1065 result_parser.run()
1066 else:
1067 logger.warning("Requested test definition %s doesn't exist" % test['path'])
Chase Qi09edc7f2016-08-18 13:18:50 +08001068
Dan Ruea9eb01c2017-06-07 16:29:09 -05001069
Chase Qi09edc7f2016-08-18 13:18:50 +08001070if __name__ == "__main__":
1071 main()