blob: f290ae8fb2653b9f1b5940b5cbac37fa1bfc1fb8 [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'
Anders Roxell42c32662021-02-09 12:28:31 +0100325 if def_param_name == 'yaml_line':
Chase Qi09edc7f2016-08-18 13:18:50 +0800326 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()):
Anders Roxell42c32662021-02-09 12:28:31 +0100330 if def_param_name == 'yaml_line':
Chase Qi09edc7f2016-08-18 13:18:50 +0800331 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()):
Anders Roxell42c32662021-02-09 12:28:31 +0100341 if param_name == 'yaml_line':
Chase Qi09edc7f2016-08-18 13:18:50 +0800342 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'])
Milosz Wasilewskid71f28a2020-06-10 20:05:13 +0100377 self.child = pexpect.spawnu('/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')
Milosz Wasilewskid71f28a2020-06-10 20:05:13 +0100392 print(self.child.before)
Chase Qi09edc7f2016-08-18 13:18:50 +0800393 except pexpect.TIMEOUT:
394 continue
395 except pexpect.EOF:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000396 self.logger.info('%s test finished.\n' % self.test['test_uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800397 break
398
399
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000400class RemoteTestRun(AutomatedTestRun):
401 def copy_to_target(self):
402 os.chdir(self.test['test_path'])
403 tarball_name = "target-test-files.tar"
Dan Ruea9eb01c2017-06-07 16:29:09 -0500404
405 self.logger.info("Archiving test files")
406 run_command(
407 'tar -caf %s run.sh uuid automated/lib automated/bin automated/utils %s' %
408 (tarball_name, self.test['tc_relative_dir']))
409
410 self.logger.info("Creating test path")
411 run_command("mkdir -p %s" % (self.test['target_test_path']), self.args.target)
412
413 self.logger.info("Copying test archive to target host")
414 run_command('scp %s ./%s %s:%s' % (SSH_PARAMS, tarball_name, self.args.target,
415 self.test['target_test_path']))
416
417 self.logger.info("Unarchiving test files on target")
418 run_command("cd %s && tar -xf %s" % (self.test['target_test_path'],
419 tarball_name), self.args.target)
420
421 self.logger.info("Removing test file archive from target")
422 run_command("rm %s/%s" % (self.test['target_test_path'],
423 tarball_name), self.args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000424
425 def run(self):
426 self.copy_to_target()
427 self.logger.info('Executing %s/run.sh remotely on %s' % (self.test['target_test_path'], self.args.target))
428 shell_cmd = 'ssh %s %s "%s/run.sh 2>&1"' % (SSH_PARAMS, self.args.target, self.test['target_test_path'])
429 self.logger.debug('shell_cmd: %s' % shell_cmd)
430 output = open("%s/stdout.log" % self.test['test_path'], "w")
Milosz Wasilewskid71f28a2020-06-10 20:05:13 +0100431 self.child = pexpect.spawnu(shell_cmd)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000432 self.child.logfile = output
433 self.check_result()
434
435
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000436class ManualTestShell(cmd.Cmd):
Nicolas Dechesne1945d3f2020-10-07 23:36:34 +0200437 def __init__(self, test_dict, result_path, test_case_id):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000438 cmd.Cmd.__init__(self)
439 self.test_dict = test_dict
Nicolas Dechesne1945d3f2020-10-07 23:36:34 +0200440 self.test_case_id = test_case_id
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000441 self.result_path = result_path
442 self.current_step_index = 0
443 self.steps = self.test_dict['run']['steps']
444 self.expected = self.test_dict['run']['expected']
Nicolas Dechesne1945d3f2020-10-07 23:36:34 +0200445 self.prompt = "%s[%s] > " % (self.test_dict['metadata']['name'], self.test_case_id)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000446 self.result = None
447 self.intro = """
448 Welcome to manual test executor. Type 'help' for available commands.
449 This shell is meant to be executed on your computer, not on the system
450 under test. Please execute the steps from the test case, compare to
451 expected result and record the test result as 'pass' or 'fail'. If there
452 is an issue that prevents from executing the step, please record the result
453 as 'skip'.
454 """
455
456 def do_quit(self, line):
457 """
458 Exit test execution
459 """
460 if self.result is not None:
461 return True
462 if line.find("-f") >= 0:
463 self._record_result("skip")
464 return True
Chase Qic69235d2017-05-23 14:56:47 +0800465 print("Test result not recorded. Use -f to force. Forced quit records result as 'skip'")
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000466
467 do_EOF = do_quit
468
469 def do_description(self, line):
470 """
471 Prints current test overall description
472 """
Chase Qic69235d2017-05-23 14:56:47 +0800473 print(self.test_dict['metadata']['description'])
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000474
475 def do_steps(self, line):
476 """
477 Prints all steps of the current test case
478 """
479 for index, step in enumerate(self.steps):
Chase Qic69235d2017-05-23 14:56:47 +0800480 print("%s. %s" % (index, step))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000481
482 def do_expected(self, line):
483 """
484 Prints all expected results of the current test case
485 """
486 for index, expected in enumerate(self.expected):
Chase Qic69235d2017-05-23 14:56:47 +0800487 print("%s. %s" % (index, expected))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000488
489 def do_current(self, line):
490 """
491 Prints current test step
492 """
493 self._print_step()
494
495 do_start = do_current
496
497 def do_next(self, line):
498 """
499 Prints next test step
500 """
501 if len(self.steps) > self.current_step_index + 1:
502 self.current_step_index += 1
503 self._print_step()
504
505 def _print_step(self):
Chase Qic69235d2017-05-23 14:56:47 +0800506 print("%s. %s" % (self.current_step_index, self.steps[self.current_step_index]))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000507
508 def _record_result(self, result):
Chase Qic69235d2017-05-23 14:56:47 +0800509 print("Recording %s in %s/stdout.log" % (result, self.result_path))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000510 with open("%s/stdout.log" % self.result_path, "a") as f:
511 f.write("<LAVA_SIGNAL_TESTCASE TEST_CASE_ID=%s RESULT=%s>" %
Nicolas Dechesne1945d3f2020-10-07 23:36:34 +0200512 (self.test_case_id, result))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000513
514 def do_pass(self, line):
515 """
516 Records PASS as test result
517 """
518 self.result = "pass"
519 self._record_result(self.result)
520 return True
521
522 def do_fail(self, line):
523 """
524 Records FAIL as test result
525 """
526 self.result = "fail"
527 self._record_result(self.result)
528 return True
529
530 def do_skip(self, line):
531 """
532 Records SKIP as test result
533 """
534 self.result = "skip"
535 self._record_result(self.result)
536 return True
537
538
539class ManualTestRun(TestRun, cmd.Cmd):
540 def run(self):
Chase Qic69235d2017-05-23 14:56:47 +0800541 print(self.test['test_name'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000542 with open('%s/testdef.yaml' % self.test['test_path'], 'r') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000543 self.testdef = yaml.safe_load(f)
544
Nicolas Dechesne1945d3f2020-10-07 23:36:34 +0200545 if 'name' in self.test:
546 test_case_id = self.test['name']
547 else:
548 test_case_id = self.testdef['metadata']['name']
549
550 ManualTestShell(self.testdef, self.test['test_path'], test_case_id).cmdloop()
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000551
552 def check_result(self):
553 pass
554
555
Dan Ruea9eb01c2017-06-07 16:29:09 -0500556def get_packages(linux_distribution, target=None):
557 """ Return a list of installed packages with versions
558
559 linux_distribution is a string that may be 'debian',
560 'ubuntu', 'centos', or 'fedora'.
561
562 For example (ubuntu):
563 'packages': ['acl-2.2.52-2',
564 'adduser-3.113+nmu3',
565 ...
566 'zlib1g:amd64-1:1.2.8.dfsg-2+b1',
567 'zlib1g-dev:amd64-1:1.2.8.dfsg-2+b1']
568
569 (centos):
570 "packages": ["acl-2.2.51-12.el7",
571 "apr-1.4.8-3.el7",
572 ...
573 "zlib-1.2.7-17.el7",
574 "zlib-devel-1.2.7-17.el7"
575 ]
576 """
577
578 logger = logging.getLogger('RUNNER.get_packages')
579 packages = []
580 if linux_distribution in ['debian', 'ubuntu']:
581 # Debian (apt) based system
582 packages = run_command("dpkg-query -W -f '${package}-${version}\n'", target).splitlines()
583
584 elif linux_distribution in ['centos', 'fedora']:
585 # RedHat (rpm) based system
586 packages = run_command("rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\n'", target).splitlines()
587 else:
588 logger.warning("Unknown linux distribution '{}'; package list not populated.".format(linux_distribution))
589
590 packages.sort()
591 return packages
592
593
594def get_environment(target=None, skip_collection=False):
595 """ Return a dictionary with environmental information
596
597 target: optional ssh host string to gather environment remotely.
598 skip_collection: Skip data collection and return an empty dictionary.
599
600 For example (on a HiSilicon D03):
601 {
602 "bios_version": "Hisilicon D03 UEFI 16.12 Release",
603 "board_name": "D03",
604 "board_vendor": "Huawei",
605 "kernel": "4.9.0-20.gitedc2a1c.linaro.aarch64",
606 "linux_distribution": "centos",
607 "packages": [
608 "GeoIP-1.5.0-11.el7",
609 "NetworkManager-1.4.0-20.el7_3",
610 ...
611 "yum-plugin-fastestmirror-1.1.31-40.el7",
612 "zlib-1.2.7-17.el7"
613 ],
614 "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"
615 }
616 """
617
618 environment = {}
619 if skip_collection:
620 return environment
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100621 try:
622 environment['linux_distribution'] = run_command(
623 "grep ^ID= /etc/os-release", target).split('=')[-1].strip('"').lower()
624 except subprocess.CalledProcessError:
625 environment['linux_distribution'] = ""
626
627 try:
628 environment['kernel'] = run_command("uname -r", target)
629 except subprocess.CalledProcessError:
630 environment['kernel'] = ""
631
632 try:
633 environment['uname'] = run_command("uname -a", target)
634 except subprocess.CalledProcessError:
635 environment['uname'] = ""
Dan Ruea9eb01c2017-06-07 16:29:09 -0500636
637 try:
638 environment['bios_version'] = run_command(
639 "cat /sys/devices/virtual/dmi/id/bios_version", target)
640 except subprocess.CalledProcessError:
641 environment['bios_version'] = ""
642
643 try:
644 environment['board_vendor'] = run_command(
645 "cat /sys/devices/virtual/dmi/id/board_vendor", target)
646 except subprocess.CalledProcessError:
647 environment['board_vendor'] = ""
648
649 try:
650 environment['board_name'] = run_command(
651 "cat /sys/devices/virtual/dmi/id/board_name", target)
652 except subprocess.CalledProcessError:
653 environment['board_name'] = ""
654
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100655 try:
656 environment['packages'] = get_packages(environment['linux_distribution'], target)
657 except subprocess.CalledProcessError:
658 environment['packages'] = []
Dan Ruea9eb01c2017-06-07 16:29:09 -0500659 return environment
660
661
Chase Qi09edc7f2016-08-18 13:18:50 +0800662class ResultParser(object):
663 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000664 self.test = test
665 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800666 self.metrics = []
667 self.results = {}
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000668 self.results['test'] = test['test_name']
669 self.results['id'] = test['test_uuid']
Dan Ruea9eb01c2017-06-07 16:29:09 -0500670 self.results['test_plan'] = args.test_plan
671 self.results['environment'] = get_environment(
672 target=self.args.target, skip_collection=self.args.skip_environment)
Chase Qi09edc7f2016-08-18 13:18:50 +0800673 self.logger = logging.getLogger('RUNNER.ResultParser')
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000674 self.results['params'] = {}
Chase Qie94ba522017-05-26 12:05:18 +0800675 self.pattern = None
676 self.fixup = None
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530677 self.qa_reports_server = args.qa_reports_server
678 if args.qa_reports_token is not None:
679 self.qa_reports_token = args.qa_reports_token
680 else:
681 self.qa_reports_token = os.environ.get("QA_REPORTS_TOKEN", get_token_from_netrc(self.qa_reports_server))
682 self.qa_reports_project = args.qa_reports_project
683 self.qa_reports_group = args.qa_reports_group
684 self.qa_reports_env = args.qa_reports_env
685 self.qa_reports_build_version = args.qa_reports_build_version
Milosz Wasilewskib4b40de2020-06-11 12:54:18 +0100686 self.qa_reports_disable_metadata = args.qa_reports_disable_metadata
Milosz Wasilewski37848d32020-06-11 14:33:43 +0100687 self.qa_reports_metadata = args.qa_reports_metadata
Milosz Wasilewskiabe2dcc2020-06-12 10:54:46 +0100688 self.qa_reports_metadata_file = args.qa_reports_metadata_file
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530689
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000690 with open(os.path.join(self.test['test_path'], "testdef.yaml"), "r") as f:
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000691 self.testdef = yaml.safe_load(f)
Chase Qi0d75a6b2016-11-29 23:43:54 +0800692 self.results['name'] = ""
693 if 'metadata' in self.testdef.keys() and \
694 'name' in self.testdef['metadata'].keys():
695 self.results['name'] = self.testdef['metadata']['name']
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000696 if 'params' in self.testdef.keys():
697 self.results['params'] = self.testdef['params']
Nicolas Dechesnefaa343d2017-10-23 00:33:10 +0200698 if self.args.test_def_params:
699 for param_name, param_value in self.args.test_def_params.items():
700 self.results['params'][param_name] = param_value
Chase Qie94ba522017-05-26 12:05:18 +0800701 if 'parse' in self.testdef.keys() and 'pattern' in self.testdef['parse'].keys():
702 self.pattern = self.testdef['parse']['pattern']
703 self.logger.info("Enabling log parse pattern: %s" % self.pattern)
704 if 'fixupdict' in self.testdef['parse'].keys():
705 self.fixup = self.testdef['parse']['fixupdict']
706 self.logger.info("Enabling log parse pattern fixup: %s" % self.fixup)
Chase Qiae88be32016-11-23 20:32:21 +0800707 if 'parameters' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000708 self.results['params'].update(test['parameters'])
Chase Qiae88be32016-11-23 20:32:21 +0800709 if 'params' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000710 self.results['params'].update(test['params'])
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000711 if 'version' in test.keys():
712 self.results['version'] = test['version']
713 else:
714 path = os.getcwd()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000715 os.chdir(self.test['test_path'])
Chase Qi3efa7692017-06-26 15:54:05 +0800716 if sys.version_info[0] < 3:
717 test_version = subprocess.check_output("git rev-parse HEAD", shell=True)
718 else:
719 test_version = subprocess.check_output("git rev-parse HEAD", shell=True).decode('utf-8')
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000720 self.results['version'] = test_version.rstrip()
721 os.chdir(path)
Chase Qiea543352017-09-21 16:44:30 +0800722 self.lava_run = args.lava_run
723 if self.lava_run and not find_executable('lava-test-case'):
724 self.logger.info("lava-test-case not found, '-l' or '--lava_run' option ignored'")
725 self.lava_run = False
Chase Qi09edc7f2016-08-18 13:18:50 +0800726
727 def run(self):
728 self.parse_stdout()
Chase Qie94ba522017-05-26 12:05:18 +0800729 if self.pattern:
730 self.parse_pattern()
731 # If 'metrics' is empty, add 'no-result-found fail'.
732 if not self.metrics:
733 self.metrics = [{'test_case_id': 'no-result-found', 'result': 'fail', 'measurement': '', 'units': ''}]
734 self.results['metrics'] = self.metrics
Chase Qi09edc7f2016-08-18 13:18:50 +0800735 self.dict_to_json()
736 self.dict_to_csv()
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530737 self.send_to_qa_reports()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000738 self.logger.info('Result files saved to: %s' % self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800739 print('--- Printing result.csv ---')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000740 with open('%s/result.csv' % self.test['test_path']) as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800741 print(f.read())
742
743 def parse_stdout(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000744 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000745 test_case_re = re.compile("TEST_CASE_ID=(.*)")
746 result_re = re.compile("RESULT=(.*)")
747 measurement_re = re.compile("MEASUREMENT=(.*)")
748 units_re = re.compile("UNITS=(.*)")
Chase Qi09edc7f2016-08-18 13:18:50 +0800749 for line in f:
750 if re.match(r'\<(|LAVA_SIGNAL_TESTCASE )TEST_CASE_ID=.*', line):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000751 line = line.strip('\n').strip('\r').strip('<>').split(' ')
Chase Qi09edc7f2016-08-18 13:18:50 +0800752 data = {'test_case_id': '',
753 'result': '',
754 'measurement': '',
755 'units': ''}
756
757 for string in line:
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000758 test_case_match = test_case_re.match(string)
759 result_match = result_re.match(string)
760 measurement_match = measurement_re.match(string)
761 units_match = units_re.match(string)
762 if test_case_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000763 data['test_case_id'] = test_case_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000764 if result_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000765 data['result'] = result_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000766 if measurement_match:
Nicolas Dechesne508a2272020-10-03 11:10:37 +0200767 try:
768 data['measurement'] = float(measurement_match.group(1))
769 except ValueError as e:
770 pass
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000771 if units_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000772 data['units'] = units_match.group(1)
Chase Qi09edc7f2016-08-18 13:18:50 +0800773
774 self.metrics.append(data.copy())
775
Chase Qiea543352017-09-21 16:44:30 +0800776 if self.lava_run:
777 self.send_to_lava(data)
778
Chase Qie94ba522017-05-26 12:05:18 +0800779 def parse_pattern(self):
780 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
Aníbal Limónc36cb792017-11-29 11:54:30 -0600781 rex_pattern = re.compile(r'%s' % self.pattern)
Chase Qie94ba522017-05-26 12:05:18 +0800782 for line in f:
783 data = {}
Aníbal Limónc36cb792017-11-29 11:54:30 -0600784 m = rex_pattern.search(line)
Chase Qie94ba522017-05-26 12:05:18 +0800785 if m:
786 data = m.groupdict()
787 for x in ['measurement', 'units']:
788 if x not in data:
789 data[x] = ''
Aníbal Limónbe78bec2017-11-28 17:08:44 -0600790 if self.fixup and data['result'] in self.fixup:
Chase Qie94ba522017-05-26 12:05:18 +0800791 data['result'] = self.fixup[data['result']]
Chase Qi1f2a9a02017-03-09 15:45:04 +0100792
Chase Qie94ba522017-05-26 12:05:18 +0800793 self.metrics.append(data.copy())
Chase Qi09edc7f2016-08-18 13:18:50 +0800794
Chase Qiea543352017-09-21 16:44:30 +0800795 if self.lava_run:
796 self.send_to_lava(data)
797
798 def send_to_lava(self, data):
799 cmd = 'lava-test-case {} --result {}'.format(data['test_case_id'], data['result'])
800 if data['measurement']:
801 cmd = '{} --measurement {} --units {}'.format(cmd, data['measurement'], data['units'])
802 self.logger.debug('lava-run: cmd: {}'.format(cmd))
803 subprocess.call(shlex.split(cmd))
804
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530805 def send_to_qa_reports(self):
806 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):
807 self.logger.warning("All parameters for qa reports are not set, results will not be pushed to qa reports")
808 return
809
810 SquadApi.configure(
811 url=self.qa_reports_server, token=self.qa_reports_token
812 )
813 tests = {}
814 metrics = {}
815 for metric in self.metrics:
816 if metric['measurement'] != "":
817 metrics["{}/{}".format(self.test['test_name'], metric['test_case_id'])] = metric['measurement']
818 else:
819 tests["{}/{}".format(self.test['test_name'], metric['test_case_id'])] = metric['result']
820
821 with open("{}/stdout.log".format(self.test['test_path']), "r") as logfile:
822 log = logfile.read()
823
Milosz Wasilewskiabe2dcc2020-06-12 10:54:46 +0100824 metadata = {}
Milosz Wasilewskib00ceff2020-06-15 13:48:01 +0100825 if not self.qa_reports_disable_metadata:
826 if self.qa_reports_metadata:
827 metadata.update(self.qa_reports_metadata)
828 if self.qa_reports_metadata_file:
829 try:
830 with open(self.qa_reports_metadata_file, "r") as metadata_file:
831 loaded_metadata = yaml.load(metadata_file, Loader=yaml.SafeLoader)
832 # check if loaded metadata is key=value and both are strings
833 for key, value in loaded_metadata.items():
834 if type(key) == str and type(value) == str:
835 # only update metadata with simple keys
836 # ignore all other items in the dictionary
837 metadata.update({key: value})
838 else:
839 self.logger.warning("Ignoring key: %s" % key)
840 except FileNotFoundError:
841 self.logger.warning("Metadata file not found")
842 except PermissionError:
843 self.logger.warning("Insufficient permissions to open metadata file")
Milosz Wasilewskif883f102020-06-11 12:46:58 +0100844 if submit_results(
845 group_project_slug="{}/{}".format(self.qa_reports_group, self.qa_reports_project),
846 build_version=self.qa_reports_build_version,
847 env_slug=self.qa_reports_env,
848 tests=tests,
849 metrics=metrics,
850 log=log,
Milosz Wasilewskib00ceff2020-06-15 13:48:01 +0100851 metadata=metadata,
Milosz Wasilewskif883f102020-06-11 12:46:58 +0100852 attachments=None):
853 self.logger.info("Results pushed to QA Reports")
854 else:
855 self.logger.warning("Results upload to QA Reports failed!")
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530856
Chase Qi09edc7f2016-08-18 13:18:50 +0800857 def dict_to_json(self):
Chase Qi87f4f402016-11-07 15:32:01 +0800858 # Save test results to output/test_id/result.json
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000859 with open('%s/result.json' % self.test['test_path'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800860 json.dump([self.results], f, indent=4)
861
862 # Collect test results of all tests in output/result.json
863 feeds = []
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000864 if os.path.isfile('%s/result.json' % self.test['output']):
865 with open('%s/result.json' % self.test['output'], 'r') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800866 feeds = json.load(f)
867
868 feeds.append(self.results)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000869 with open('%s/result.json' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800870 json.dump(feeds, f, indent=4)
Chase Qi09edc7f2016-08-18 13:18:50 +0800871
872 def dict_to_csv(self):
Chase Qica15cf52016-11-10 17:00:22 +0800873 # Convert dict self.results['params'] to a string.
874 test_params = ''
875 if self.results['params']:
876 params_dict = self.results['params']
Chase Qic69235d2017-05-23 14:56:47 +0800877 test_params = ';'.join(['%s=%s' % (k, v) for k, v in params_dict.items()])
Chase Qi09edc7f2016-08-18 13:18:50 +0800878
Chase Qica15cf52016-11-10 17:00:22 +0800879 for metric in self.results['metrics']:
Chase Qi0d75a6b2016-11-29 23:43:54 +0800880 metric['name'] = self.results['name']
Chase Qica15cf52016-11-10 17:00:22 +0800881 metric['test_params'] = test_params
882
883 # Save test results to output/test_id/result.csv
Chase Qi0d75a6b2016-11-29 23:43:54 +0800884 fieldnames = ['name', 'test_case_id', 'result', 'measurement', 'units', 'test_params']
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000885 with open('%s/result.csv' % self.test['test_path'], 'w') as f:
Chase Qica15cf52016-11-10 17:00:22 +0800886 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800887 writer.writeheader()
888 for metric in self.results['metrics']:
889 writer.writerow(metric)
890
Chase Qi87f4f402016-11-07 15:32:01 +0800891 # Collect test results of all tests in output/result.csv
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000892 if not os.path.isfile('%s/result.csv' % self.test['output']):
893 with open('%s/result.csv' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800894 writer = csv.DictWriter(f, fieldnames=fieldnames)
895 writer.writeheader()
896
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000897 with open('%s/result.csv' % self.test['output'], 'a') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800898 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800899 for metric in self.results['metrics']:
Chase Qi09edc7f2016-08-18 13:18:50 +0800900 writer.writerow(metric)
901
902
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530903def get_token_from_netrc(qa_reports_server):
904 if qa_reports_server is None:
905 return
906 parse = urlparse(qa_reports_server)
907 netrc_local = netrc.netrc()
908 authTokens = netrc_local.authenticators("{}".format(parse.netloc))
909 if authTokens is not None:
910 hostname, username, authToken = authTokens
911 return authToken
912 # Unable to find Token hence returning None
913 return
914
915
Chase Qi09edc7f2016-08-18 13:18:50 +0800916def get_args():
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100917 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
Milosz Wasilewski855acbe2017-07-20 13:28:58 +0100918 parser.add_argument('-o', '--output', default=os.getenv("HOME", "") + '/output', dest='output',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100919 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800920 specify a directory to store test and result files.
Nicolas Dechesnef6c4c212017-01-18 17:30:04 +0100921 Default: $HOME/output
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100922 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800923 parser.add_argument('-p', '--test_plan', default=None, dest='test_plan',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100924 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800925 specify an test plan file which has tests and related
926 params listed in yaml format.
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100927 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800928 parser.add_argument('-d', '--test_def', default=None, dest='test_def',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100929 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800930 base on test definition repo location, specify relative
931 path to the test definition to run.
932 Format example: "ubuntu/smoke-tests-basic.yaml"
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100933 '''))
934 parser.add_argument('-r', '--test_def_params', default={}, dest='test_def_params',
935 action=StoreDictKeyPair, nargs="+", metavar="KEY=VALUE",
936 help=textwrap.dedent('''\
937 Set additional parameters when using test definition without
938 a test plan. The name values are set similarily to environment
939 variables:
940 --test_def_params KEY1=VALUE1 KEY2=VALUE2 ...
941 '''))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000942 parser.add_argument('-k', '--kind', default="automated", dest='kind',
943 choices=['automated', 'manual'],
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100944 help=textwrap.dedent('''\
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000945 Selects type of tests to be executed from the test plan.
946 Possible options: automated, manual
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100947 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800948 parser.add_argument('-t', '--timeout', type=int, default=None,
949 dest='timeout', help='Specify test timeout')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000950 parser.add_argument('-g', '--target', default=None,
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100951 dest='target', help=textwrap.dedent('''\
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000952 Specify SSH target to execute tests.
953 Format: user@host
954 Note: ssh authentication must be paswordless
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100955 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800956 parser.add_argument('-s', '--skip_install', dest='skip_install',
957 default=False, action='store_true',
958 help='skip install section defined in test definition.')
Dan Ruea9eb01c2017-06-07 16:29:09 -0500959 parser.add_argument('-e', '--skip_environment', dest='skip_environment',
960 default=False, action='store_true',
961 help='skip environmental data collection (board name, distro, etc)')
Chase Qiea543352017-09-21 16:44:30 +0800962 parser.add_argument('-l', '--lava_run', dest='lava_run',
963 default=False, action='store_true',
964 help='send test result to LAVA with lava-test-case.')
Chase Qia158efe2017-11-17 12:35:11 +0800965 parser.add_argument('-O', '--overlay', default=None,
966 dest='overlay', help=textwrap.dedent('''\
967 Specify test plan ovelay file to:
968 * skip tests
969 * amend test parameters
970 * add new tests
971 '''))
Chase Qi0e9b36e2017-12-07 16:13:44 +0800972 parser.add_argument('-v', '--verbose', action='store_true', dest='verbose',
973 default=False, help='Set log level to DEBUG.')
Vishal Bhoje94da4a2020-05-15 12:22:40 +0530974 parser.add_argument(
975 "--qa-reports-server",
976 dest="qa_reports_server",
977 default=None,
978 help="qa reports server where the results have to be sent",
979 )
980 parser.add_argument(
981 "--qa-reports-token",
982 dest="qa_reports_token",
983 default=None,
984 help="qa reports token to upload the results to qa_reports_server",
985 )
986 parser.add_argument(
987 "--qa-reports-project",
988 dest="qa_reports_project",
989 default=None,
990 help="qa reports projects to which the results have to be uploaded",
991 )
992 parser.add_argument(
993 "--qa-reports-group",
994 dest="qa_reports_group",
995 default=None,
996 help="qa reports group in which the results have to be stored",
997 )
998 parser.add_argument(
999 "--qa-reports-env",
1000 dest="qa_reports_env",
1001 default=None,
1002 help="qa reports environment for the results that have to be stored",
1003 )
1004 parser.add_argument(
1005 "--qa-reports-build-version",
1006 dest="qa_reports_build_version",
1007 default=None,
1008 help="qa reports build id for the result set",
1009 )
Milosz Wasilewskib4b40de2020-06-11 12:54:18 +01001010 parser.add_argument(
1011 "--qa-reports-disable-metadata",
1012 dest="qa_reports_disable_metadata",
1013 default=False,
1014 action='store_true',
Milosz Wasilewski37848d32020-06-11 14:33:43 +01001015 help="Disable sending metadata to SQUAD. Default: false",
1016 )
1017 parser.add_argument(
1018 "--qa-reports-metadata",
1019 dest="qa_reports_metadata",
1020 default={},
1021 action=StoreDictKeyPair,
1022 nargs="+",
1023 metavar="KEY=VALUE",
1024 help="List of metadata key=value pairs to be sent to SQUAD",
Milosz Wasilewskib4b40de2020-06-11 12:54:18 +01001025 )
Milosz Wasilewskiabe2dcc2020-06-12 10:54:46 +01001026 parser.add_argument(
1027 "--qa-reports-metadata-file",
1028 dest="qa_reports_metadata_file",
1029 default=None,
1030 help="YAML file that defines metadata to be reported to SQUAD",
1031 )
Milosz Wasilewskib4b40de2020-06-11 12:54:18 +01001032
Chase Qi09edc7f2016-08-18 13:18:50 +08001033 args = parser.parse_args()
1034 return args
1035
1036
1037def main():
Chase Qi0e9b36e2017-12-07 16:13:44 +08001038 args = get_args()
1039
Chase Qi09edc7f2016-08-18 13:18:50 +08001040 # Setup logger.
1041 logger = logging.getLogger('RUNNER')
Chase Qi0e9b36e2017-12-07 16:13:44 +08001042 logger.setLevel(logging.INFO)
1043 if args.verbose:
1044 logger.setLevel(logging.DEBUG)
Chase Qi09edc7f2016-08-18 13:18:50 +08001045 ch = logging.StreamHandler()
1046 ch.setLevel(logging.DEBUG)
1047 formatter = logging.Formatter('%(asctime)s - %(name)s: %(levelname)s: %(message)s')
1048 ch.setFormatter(formatter)
1049 logger.addHandler(ch)
1050
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001051 logger.debug('Test job arguments: %s' % args)
1052 if args.kind != "manual" and args.target is None:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001053 if os.geteuid() != 0:
1054 logger.error("Sorry, you need to run this as root")
1055 sys.exit(1)
Chase Qi09edc7f2016-08-18 13:18:50 +08001056
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001057 # Validate target argument format and connectivity.
1058 if args.target:
1059 rex = re.compile('.+@.+')
1060 if not rex.match(args.target):
1061 logger.error('Usage: -g username@host')
1062 sys.exit(1)
1063 if pexpect.which('ssh') is None:
1064 logger.error('openssh client must be installed on the host.')
1065 sys.exit(1)
1066 try:
Dan Ruea9eb01c2017-06-07 16:29:09 -05001067 run_command("exit", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001068 except subprocess.CalledProcessError as e:
1069 logger.error('ssh login failed.')
1070 print(e)
1071 sys.exit(1)
1072
Chase Qi09edc7f2016-08-18 13:18:50 +08001073 # Generate test plan.
Chase Qi09edc7f2016-08-18 13:18:50 +08001074 test_plan = TestPlan(args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001075 test_list = test_plan.test_list(args.kind)
Chase Qi09edc7f2016-08-18 13:18:50 +08001076 logger.info('Tests to run:')
1077 for test in test_list:
1078 print(test)
1079
1080 # Run tests.
1081 for test in test_list:
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001082 # Set and save test params to test dictionary.
1083 test['test_name'] = os.path.splitext(test['path'].split('/')[-1])[0]
1084 test['test_uuid'] = '%s_%s' % (test['test_name'], test['uuid'])
1085 test['output'] = os.path.realpath(args.output)
1086 if args.target is not None and '-o' not in sys.argv:
1087 test['output'] = os.path.join(test['output'], args.target)
1088 test['test_path'] = os.path.join(test['output'], test['test_uuid'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001089 if args.target is not None:
Chase Qi204b5422017-04-06 11:01:58 +08001090 # Get relative directory path of yaml file for partial file copy.
1091 # '-d' takes any relative paths to the yaml file, so get the realpath first.
1092 tc_realpath = os.path.realpath(test['path'])
1093 tc_dirname = os.path.dirname(tc_realpath)
1094 test['tc_relative_dir'] = '%s%s' % (args.kind, tc_dirname.split(args.kind)[1])
Dan Ruea9eb01c2017-06-07 16:29:09 -05001095 target_user_home = run_command("echo $HOME", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +00001096 test['target_test_path'] = '%s/output/%s' % (target_user_home, test['test_uuid'])
1097 logger.debug('Test parameters: %s' % test)
1098
Chase Qi09edc7f2016-08-18 13:18:50 +08001099 # Create directories and copy files needed.
1100 setup = TestSetup(test, args)
1101 setup.create_dir()
1102 setup.copy_test_repo()
Milosz Wasilewski970431b2016-11-25 14:10:08 +00001103 setup.checkout_version()
Chase Qi09edc7f2016-08-18 13:18:50 +08001104 setup.create_uuid_file()
1105
1106 # Convert test definition.
1107 test_def = TestDefinition(test, args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001108 if test_def.exists:
1109 test_def.definition()
1110 test_def.metadata()
Nicolas Dechesne51b85a82017-02-04 00:45:48 +01001111 test_def.mkrun()
Chase Qi09edc7f2016-08-18 13:18:50 +08001112
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001113 # Run test.
Nicolas Dechesne51b85a82017-02-04 00:45:48 +01001114 test_def.run()
Chase Qi09edc7f2016-08-18 13:18:50 +08001115
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +00001116 # Parse test output, save results in json and csv format.
1117 result_parser = ResultParser(test, args)
1118 result_parser.run()
1119 else:
1120 logger.warning("Requested test definition %s doesn't exist" % test['path'])
Chase Qi09edc7f2016-08-18 13:18:50 +08001121
Dan Ruea9eb01c2017-06-07 16:29:09 -05001122
Chase Qi09edc7f2016-08-18 13:18:50 +08001123if __name__ == "__main__":
1124 main()