1*4882a593Smuzhiyun# test case management tool - manual execution from testopia test cases 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# Copyright (c) 2018, Intel Corporation. 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun 8*4882a593Smuzhiyunimport argparse 9*4882a593Smuzhiyunimport json 10*4882a593Smuzhiyunimport os 11*4882a593Smuzhiyunimport sys 12*4882a593Smuzhiyunimport datetime 13*4882a593Smuzhiyunimport re 14*4882a593Smuzhiyunimport copy 15*4882a593Smuzhiyunfrom oeqa.core.runner import OETestResultJSONHelper 16*4882a593Smuzhiyun 17*4882a593Smuzhiyun 18*4882a593Smuzhiyundef load_json_file(f): 19*4882a593Smuzhiyun with open(f, "r") as filedata: 20*4882a593Smuzhiyun return json.load(filedata) 21*4882a593Smuzhiyun 22*4882a593Smuzhiyundef write_json_file(f, json_data): 23*4882a593Smuzhiyun os.makedirs(os.path.dirname(f), exist_ok=True) 24*4882a593Smuzhiyun with open(f, 'w') as filedata: 25*4882a593Smuzhiyun filedata.write(json.dumps(json_data, sort_keys=True, indent=4)) 26*4882a593Smuzhiyun 27*4882a593Smuzhiyunclass ManualTestRunner(object): 28*4882a593Smuzhiyun 29*4882a593Smuzhiyun def _get_test_module(self, case_file): 30*4882a593Smuzhiyun return os.path.basename(case_file).split('.')[0] 31*4882a593Smuzhiyun 32*4882a593Smuzhiyun def _get_input(self, config): 33*4882a593Smuzhiyun while True: 34*4882a593Smuzhiyun output = input('{} = '.format(config)) 35*4882a593Smuzhiyun if re.match('^[a-z0-9-.]+$', output): 36*4882a593Smuzhiyun break 37*4882a593Smuzhiyun print('Only lowercase alphanumeric, hyphen and dot are allowed. Please try again') 38*4882a593Smuzhiyun return output 39*4882a593Smuzhiyun 40*4882a593Smuzhiyun def _get_available_config_options(self, config_options, test_module, target_config): 41*4882a593Smuzhiyun avail_config_options = None 42*4882a593Smuzhiyun if test_module in config_options: 43*4882a593Smuzhiyun avail_config_options = config_options[test_module].get(target_config) 44*4882a593Smuzhiyun return avail_config_options 45*4882a593Smuzhiyun 46*4882a593Smuzhiyun def _choose_config_option(self, options): 47*4882a593Smuzhiyun while True: 48*4882a593Smuzhiyun output = input('{} = '.format('Option index number')) 49*4882a593Smuzhiyun if output in options: 50*4882a593Smuzhiyun break 51*4882a593Smuzhiyun print('Only integer index inputs from above available configuration options are allowed. Please try again.') 52*4882a593Smuzhiyun return options[output] 53*4882a593Smuzhiyun 54*4882a593Smuzhiyun def _get_config(self, config_options, test_module): 55*4882a593Smuzhiyun from oeqa.utils.metadata import get_layers 56*4882a593Smuzhiyun from oeqa.utils.commands import get_bb_var 57*4882a593Smuzhiyun from resulttool.resultutils import store_map 58*4882a593Smuzhiyun 59*4882a593Smuzhiyun layers = get_layers(get_bb_var('BBLAYERS')) 60*4882a593Smuzhiyun configurations = {} 61*4882a593Smuzhiyun configurations['LAYERS'] = layers 62*4882a593Smuzhiyun configurations['STARTTIME'] = datetime.datetime.now().strftime('%Y%m%d%H%M%S') 63*4882a593Smuzhiyun configurations['TEST_TYPE'] = 'manual' 64*4882a593Smuzhiyun configurations['TEST_MODULE'] = test_module 65*4882a593Smuzhiyun 66*4882a593Smuzhiyun extra_config = set(store_map['manual']) - set(configurations) 67*4882a593Smuzhiyun for config in sorted(extra_config): 68*4882a593Smuzhiyun avail_config_options = self._get_available_config_options(config_options, test_module, config) 69*4882a593Smuzhiyun if avail_config_options: 70*4882a593Smuzhiyun print('---------------------------------------------') 71*4882a593Smuzhiyun print('These are available configuration #%s options:' % config) 72*4882a593Smuzhiyun print('---------------------------------------------') 73*4882a593Smuzhiyun for option, _ in sorted(avail_config_options.items(), key=lambda x: int(x[0])): 74*4882a593Smuzhiyun print('%s: %s' % (option, avail_config_options[option])) 75*4882a593Smuzhiyun print('Please select configuration option, enter the integer index number.') 76*4882a593Smuzhiyun value_conf = self._choose_config_option(avail_config_options) 77*4882a593Smuzhiyun print('---------------------------------------------\n') 78*4882a593Smuzhiyun else: 79*4882a593Smuzhiyun print('---------------------------------------------') 80*4882a593Smuzhiyun print('This is configuration #%s. Please provide configuration value(use "None" if not applicable).' % config) 81*4882a593Smuzhiyun print('---------------------------------------------') 82*4882a593Smuzhiyun value_conf = self._get_input('Configuration Value') 83*4882a593Smuzhiyun print('---------------------------------------------\n') 84*4882a593Smuzhiyun configurations[config] = value_conf 85*4882a593Smuzhiyun return configurations 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun def _execute_test_steps(self, case): 88*4882a593Smuzhiyun test_result = {} 89*4882a593Smuzhiyun print('------------------------------------------------------------------------') 90*4882a593Smuzhiyun print('Executing test case: %s' % case['test']['@alias']) 91*4882a593Smuzhiyun print('------------------------------------------------------------------------') 92*4882a593Smuzhiyun print('You have total %s test steps to be executed.' % len(case['test']['execution'])) 93*4882a593Smuzhiyun print('------------------------------------------------------------------------\n') 94*4882a593Smuzhiyun for step, _ in sorted(case['test']['execution'].items(), key=lambda x: int(x[0])): 95*4882a593Smuzhiyun print('Step %s: %s' % (step, case['test']['execution'][step]['action'])) 96*4882a593Smuzhiyun expected_output = case['test']['execution'][step]['expected_results'] 97*4882a593Smuzhiyun if expected_output: 98*4882a593Smuzhiyun print('Expected output: %s' % expected_output) 99*4882a593Smuzhiyun while True: 100*4882a593Smuzhiyun done = input('\nPlease provide test results: (P)assed/(F)ailed/(B)locked/(S)kipped? \n').lower() 101*4882a593Smuzhiyun result_types = {'p':'PASSED', 102*4882a593Smuzhiyun 'f':'FAILED', 103*4882a593Smuzhiyun 'b':'BLOCKED', 104*4882a593Smuzhiyun 's':'SKIPPED'} 105*4882a593Smuzhiyun if done in result_types: 106*4882a593Smuzhiyun for r in result_types: 107*4882a593Smuzhiyun if done == r: 108*4882a593Smuzhiyun res = result_types[r] 109*4882a593Smuzhiyun if res == 'FAILED': 110*4882a593Smuzhiyun log_input = input('\nPlease enter the error and the description of the log: (Ex:log:211 Error Bitbake)\n') 111*4882a593Smuzhiyun test_result.update({case['test']['@alias']: {'status': '%s' % res, 'log': '%s' % log_input}}) 112*4882a593Smuzhiyun else: 113*4882a593Smuzhiyun test_result.update({case['test']['@alias']: {'status': '%s' % res}}) 114*4882a593Smuzhiyun break 115*4882a593Smuzhiyun print('Invalid input!') 116*4882a593Smuzhiyun return test_result 117*4882a593Smuzhiyun 118*4882a593Smuzhiyun def _get_write_dir(self): 119*4882a593Smuzhiyun return os.environ['BUILDDIR'] + '/tmp/log/manual/' 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun def run_test(self, case_file, config_options_file, testcase_config_file): 122*4882a593Smuzhiyun test_module = self._get_test_module(case_file) 123*4882a593Smuzhiyun cases = load_json_file(case_file) 124*4882a593Smuzhiyun config_options = {} 125*4882a593Smuzhiyun if config_options_file: 126*4882a593Smuzhiyun config_options = load_json_file(config_options_file) 127*4882a593Smuzhiyun configurations = self._get_config(config_options, test_module) 128*4882a593Smuzhiyun result_id = 'manual_%s_%s' % (test_module, configurations['STARTTIME']) 129*4882a593Smuzhiyun test_results = {} 130*4882a593Smuzhiyun if testcase_config_file: 131*4882a593Smuzhiyun test_case_config = load_json_file(testcase_config_file) 132*4882a593Smuzhiyun test_case_to_execute = test_case_config['testcases'] 133*4882a593Smuzhiyun for case in copy.deepcopy(cases) : 134*4882a593Smuzhiyun if case['test']['@alias'] not in test_case_to_execute: 135*4882a593Smuzhiyun cases.remove(case) 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun print('\nTotal number of test cases in this test suite: %s\n' % len(cases)) 138*4882a593Smuzhiyun for c in cases: 139*4882a593Smuzhiyun test_result = self._execute_test_steps(c) 140*4882a593Smuzhiyun test_results.update(test_result) 141*4882a593Smuzhiyun return configurations, result_id, self._get_write_dir(), test_results 142*4882a593Smuzhiyun 143*4882a593Smuzhiyun def _get_true_false_input(self, input_message): 144*4882a593Smuzhiyun yes_list = ['Y', 'YES'] 145*4882a593Smuzhiyun no_list = ['N', 'NO'] 146*4882a593Smuzhiyun while True: 147*4882a593Smuzhiyun more_config_option = input(input_message).upper() 148*4882a593Smuzhiyun if more_config_option in yes_list or more_config_option in no_list: 149*4882a593Smuzhiyun break 150*4882a593Smuzhiyun print('Invalid input!') 151*4882a593Smuzhiyun if more_config_option in no_list: 152*4882a593Smuzhiyun return False 153*4882a593Smuzhiyun return True 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun def make_config_option_file(self, logger, case_file, config_options_file): 156*4882a593Smuzhiyun config_options = {} 157*4882a593Smuzhiyun if config_options_file: 158*4882a593Smuzhiyun config_options = load_json_file(config_options_file) 159*4882a593Smuzhiyun new_test_module = self._get_test_module(case_file) 160*4882a593Smuzhiyun print('Creating configuration options file for test module: %s' % new_test_module) 161*4882a593Smuzhiyun new_config_options = {} 162*4882a593Smuzhiyun 163*4882a593Smuzhiyun while True: 164*4882a593Smuzhiyun config_name = input('\nPlease provide test configuration to create:\n').upper() 165*4882a593Smuzhiyun new_config_options[config_name] = {} 166*4882a593Smuzhiyun while True: 167*4882a593Smuzhiyun config_value = self._get_input('Configuration possible option value') 168*4882a593Smuzhiyun config_option_index = len(new_config_options[config_name]) + 1 169*4882a593Smuzhiyun new_config_options[config_name][config_option_index] = config_value 170*4882a593Smuzhiyun more_config_option = self._get_true_false_input('\nIs there more configuration option input: (Y)es/(N)o\n') 171*4882a593Smuzhiyun if not more_config_option: 172*4882a593Smuzhiyun break 173*4882a593Smuzhiyun more_config = self._get_true_false_input('\nIs there more configuration to create: (Y)es/(N)o\n') 174*4882a593Smuzhiyun if not more_config: 175*4882a593Smuzhiyun break 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun if new_config_options: 178*4882a593Smuzhiyun config_options[new_test_module] = new_config_options 179*4882a593Smuzhiyun if not config_options_file: 180*4882a593Smuzhiyun config_options_file = os.path.join(self._get_write_dir(), 'manual_config_options.json') 181*4882a593Smuzhiyun write_json_file(config_options_file, config_options) 182*4882a593Smuzhiyun logger.info('Configuration option file created at %s' % config_options_file) 183*4882a593Smuzhiyun 184*4882a593Smuzhiyun def make_testcase_config_file(self, logger, case_file, testcase_config_file): 185*4882a593Smuzhiyun if testcase_config_file: 186*4882a593Smuzhiyun if os.path.exists(testcase_config_file): 187*4882a593Smuzhiyun print('\nTest configuration file with name %s already exists. Please provide a unique file name' % (testcase_config_file)) 188*4882a593Smuzhiyun return 0 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun if not testcase_config_file: 191*4882a593Smuzhiyun testcase_config_file = os.path.join(self._get_write_dir(), "testconfig_new.json") 192*4882a593Smuzhiyun 193*4882a593Smuzhiyun testcase_config = {} 194*4882a593Smuzhiyun cases = load_json_file(case_file) 195*4882a593Smuzhiyun new_test_module = self._get_test_module(case_file) 196*4882a593Smuzhiyun new_testcase_config = {} 197*4882a593Smuzhiyun new_testcase_config['testcases'] = [] 198*4882a593Smuzhiyun 199*4882a593Smuzhiyun print('\nAdd testcases for this configuration file:') 200*4882a593Smuzhiyun for case in cases: 201*4882a593Smuzhiyun print('\n' + case['test']['@alias']) 202*4882a593Smuzhiyun add_tc_config = self._get_true_false_input('\nDo you want to add this test case to test configuration : (Y)es/(N)o\n') 203*4882a593Smuzhiyun if add_tc_config: 204*4882a593Smuzhiyun new_testcase_config['testcases'].append(case['test']['@alias']) 205*4882a593Smuzhiyun write_json_file(testcase_config_file, new_testcase_config) 206*4882a593Smuzhiyun logger.info('Testcase Configuration file created at %s' % testcase_config_file) 207*4882a593Smuzhiyun 208*4882a593Smuzhiyundef manualexecution(args, logger): 209*4882a593Smuzhiyun testrunner = ManualTestRunner() 210*4882a593Smuzhiyun if args.make_config_options_file: 211*4882a593Smuzhiyun testrunner.make_config_option_file(logger, args.file, args.config_options_file) 212*4882a593Smuzhiyun return 0 213*4882a593Smuzhiyun if args.make_testcase_config_file: 214*4882a593Smuzhiyun testrunner.make_testcase_config_file(logger, args.file, args.testcase_config_file) 215*4882a593Smuzhiyun return 0 216*4882a593Smuzhiyun configurations, result_id, write_dir, test_results = testrunner.run_test(args.file, args.config_options_file, args.testcase_config_file) 217*4882a593Smuzhiyun resultjsonhelper = OETestResultJSONHelper() 218*4882a593Smuzhiyun resultjsonhelper.dump_testresult_file(write_dir, configurations, result_id, test_results) 219*4882a593Smuzhiyun return 0 220*4882a593Smuzhiyun 221*4882a593Smuzhiyundef register_commands(subparsers): 222*4882a593Smuzhiyun """Register subcommands from this plugin""" 223*4882a593Smuzhiyun parser_build = subparsers.add_parser('manualexecution', help='helper script for results populating during manual test execution.', 224*4882a593Smuzhiyun description='helper script for results populating during manual test execution. You can find manual test case JSON file in meta/lib/oeqa/manual/', 225*4882a593Smuzhiyun group='manualexecution') 226*4882a593Smuzhiyun parser_build.set_defaults(func=manualexecution) 227*4882a593Smuzhiyun parser_build.add_argument('file', help='specify path to manual test case JSON file.Note: Please use \"\" to encapsulate the file path.') 228*4882a593Smuzhiyun parser_build.add_argument('-c', '--config-options-file', default='', 229*4882a593Smuzhiyun help='the config options file to import and used as available configuration option selection or make config option file') 230*4882a593Smuzhiyun parser_build.add_argument('-m', '--make-config-options-file', action='store_true', 231*4882a593Smuzhiyun help='make the configuration options file based on provided inputs') 232*4882a593Smuzhiyun parser_build.add_argument('-t', '--testcase-config-file', default='', 233*4882a593Smuzhiyun help='the testcase configuration file to enable user to run a selected set of test case or make a testcase configuration file') 234*4882a593Smuzhiyun parser_build.add_argument('-d', '--make-testcase-config-file', action='store_true', 235*4882a593Smuzhiyun help='make the testcase configuration file to run a set of test cases based on user selection')