blob: 4632f848afd9fa3f53065c94fc5d6fb3d6587bc7 [file] [log] [blame]
Chase Qic69235d2017-05-23 14:56:47 +08001#!/usr/bin/env python
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
8import os
Chase Qi09edc7f2016-08-18 13:18:50 +08009import re
Milosz Wasilewski682120e2017-03-13 13:37:18 +000010import shlex
Chase Qi09edc7f2016-08-18 13:18:50 +080011import shutil
Milosz Wasilewski970431b2016-11-25 14:10:08 +000012import subprocess
Chase Qi09edc7f2016-08-18 13:18:50 +080013import sys
Milosz Wasilewski259ba192017-07-27 10:59:25 +010014import textwrap
Chase Qi09edc7f2016-08-18 13:18:50 +080015import time
Chase Qi09edc7f2016-08-18 13:18:50 +080016from uuid import uuid4
Chase Qiea543352017-09-21 16:44:30 +080017from distutils.spawn import find_executable
Chase Qi09edc7f2016-08-18 13:18:50 +080018
19
Chase Qifaf7d282016-08-29 19:34:01 +080020try:
21 import pexpect
22 import yaml
23except ImportError as e:
24 print(e)
25 print('Please run the below command to install modules required')
Chase Qic69235d2017-05-23 14:56:47 +080026 print('Python2: pip install -r ${REPO_PATH}/automated/utils/requirements.txt')
27 print('Python3: pip3 install -r ${REPO_PATH}/automated/utils/requirements.txt')
Chase Qifaf7d282016-08-29 19:34:01 +080028 sys.exit(1)
29
30
Milosz Wasilewski259ba192017-07-27 10:59:25 +010031class StoreDictKeyPair(argparse.Action):
32 def __init__(self, option_strings, dest, nargs=None, **kwargs):
33 self._nargs = nargs
34 super(StoreDictKeyPair, self).__init__(option_strings, dest, nargs=nargs, **kwargs)
35
36 def __call__(self, parser, namespace, values, option_string=None):
37 my_dict = {}
38 for kv in values:
Milosz Wasilewskif9c8c062017-08-02 16:10:40 +010039 if "=" in kv:
40 k, v = kv.split("=", 1)
41 my_dict[k] = v
42 else:
43 print("Invalid parameter: %s" % kv)
Milosz Wasilewski259ba192017-07-27 10:59:25 +010044 setattr(namespace, self.dest, my_dict)
45
46
Milosz Wasilewskidf71a762017-07-20 13:26:21 +010047# quit gracefully if the connection is closed by remote host
48SSH_PARAMS = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=5"
Milosz Wasilewski682120e2017-03-13 13:37:18 +000049
50
Dan Ruea9eb01c2017-06-07 16:29:09 -050051def run_command(command, target=None):
52 """ Run a shell command. If target is specified, ssh to the given target first. """
53
54 run = command
55 if target:
56 run = 'ssh {} {} "{}"'.format(SSH_PARAMS, target, command)
57
58 logger = logging.getLogger('RUNNER.run_command')
59 logger.debug(run)
Chase Qi3efa7692017-06-26 15:54:05 +080060 if sys.version_info[0] < 3:
61 return subprocess.check_output(shlex.split(run)).strip()
62 else:
63 return subprocess.check_output(shlex.split(run)).strip().decode('utf-8')
Milosz Wasilewski682120e2017-03-13 13:37:18 +000064
65
Chase Qi09edc7f2016-08-18 13:18:50 +080066class TestPlan(object):
67 """
68 Analysis args specified, then generate test plan.
69 """
70
71 def __init__(self, args):
Chase Qi09edc7f2016-08-18 13:18:50 +080072 self.test_def = args.test_def
73 self.test_plan = args.test_plan
74 self.timeout = args.timeout
75 self.skip_install = args.skip_install
76 self.logger = logging.getLogger('RUNNER.TestPlan')
Chase Qia158efe2017-11-17 12:35:11 +080077 self.overlay = args.overlay
78
79 def apply_overlay(self, test_list):
80 fixed_test_list = copy.deepcopy(test_list)
81 logger = logging.getLogger('RUNNER.TestPlan.Overlay')
82 with open(self.overlay) as f:
83 data = yaml.load(f)
84
85 if data.get('skip'):
86 skip_tests = data['skip']
87 for test in test_list:
88 for skip_test in skip_tests:
89 if test['path'] == skip_test['path'] and test['repository'] == skip_test['repository']:
90 fixed_test_list.remove(test)
91 logger.info("Skipped: {}".format(test))
92 else:
93 continue
94
95 if data.get('amend'):
96 amend_tests = data['amend']
97 for test in fixed_test_list:
98 for amend_test in amend_tests:
99 if test['path'] == amend_test['path'] and test['repository'] == skip_test['repository']:
100 if amend_test.get('parameters'):
101 if test.get('parameters'):
102 test['parameters'].update(amend_test['parameters'])
103 else:
104 test['parameters'] = amend_test['parameters']
105 logger.info('Updated: {}'.format(test))
106 else:
107 logger.warning("'parameters' not found in {}, nothing to amend.".format(amend_test))
108
109 if data.get('add'):
110 add_tests = data['add']
111 unique_add_tests = []
112 for test in add_tests:
113 if test not in unique_add_tests:
114 unique_add_tests.append(test)
115 else:
116 logger.warning("Skipping duplicate test {}".format(test))
117
118 for test in test_list:
119 del test['uuid']
120
121 for add_test in unique_add_tests:
122 if add_test in test_list:
123 logger.warning("{} already included in test plan, do nothing.".format(add_test))
124 else:
125 add_test['uuid'] = str(uuid4())
126 fixed_test_list.append(add_test)
127 logger.info("Added: {}".format(add_test))
128
129 return fixed_test_list
Chase Qi09edc7f2016-08-18 13:18:50 +0800130
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000131 def test_list(self, kind="automated"):
Chase Qi09edc7f2016-08-18 13:18:50 +0800132 if self.test_def:
133 if not os.path.exists(self.test_def):
134 self.logger.error(' %s NOT found, exiting...' % self.test_def)
135 sys.exit(1)
136
137 test_list = [{'path': self.test_def}]
138 test_list[0]['uuid'] = str(uuid4())
139 test_list[0]['timeout'] = self.timeout
140 test_list[0]['skip_install'] = self.skip_install
141 elif self.test_plan:
142 if not os.path.exists(self.test_plan):
143 self.logger.error(' %s NOT found, exiting...' % self.test_plan)
144 sys.exit(1)
145
146 with open(self.test_plan, 'r') as f:
147 test_plan = yaml.safe_load(f)
148 try:
Chase Qidca4fb62017-11-22 12:09:42 +0800149 plan_version = test_plan['metadata'].get('format')
150 self.logger.info('Test plan version: {}'.format(plan_version))
151 if plan_version == "Linaro Test Plan v2":
152 tests = test_plan['tests'][kind]
153 elif plan_version == "Linaro Test Plan v1" or plan_version is None:
154 tests = []
155 for requirement in test_plan['requirements']:
156 if 'tests' in requirement.keys():
157 if requirement['tests'] and \
158 kind in requirement['tests'].keys() and \
159 requirement['tests'][kind]:
160 for test in requirement['tests'][kind]:
161 tests.append(test)
162
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000163 test_list = []
Dan Rued9d7b652017-05-26 14:00:10 -0500164 unique_tests = [] # List of test hashes
Chase Qidca4fb62017-11-22 12:09:42 +0800165 for test in tests:
166 test_hash = hash(json.dumps(test, sort_keys=True))
167 if test_hash in unique_tests:
168 # Test is already in the test_list; don't add it again.
169 self.logger.warning("Skipping duplicate test {}".format(test))
170 continue
171 unique_tests.append(test_hash)
172 test_list.append(test)
Chase Qi09edc7f2016-08-18 13:18:50 +0800173 for test in test_list:
174 test['uuid'] = str(uuid4())
175 except KeyError as e:
176 self.logger.error("%s is missing from test plan" % str(e))
177 sys.exit(1)
178 else:
179 self.logger.error('Plese specify a test or test plan.')
180 sys.exit(1)
181
Chase Qia158efe2017-11-17 12:35:11 +0800182 if self.overlay is None:
183 return test_list
184 else:
185 return self.apply_overlay(test_list)
Chase Qi09edc7f2016-08-18 13:18:50 +0800186
187
188class TestSetup(object):
189 """
190 Create directories required, then copy files needed to these directories.
191 """
192
193 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000194 self.test = test
195 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800196 self.logger = logging.getLogger('RUNNER.TestSetup')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000197 self.test_kind = args.kind
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000198 self.test_version = test.get('version', None)
Chase Qi09edc7f2016-08-18 13:18:50 +0800199
200 def validate_env(self):
201 # Inspect if environment set properly.
202 try:
203 self.repo_path = os.environ['REPO_PATH']
204 except KeyError:
205 self.logger.error('KeyError: REPO_PATH')
206 self.logger.error("Please run '. ./bin/setenv.sh' to setup test environment")
207 sys.exit(1)
208
209 def create_dir(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000210 if not os.path.exists(self.test['output']):
211 os.makedirs(self.test['output'])
212 self.logger.info('Output directory created: %s' % self.test['output'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800213
214 def copy_test_repo(self):
215 self.validate_env()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000216 shutil.rmtree(self.test['test_path'], ignore_errors=True)
217 if self.repo_path in self.test['test_path']:
Chase Qi33eb7652016-12-02 10:43:46 +0800218 self.logger.error("Cannot copy repository into itself. Please choose output directory outside repository path")
219 sys.exit(1)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000220 shutil.copytree(self.repo_path, self.test['test_path'], symlinks=True)
221 self.logger.info('Test repo copied to: %s' % self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800222
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000223 def checkout_version(self):
224 if self.test_version:
225 path = os.getcwd()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000226 os.chdir(self.test['test_path'])
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000227 subprocess.call("git checkout %s" % self.test_version, shell=True)
228 os.chdir(path)
229
Chase Qi09edc7f2016-08-18 13:18:50 +0800230 def create_uuid_file(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000231 with open('%s/uuid' % self.test['test_path'], 'w') as f:
232 f.write(self.test['uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800233
234
235class TestDefinition(object):
236 """
237 Convert test definition to testdef.yaml, testdef_metadata and run.sh.
238 """
239
240 def __init__(self, test, args):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000241 self.test = test
242 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800243 self.logger = logging.getLogger('RUNNER.TestDef')
244 self.skip_install = args.skip_install
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000245 self.is_manual = False
Chase Qi09edc7f2016-08-18 13:18:50 +0800246 if 'skip_install' in test:
247 self.skip_install = test['skip_install']
248 self.custom_params = None
249 if 'parameters' in test:
250 self.custom_params = test['parameters']
251 if 'params' in test:
252 self.custom_params = test['params']
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000253 self.exists = False
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000254 if os.path.isfile(self.test['path']):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000255 self.exists = True
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000256 with open(self.test['path'], 'r') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000257 self.testdef = yaml.safe_load(f)
258 if self.testdef['metadata']['format'].startswith("Manual Test Definition"):
259 self.is_manual = True
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100260 if self.is_manual:
261 self.runner = ManualTestRun(test, args)
Chase Qi43bb9122017-05-23 14:37:48 +0800262 elif self.args.target is not None:
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100263 self.runner = RemoteTestRun(test, args)
Chase Qi43bb9122017-05-23 14:37:48 +0800264 else:
265 self.runner = AutomatedTestRun(test, args)
Chase Qi09edc7f2016-08-18 13:18:50 +0800266
267 def definition(self):
Chase Qic69235d2017-05-23 14:56:47 +0800268 with open('%s/testdef.yaml' % self.test['test_path'], 'wb') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800269 f.write(yaml.dump(self.testdef, encoding='utf-8', allow_unicode=True))
270
271 def metadata(self):
Chase Qic69235d2017-05-23 14:56:47 +0800272 with open('%s/testdef_metadata' % self.test['test_path'], 'wb') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800273 f.write(yaml.dump(self.testdef['metadata'], encoding='utf-8', allow_unicode=True))
274
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100275 def mkrun(self):
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000276 if not self.is_manual:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000277 with open('%s/run.sh' % self.test['test_path'], 'a') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000278 f.write('#!/bin/sh\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800279
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000280 self.parameters = self.handle_parameters()
281 if self.parameters:
282 for line in self.parameters:
283 f.write(line)
Chase Qi09edc7f2016-08-18 13:18:50 +0800284
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000285 f.write('set -e\n')
Chase Qif2762462017-03-28 17:01:10 +0800286 f.write('set -x\n')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000287 f.write('export TESTRUN_ID=%s\n' % self.testdef['metadata']['name'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000288 if self.args.target is None:
289 f.write('cd %s\n' % (self.test['test_path']))
290 else:
291 f.write('cd %s\n' % (self.test['target_test_path']))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000292 f.write('UUID=`cat uuid`\n')
293 f.write('echo "<STARTRUN $TESTRUN_ID $UUID>"\n')
Nicolas Dechesned67955a2017-03-14 09:41:04 +0100294 f.write('export PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin\n')
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000295 steps = self.testdef['run'].get('steps', [])
296 if steps:
Dan Rueb592da12017-06-07 16:32:43 -0500297 for step in steps:
298 command = step
299 if '--cmd' in step or '--shell' in step:
300 command = re.sub(r'\$(\d+)\b', r'\\$\1', step)
301 f.write('%s\n' % command)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000302 f.write('echo "<ENDRUN $TESTRUN_ID $UUID>"\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800303
Chase Qic69235d2017-05-23 14:56:47 +0800304 os.chmod('%s/run.sh' % self.test['test_path'], 0o755)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000305
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100306 def run(self):
307 self.runner.run()
Chase Qi09edc7f2016-08-18 13:18:50 +0800308
309 def handle_parameters(self):
310 ret_val = ['###default parameters from test definition###\n']
311
312 if 'params' in self.testdef:
313 for def_param_name, def_param_value in list(self.testdef['params'].items()):
314 # ?'yaml_line'
315 if def_param_name is 'yaml_line':
316 continue
317 ret_val.append('%s=\'%s\'\n' % (def_param_name, def_param_value))
318 elif 'parameters' in self.testdef:
319 for def_param_name, def_param_value in list(self.testdef['parameters'].items()):
320 if def_param_name is 'yaml_line':
321 continue
322 ret_val.append('%s=\'%s\'\n' % (def_param_name, def_param_value))
323 else:
324 return None
325
326 ret_val.append('######\n')
327
328 ret_val.append('###custom parameters from test plan###\n')
329 if self.custom_params:
330 for param_name, param_value in list(self.custom_params.items()):
331 if param_name is 'yaml_line':
332 continue
333 ret_val.append('%s=\'%s\'\n' % (param_name, param_value))
334
335 if self.skip_install:
336 ret_val.append('SKIP_INSTALL="True"\n')
337 ret_val.append('######\n')
338
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100339 ret_val.append('###custom parameters from command line###\n')
340 if self.args.test_def_params:
341 for param_name, param_value in self.args.test_def_params.items():
342 ret_val.append('%s=\'%s\'\n' % (param_name, param_value))
343 ret_val.append('######\n')
Chase Qi09edc7f2016-08-18 13:18:50 +0800344 return ret_val
345
346
347class TestRun(object):
348 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000349 self.test = test
350 self.args = args
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000351 self.logger = logging.getLogger('RUNNER.TestRun')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000352 self.test_timeout = self.args.timeout
Chase Qi09edc7f2016-08-18 13:18:50 +0800353 if 'timeout' in test:
354 self.test_timeout = test['timeout']
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000355
356 def run(self):
357 raise NotImplementedError
358
359 def check_result(self):
360 raise NotImplementedError
361
362
363class AutomatedTestRun(TestRun):
364 def run(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000365 self.logger.info('Executing %s/run.sh' % self.test['test_path'])
366 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 +0800367 self.child = pexpect.spawn('/bin/sh', ['-c', shell_cmd])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000368 self.check_result()
Chase Qi09edc7f2016-08-18 13:18:50 +0800369
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000370 def check_result(self):
Chase Qi09edc7f2016-08-18 13:18:50 +0800371 if self.test_timeout:
372 self.logger.info('Test timeout: %s' % self.test_timeout)
373 test_end = time.time() + self.test_timeout
374
375 while self.child.isalive():
376 if self.test_timeout and time.time() > test_end:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000377 self.logger.warning('%s test timed out, killing test process...' % self.test['test_uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800378 self.child.terminate(force=True)
379 break
380 try:
381 self.child.expect('\r\n')
Chase Qi3efa7692017-06-26 15:54:05 +0800382 if sys.version_info[0] < 3:
383 print(self.child.before)
384 else:
385 print(self.child.before.decode('utf-8'))
Chase Qi09edc7f2016-08-18 13:18:50 +0800386 except pexpect.TIMEOUT:
387 continue
388 except pexpect.EOF:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000389 self.logger.info('%s test finished.\n' % self.test['test_uuid'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800390 break
391
392
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000393class RemoteTestRun(AutomatedTestRun):
394 def copy_to_target(self):
395 os.chdir(self.test['test_path'])
396 tarball_name = "target-test-files.tar"
Dan Ruea9eb01c2017-06-07 16:29:09 -0500397
398 self.logger.info("Archiving test files")
399 run_command(
400 'tar -caf %s run.sh uuid automated/lib automated/bin automated/utils %s' %
401 (tarball_name, self.test['tc_relative_dir']))
402
403 self.logger.info("Creating test path")
404 run_command("mkdir -p %s" % (self.test['target_test_path']), self.args.target)
405
406 self.logger.info("Copying test archive to target host")
407 run_command('scp %s ./%s %s:%s' % (SSH_PARAMS, tarball_name, self.args.target,
408 self.test['target_test_path']))
409
410 self.logger.info("Unarchiving test files on target")
411 run_command("cd %s && tar -xf %s" % (self.test['target_test_path'],
412 tarball_name), self.args.target)
413
414 self.logger.info("Removing test file archive from target")
415 run_command("rm %s/%s" % (self.test['target_test_path'],
416 tarball_name), self.args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000417
418 def run(self):
419 self.copy_to_target()
420 self.logger.info('Executing %s/run.sh remotely on %s' % (self.test['target_test_path'], self.args.target))
421 shell_cmd = 'ssh %s %s "%s/run.sh 2>&1"' % (SSH_PARAMS, self.args.target, self.test['target_test_path'])
422 self.logger.debug('shell_cmd: %s' % shell_cmd)
423 output = open("%s/stdout.log" % self.test['test_path'], "w")
424 self.child = pexpect.spawn(shell_cmd)
425 self.child.logfile = output
426 self.check_result()
427
428
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000429class ManualTestShell(cmd.Cmd):
430 def __init__(self, test_dict, result_path):
431 cmd.Cmd.__init__(self)
432 self.test_dict = test_dict
433 self.result_path = result_path
434 self.current_step_index = 0
435 self.steps = self.test_dict['run']['steps']
436 self.expected = self.test_dict['run']['expected']
437 self.prompt = "%s > " % self.test_dict['metadata']['name']
438 self.result = None
439 self.intro = """
440 Welcome to manual test executor. Type 'help' for available commands.
441 This shell is meant to be executed on your computer, not on the system
442 under test. Please execute the steps from the test case, compare to
443 expected result and record the test result as 'pass' or 'fail'. If there
444 is an issue that prevents from executing the step, please record the result
445 as 'skip'.
446 """
447
448 def do_quit(self, line):
449 """
450 Exit test execution
451 """
452 if self.result is not None:
453 return True
454 if line.find("-f") >= 0:
455 self._record_result("skip")
456 return True
Chase Qic69235d2017-05-23 14:56:47 +0800457 print("Test result not recorded. Use -f to force. Forced quit records result as 'skip'")
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000458
459 do_EOF = do_quit
460
461 def do_description(self, line):
462 """
463 Prints current test overall description
464 """
Chase Qic69235d2017-05-23 14:56:47 +0800465 print(self.test_dict['metadata']['description'])
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000466
467 def do_steps(self, line):
468 """
469 Prints all steps of the current test case
470 """
471 for index, step in enumerate(self.steps):
Chase Qic69235d2017-05-23 14:56:47 +0800472 print("%s. %s" % (index, step))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000473
474 def do_expected(self, line):
475 """
476 Prints all expected results of the current test case
477 """
478 for index, expected in enumerate(self.expected):
Chase Qic69235d2017-05-23 14:56:47 +0800479 print("%s. %s" % (index, expected))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000480
481 def do_current(self, line):
482 """
483 Prints current test step
484 """
485 self._print_step()
486
487 do_start = do_current
488
489 def do_next(self, line):
490 """
491 Prints next test step
492 """
493 if len(self.steps) > self.current_step_index + 1:
494 self.current_step_index += 1
495 self._print_step()
496
497 def _print_step(self):
Chase Qic69235d2017-05-23 14:56:47 +0800498 print("%s. %s" % (self.current_step_index, self.steps[self.current_step_index]))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000499
500 def _record_result(self, result):
Chase Qic69235d2017-05-23 14:56:47 +0800501 print("Recording %s in %s/stdout.log" % (result, self.result_path))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000502 with open("%s/stdout.log" % self.result_path, "a") as f:
503 f.write("<LAVA_SIGNAL_TESTCASE TEST_CASE_ID=%s RESULT=%s>" %
504 (self.test_dict['metadata']['name'], result))
505
506 def do_pass(self, line):
507 """
508 Records PASS as test result
509 """
510 self.result = "pass"
511 self._record_result(self.result)
512 return True
513
514 def do_fail(self, line):
515 """
516 Records FAIL as test result
517 """
518 self.result = "fail"
519 self._record_result(self.result)
520 return True
521
522 def do_skip(self, line):
523 """
524 Records SKIP as test result
525 """
526 self.result = "skip"
527 self._record_result(self.result)
528 return True
529
530
531class ManualTestRun(TestRun, cmd.Cmd):
532 def run(self):
Chase Qic69235d2017-05-23 14:56:47 +0800533 print(self.test['test_name'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000534 with open('%s/testdef.yaml' % self.test['test_path'], 'r') as f:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000535 self.testdef = yaml.safe_load(f)
536
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000537 ManualTestShell(self.testdef, self.test['test_path']).cmdloop()
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000538
539 def check_result(self):
540 pass
541
542
Dan Ruea9eb01c2017-06-07 16:29:09 -0500543def get_packages(linux_distribution, target=None):
544 """ Return a list of installed packages with versions
545
546 linux_distribution is a string that may be 'debian',
547 'ubuntu', 'centos', or 'fedora'.
548
549 For example (ubuntu):
550 'packages': ['acl-2.2.52-2',
551 'adduser-3.113+nmu3',
552 ...
553 'zlib1g:amd64-1:1.2.8.dfsg-2+b1',
554 'zlib1g-dev:amd64-1:1.2.8.dfsg-2+b1']
555
556 (centos):
557 "packages": ["acl-2.2.51-12.el7",
558 "apr-1.4.8-3.el7",
559 ...
560 "zlib-1.2.7-17.el7",
561 "zlib-devel-1.2.7-17.el7"
562 ]
563 """
564
565 logger = logging.getLogger('RUNNER.get_packages')
566 packages = []
567 if linux_distribution in ['debian', 'ubuntu']:
568 # Debian (apt) based system
569 packages = run_command("dpkg-query -W -f '${package}-${version}\n'", target).splitlines()
570
571 elif linux_distribution in ['centos', 'fedora']:
572 # RedHat (rpm) based system
573 packages = run_command("rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}\n'", target).splitlines()
574 else:
575 logger.warning("Unknown linux distribution '{}'; package list not populated.".format(linux_distribution))
576
577 packages.sort()
578 return packages
579
580
581def get_environment(target=None, skip_collection=False):
582 """ Return a dictionary with environmental information
583
584 target: optional ssh host string to gather environment remotely.
585 skip_collection: Skip data collection and return an empty dictionary.
586
587 For example (on a HiSilicon D03):
588 {
589 "bios_version": "Hisilicon D03 UEFI 16.12 Release",
590 "board_name": "D03",
591 "board_vendor": "Huawei",
592 "kernel": "4.9.0-20.gitedc2a1c.linaro.aarch64",
593 "linux_distribution": "centos",
594 "packages": [
595 "GeoIP-1.5.0-11.el7",
596 "NetworkManager-1.4.0-20.el7_3",
597 ...
598 "yum-plugin-fastestmirror-1.1.31-40.el7",
599 "zlib-1.2.7-17.el7"
600 ],
601 "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"
602 }
603 """
604
605 environment = {}
606 if skip_collection:
607 return environment
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100608 try:
609 environment['linux_distribution'] = run_command(
610 "grep ^ID= /etc/os-release", target).split('=')[-1].strip('"').lower()
611 except subprocess.CalledProcessError:
612 environment['linux_distribution'] = ""
613
614 try:
615 environment['kernel'] = run_command("uname -r", target)
616 except subprocess.CalledProcessError:
617 environment['kernel'] = ""
618
619 try:
620 environment['uname'] = run_command("uname -a", target)
621 except subprocess.CalledProcessError:
622 environment['uname'] = ""
Dan Ruea9eb01c2017-06-07 16:29:09 -0500623
624 try:
625 environment['bios_version'] = run_command(
626 "cat /sys/devices/virtual/dmi/id/bios_version", target)
627 except subprocess.CalledProcessError:
628 environment['bios_version'] = ""
629
630 try:
631 environment['board_vendor'] = run_command(
632 "cat /sys/devices/virtual/dmi/id/board_vendor", target)
633 except subprocess.CalledProcessError:
634 environment['board_vendor'] = ""
635
636 try:
637 environment['board_name'] = run_command(
638 "cat /sys/devices/virtual/dmi/id/board_name", target)
639 except subprocess.CalledProcessError:
640 environment['board_name'] = ""
641
Milosz Wasilewskidf71a762017-07-20 13:26:21 +0100642 try:
643 environment['packages'] = get_packages(environment['linux_distribution'], target)
644 except subprocess.CalledProcessError:
645 environment['packages'] = []
Dan Ruea9eb01c2017-06-07 16:29:09 -0500646 return environment
647
648
Chase Qi09edc7f2016-08-18 13:18:50 +0800649class ResultParser(object):
650 def __init__(self, test, args):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000651 self.test = test
652 self.args = args
Chase Qi09edc7f2016-08-18 13:18:50 +0800653 self.metrics = []
654 self.results = {}
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000655 self.results['test'] = test['test_name']
656 self.results['id'] = test['test_uuid']
Dan Ruea9eb01c2017-06-07 16:29:09 -0500657 self.results['test_plan'] = args.test_plan
658 self.results['environment'] = get_environment(
659 target=self.args.target, skip_collection=self.args.skip_environment)
Chase Qi09edc7f2016-08-18 13:18:50 +0800660 self.logger = logging.getLogger('RUNNER.ResultParser')
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000661 self.results['params'] = {}
Chase Qie94ba522017-05-26 12:05:18 +0800662 self.pattern = None
663 self.fixup = None
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000664 with open(os.path.join(self.test['test_path'], "testdef.yaml"), "r") as f:
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000665 self.testdef = yaml.safe_load(f)
Chase Qi0d75a6b2016-11-29 23:43:54 +0800666 self.results['name'] = ""
667 if 'metadata' in self.testdef.keys() and \
668 'name' in self.testdef['metadata'].keys():
669 self.results['name'] = self.testdef['metadata']['name']
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000670 if 'params' in self.testdef.keys():
671 self.results['params'] = self.testdef['params']
Nicolas Dechesnefaa343d2017-10-23 00:33:10 +0200672 if self.args.test_def_params:
673 for param_name, param_value in self.args.test_def_params.items():
674 self.results['params'][param_name] = param_value
Chase Qie94ba522017-05-26 12:05:18 +0800675 if 'parse' in self.testdef.keys() and 'pattern' in self.testdef['parse'].keys():
676 self.pattern = self.testdef['parse']['pattern']
677 self.logger.info("Enabling log parse pattern: %s" % self.pattern)
678 if 'fixupdict' in self.testdef['parse'].keys():
679 self.fixup = self.testdef['parse']['fixupdict']
680 self.logger.info("Enabling log parse pattern fixup: %s" % self.fixup)
Chase Qiae88be32016-11-23 20:32:21 +0800681 if 'parameters' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000682 self.results['params'].update(test['parameters'])
Chase Qiae88be32016-11-23 20:32:21 +0800683 if 'params' in test.keys():
Milosz Wasilewskia76e8dd2016-11-25 14:13:25 +0000684 self.results['params'].update(test['params'])
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000685 if 'version' in test.keys():
686 self.results['version'] = test['version']
687 else:
688 path = os.getcwd()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000689 os.chdir(self.test['test_path'])
Chase Qi3efa7692017-06-26 15:54:05 +0800690 if sys.version_info[0] < 3:
691 test_version = subprocess.check_output("git rev-parse HEAD", shell=True)
692 else:
693 test_version = subprocess.check_output("git rev-parse HEAD", shell=True).decode('utf-8')
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000694 self.results['version'] = test_version.rstrip()
695 os.chdir(path)
Chase Qiea543352017-09-21 16:44:30 +0800696 self.lava_run = args.lava_run
697 if self.lava_run and not find_executable('lava-test-case'):
698 self.logger.info("lava-test-case not found, '-l' or '--lava_run' option ignored'")
699 self.lava_run = False
Chase Qi09edc7f2016-08-18 13:18:50 +0800700
701 def run(self):
702 self.parse_stdout()
Chase Qie94ba522017-05-26 12:05:18 +0800703 if self.pattern:
704 self.parse_pattern()
705 # If 'metrics' is empty, add 'no-result-found fail'.
706 if not self.metrics:
707 self.metrics = [{'test_case_id': 'no-result-found', 'result': 'fail', 'measurement': '', 'units': ''}]
708 self.results['metrics'] = self.metrics
Chase Qi09edc7f2016-08-18 13:18:50 +0800709 self.dict_to_json()
710 self.dict_to_csv()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000711 self.logger.info('Result files saved to: %s' % self.test['test_path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800712 print('--- Printing result.csv ---')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000713 with open('%s/result.csv' % self.test['test_path']) as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800714 print(f.read())
715
716 def parse_stdout(self):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000717 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000718 test_case_re = re.compile("TEST_CASE_ID=(.*)")
719 result_re = re.compile("RESULT=(.*)")
720 measurement_re = re.compile("MEASUREMENT=(.*)")
721 units_re = re.compile("UNITS=(.*)")
Chase Qi09edc7f2016-08-18 13:18:50 +0800722 for line in f:
723 if re.match(r'\<(|LAVA_SIGNAL_TESTCASE )TEST_CASE_ID=.*', line):
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000724 line = line.strip('\n').strip('\r').strip('<>').split(' ')
Chase Qi09edc7f2016-08-18 13:18:50 +0800725 data = {'test_case_id': '',
726 'result': '',
727 'measurement': '',
728 'units': ''}
729
730 for string in line:
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000731 test_case_match = test_case_re.match(string)
732 result_match = result_re.match(string)
733 measurement_match = measurement_re.match(string)
734 units_match = units_re.match(string)
735 if test_case_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000736 data['test_case_id'] = test_case_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000737 if result_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000738 data['result'] = result_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000739 if measurement_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000740 data['measurement'] = measurement_match.group(1)
Milosz Wasilewskiff695622016-12-05 16:00:06 +0000741 if units_match:
Milosz Wasilewski5a4dd442016-12-07 15:01:57 +0000742 data['units'] = units_match.group(1)
Chase Qi09edc7f2016-08-18 13:18:50 +0800743
744 self.metrics.append(data.copy())
745
Chase Qiea543352017-09-21 16:44:30 +0800746 if self.lava_run:
747 self.send_to_lava(data)
748
Chase Qie94ba522017-05-26 12:05:18 +0800749 def parse_pattern(self):
750 with open('%s/stdout.log' % self.test['test_path'], 'r') as f:
751 for line in f:
752 data = {}
753 m = re.search(r'%s' % self.pattern, line)
754 if m:
755 data = m.groupdict()
756 for x in ['measurement', 'units']:
757 if x not in data:
758 data[x] = ''
Aníbal Limónbe78bec2017-11-28 17:08:44 -0600759 if self.fixup and data['result'] in self.fixup:
Chase Qie94ba522017-05-26 12:05:18 +0800760 data['result'] = self.fixup[data['result']]
Chase Qi1f2a9a02017-03-09 15:45:04 +0100761
Chase Qie94ba522017-05-26 12:05:18 +0800762 self.metrics.append(data.copy())
Chase Qi09edc7f2016-08-18 13:18:50 +0800763
Chase Qiea543352017-09-21 16:44:30 +0800764 if self.lava_run:
765 self.send_to_lava(data)
766
767 def send_to_lava(self, data):
768 cmd = 'lava-test-case {} --result {}'.format(data['test_case_id'], data['result'])
769 if data['measurement']:
770 cmd = '{} --measurement {} --units {}'.format(cmd, data['measurement'], data['units'])
771 self.logger.debug('lava-run: cmd: {}'.format(cmd))
772 subprocess.call(shlex.split(cmd))
773
Chase Qi09edc7f2016-08-18 13:18:50 +0800774 def dict_to_json(self):
Chase Qi87f4f402016-11-07 15:32:01 +0800775 # Save test results to output/test_id/result.json
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000776 with open('%s/result.json' % self.test['test_path'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800777 json.dump([self.results], f, indent=4)
778
779 # Collect test results of all tests in output/result.json
780 feeds = []
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000781 if os.path.isfile('%s/result.json' % self.test['output']):
782 with open('%s/result.json' % self.test['output'], 'r') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800783 feeds = json.load(f)
784
785 feeds.append(self.results)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000786 with open('%s/result.json' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800787 json.dump(feeds, f, indent=4)
Chase Qi09edc7f2016-08-18 13:18:50 +0800788
789 def dict_to_csv(self):
Chase Qica15cf52016-11-10 17:00:22 +0800790 # Convert dict self.results['params'] to a string.
791 test_params = ''
792 if self.results['params']:
793 params_dict = self.results['params']
Chase Qic69235d2017-05-23 14:56:47 +0800794 test_params = ';'.join(['%s=%s' % (k, v) for k, v in params_dict.items()])
Chase Qi09edc7f2016-08-18 13:18:50 +0800795
Chase Qica15cf52016-11-10 17:00:22 +0800796 for metric in self.results['metrics']:
Chase Qi0d75a6b2016-11-29 23:43:54 +0800797 metric['name'] = self.results['name']
Chase Qica15cf52016-11-10 17:00:22 +0800798 metric['test_params'] = test_params
799
800 # Save test results to output/test_id/result.csv
Chase Qi0d75a6b2016-11-29 23:43:54 +0800801 fieldnames = ['name', 'test_case_id', 'result', 'measurement', 'units', 'test_params']
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000802 with open('%s/result.csv' % self.test['test_path'], 'w') as f:
Chase Qica15cf52016-11-10 17:00:22 +0800803 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800804 writer.writeheader()
805 for metric in self.results['metrics']:
806 writer.writerow(metric)
807
Chase Qi87f4f402016-11-07 15:32:01 +0800808 # Collect test results of all tests in output/result.csv
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000809 if not os.path.isfile('%s/result.csv' % self.test['output']):
810 with open('%s/result.csv' % self.test['output'], 'w') as f:
Chase Qi87f4f402016-11-07 15:32:01 +0800811 writer = csv.DictWriter(f, fieldnames=fieldnames)
812 writer.writeheader()
813
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000814 with open('%s/result.csv' % self.test['output'], 'a') as f:
Chase Qi09edc7f2016-08-18 13:18:50 +0800815 writer = csv.DictWriter(f, fieldnames=fieldnames)
Chase Qi09edc7f2016-08-18 13:18:50 +0800816 for metric in self.results['metrics']:
Chase Qi09edc7f2016-08-18 13:18:50 +0800817 writer.writerow(metric)
818
819
820def get_args():
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100821 parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
Milosz Wasilewski855acbe2017-07-20 13:28:58 +0100822 parser.add_argument('-o', '--output', default=os.getenv("HOME", "") + '/output', dest='output',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100823 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800824 specify a directory to store test and result files.
Nicolas Dechesnef6c4c212017-01-18 17:30:04 +0100825 Default: $HOME/output
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100826 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800827 parser.add_argument('-p', '--test_plan', default=None, dest='test_plan',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100828 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800829 specify an test plan file which has tests and related
830 params listed in yaml format.
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100831 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800832 parser.add_argument('-d', '--test_def', default=None, dest='test_def',
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100833 help=textwrap.dedent('''\
Chase Qi09edc7f2016-08-18 13:18:50 +0800834 base on test definition repo location, specify relative
835 path to the test definition to run.
836 Format example: "ubuntu/smoke-tests-basic.yaml"
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100837 '''))
838 parser.add_argument('-r', '--test_def_params', default={}, dest='test_def_params',
839 action=StoreDictKeyPair, nargs="+", metavar="KEY=VALUE",
840 help=textwrap.dedent('''\
841 Set additional parameters when using test definition without
842 a test plan. The name values are set similarily to environment
843 variables:
844 --test_def_params KEY1=VALUE1 KEY2=VALUE2 ...
845 '''))
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000846 parser.add_argument('-k', '--kind', default="automated", dest='kind',
847 choices=['automated', 'manual'],
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100848 help=textwrap.dedent('''\
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000849 Selects type of tests to be executed from the test plan.
850 Possible options: automated, manual
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100851 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800852 parser.add_argument('-t', '--timeout', type=int, default=None,
853 dest='timeout', help='Specify test timeout')
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000854 parser.add_argument('-g', '--target', default=None,
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100855 dest='target', help=textwrap.dedent('''\
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000856 Specify SSH target to execute tests.
857 Format: user@host
858 Note: ssh authentication must be paswordless
Milosz Wasilewski259ba192017-07-27 10:59:25 +0100859 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800860 parser.add_argument('-s', '--skip_install', dest='skip_install',
861 default=False, action='store_true',
862 help='skip install section defined in test definition.')
Dan Ruea9eb01c2017-06-07 16:29:09 -0500863 parser.add_argument('-e', '--skip_environment', dest='skip_environment',
864 default=False, action='store_true',
865 help='skip environmental data collection (board name, distro, etc)')
Chase Qiea543352017-09-21 16:44:30 +0800866 parser.add_argument('-l', '--lava_run', dest='lava_run',
867 default=False, action='store_true',
868 help='send test result to LAVA with lava-test-case.')
Chase Qia158efe2017-11-17 12:35:11 +0800869 parser.add_argument('-O', '--overlay', default=None,
870 dest='overlay', help=textwrap.dedent('''\
871 Specify test plan ovelay file to:
872 * skip tests
873 * amend test parameters
874 * add new tests
875 '''))
Chase Qi09edc7f2016-08-18 13:18:50 +0800876 args = parser.parse_args()
877 return args
878
879
880def main():
881 # Setup logger.
882 logger = logging.getLogger('RUNNER')
883 logger.setLevel(logging.DEBUG)
884 ch = logging.StreamHandler()
885 ch.setLevel(logging.DEBUG)
886 formatter = logging.Formatter('%(asctime)s - %(name)s: %(levelname)s: %(message)s')
887 ch.setFormatter(formatter)
888 logger.addHandler(ch)
889
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000890 args = get_args()
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000891 logger.debug('Test job arguments: %s' % args)
892 if args.kind != "manual" and args.target is None:
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000893 if os.geteuid() != 0:
894 logger.error("Sorry, you need to run this as root")
895 sys.exit(1)
Chase Qi09edc7f2016-08-18 13:18:50 +0800896
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000897 # Validate target argument format and connectivity.
898 if args.target:
899 rex = re.compile('.+@.+')
900 if not rex.match(args.target):
901 logger.error('Usage: -g username@host')
902 sys.exit(1)
903 if pexpect.which('ssh') is None:
904 logger.error('openssh client must be installed on the host.')
905 sys.exit(1)
906 try:
Dan Ruea9eb01c2017-06-07 16:29:09 -0500907 run_command("exit", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000908 except subprocess.CalledProcessError as e:
909 logger.error('ssh login failed.')
910 print(e)
911 sys.exit(1)
912
Chase Qi09edc7f2016-08-18 13:18:50 +0800913 # Generate test plan.
Chase Qi09edc7f2016-08-18 13:18:50 +0800914 test_plan = TestPlan(args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000915 test_list = test_plan.test_list(args.kind)
Chase Qi09edc7f2016-08-18 13:18:50 +0800916 logger.info('Tests to run:')
917 for test in test_list:
918 print(test)
919
920 # Run tests.
921 for test in test_list:
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000922 # Set and save test params to test dictionary.
923 test['test_name'] = os.path.splitext(test['path'].split('/')[-1])[0]
924 test['test_uuid'] = '%s_%s' % (test['test_name'], test['uuid'])
925 test['output'] = os.path.realpath(args.output)
926 if args.target is not None and '-o' not in sys.argv:
927 test['output'] = os.path.join(test['output'], args.target)
928 test['test_path'] = os.path.join(test['output'], test['test_uuid'])
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000929 if args.target is not None:
Chase Qi204b5422017-04-06 11:01:58 +0800930 # Get relative directory path of yaml file for partial file copy.
931 # '-d' takes any relative paths to the yaml file, so get the realpath first.
932 tc_realpath = os.path.realpath(test['path'])
933 tc_dirname = os.path.dirname(tc_realpath)
934 test['tc_relative_dir'] = '%s%s' % (args.kind, tc_dirname.split(args.kind)[1])
Dan Ruea9eb01c2017-06-07 16:29:09 -0500935 target_user_home = run_command("echo $HOME", args.target)
Milosz Wasilewski682120e2017-03-13 13:37:18 +0000936 test['target_test_path'] = '%s/output/%s' % (target_user_home, test['test_uuid'])
937 logger.debug('Test parameters: %s' % test)
938
Chase Qi09edc7f2016-08-18 13:18:50 +0800939 # Create directories and copy files needed.
940 setup = TestSetup(test, args)
941 setup.create_dir()
942 setup.copy_test_repo()
Milosz Wasilewski970431b2016-11-25 14:10:08 +0000943 setup.checkout_version()
Chase Qi09edc7f2016-08-18 13:18:50 +0800944 setup.create_uuid_file()
945
946 # Convert test definition.
947 test_def = TestDefinition(test, args)
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000948 if test_def.exists:
949 test_def.definition()
950 test_def.metadata()
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100951 test_def.mkrun()
Chase Qi09edc7f2016-08-18 13:18:50 +0800952
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000953 # Run test.
Nicolas Dechesne51b85a82017-02-04 00:45:48 +0100954 test_def.run()
Chase Qi09edc7f2016-08-18 13:18:50 +0800955
Milosz Wasilewski2fea70e2016-11-11 12:16:09 +0000956 # Parse test output, save results in json and csv format.
957 result_parser = ResultParser(test, args)
958 result_parser.run()
959 else:
960 logger.warning("Requested test definition %s doesn't exist" % test['path'])
Chase Qi09edc7f2016-08-18 13:18:50 +0800961
Dan Ruea9eb01c2017-06-07 16:29:09 -0500962
Chase Qi09edc7f2016-08-18 13:18:50 +0800963if __name__ == "__main__":
964 main()