#!/usr/bin/python # # Copyright (C) 2012 Intel Corporation # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # Authors: # Jing,Wang # Yuanyuan,Zou """ testkit lite tools""" import os import sys import traceback import platform import signal import ConfigParser import xml.etree.ElementTree as etree from optparse import OptionParser, make_option from datetime import datetime import json try: # import logger from testkitlite.util.log import LOGGER except ImportError, err: print "[ Error: loading logging failed, error: %s ]\n" % err print "try to run command " \ "'export PYTHONPATH=/usr/local/lib/python2.7/dist-packages' or " \ "'export PYTHONPATH=/usr/local/lib/python2.7/site-packages' to resolve module missed issue" sys.exit(1) # get platform version info OS_VER = platform.system() JOIN = os.path.join EXISTS = os.path.exists DIRNAME = os.path.dirname BASENAME = os.path.basename ABSPATH = os.path.abspath SPLIT = os.path.split ISLINK = os.path.islink TESTKIT_DIR = "/opt/testkit/lite" if not OS_VER == "Linux" and not OS_VER == "Darwin": TESTKIT_DIR = DIRNAME(ABSPATH(__file__)) sys.path += [JOIN(TESTKIT_DIR)] TESTKIT_DIR = JOIN(TESTKIT_DIR, "results") LOG_DIR = TESTKIT_DIR TEST_PACKAGES_DIR = JOIN(TESTKIT_DIR, "test_packages") COMMON_FILTERS = { "suite": [], "set": [], "priority": [], "id": [], "type": [], "status": [], "component": []} down_status = False remote_test = False can_merge_result = False device_id = "" device_locked = False RUNNER = None # detect version option if "--version" in sys.argv: try: CONFIG = ConfigParser.ConfigParser() if platform.system() == "Linux": CONFIG.read('/opt/testkit/lite/VERSION') else: VERSION_FILE = JOIN(sys.path[0], 'VERSION') CONFIG.read(VERSION_FILE) VERSION = CONFIG.get('public_version', 'version') LOGGER.info("V%s" % VERSION) sys.exit() except ConfigParser.Error, err: LOGGER.error( "[ Error: fail to parse version info, error: %s ]\n" % err) sys.exit(1) # detect internal version option if "--internal-version" in sys.argv: try: CONFIG = ConfigParser.ConfigParser() if platform.system() == "Linux": CONFIG.read('/opt/testkit/lite/VERSION') else: VERSION_FILE = JOIN(sys.path[0], 'VERSION') CONFIG.read(VERSION_FILE) VERSION = CONFIG.get('internal_version', 'version') print VERSION sys.exit() except ConfigParser.Error, err: print "[ Error: fail to parse version info, error: %s ]\n" % err sys.exit(1) def varnarg(option, opt_str, value, parser): """ parser srg""" value = [] import re for arg in parser.rargs: if re.search('^--.+', arg) or \ re.search('^-[\D]', arg): break value.append(arg) del parser.rargs[:len(value)] setattr(parser.values, option.dest, value) def unlock_and_exit(exit_code=signal.SIGINT): if device_locked: release_device_lock(device_id) sys.exit(exit_code) def final_clean_test(): try: if RUNNER is not None: if RUNNER.session_id: RUNNER.finalize_test(RUNNER.session_id) if can_merge_result: RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR) if down_status: clean_testxml(OPTIONS.testxml, remote_test) except (KeyboardInterrupt, Exception), err: pass def sig_exit_handler(*args): final_clean_test() LOGGER.info("\n[ exiting testkit-lite on system signal ]\n") unlock_and_exit() signal.signal(signal.SIGTSTP, sig_exit_handler) signal.signal(signal.SIGTERM, sig_exit_handler) try: OPTION_LIST = [ make_option("-f", "--testxml", dest="testxml", action="callback", callback=varnarg, help="Specify the path of test definition file (tests.xml)." " If run more one test package,just list the all the path " " of \"tests.xml\" and separate with a whitespace"), make_option("-D", "--dryrun", dest="bdryrun", action="store_true", help="Dry-run the selected test cases"), make_option("-M", "--manual-only", dest="bmanualonly", action="store_true", help="Enable only manual tests"), make_option("-A", "--auto-only", dest="bautoonly", action="store_true", help="Enable only auto tests"), make_option("-o", "--output", dest="resultfile", help="Specify output file for result xml. \ If more than one testxml provided, \ results will be merged together to this output file"), make_option("-e", dest="exttest", action="store", help="Launch external test with a launcher,\ supports browser or other web-runtime"), make_option("-k", "--worker", dest="worker", action="store", help="Specify a test engine for execution, use value 'default' by default"), make_option("-p", "--target-platform", dest="targetplatform", action="store", help="specify special test target platform, e.g. xw_android, chrome_ubuntu"), make_option("--webdriver-url", dest="wdurl", action="store", help="specify special web driver listening url"), make_option("--version", dest="version_info", action="store_true", help="Show version information"), make_option("--internal-version", dest="internal_version_info", action="store_true", help="Show internal version information"), make_option("--deviceid", dest="device_serial", action="store", help="set device serial information"), make_option("--testprefix", dest="test_prefix", action="store", help="set prefix for test case entry"), make_option("--testenvs", dest="test_env", action="store", help="set environs for test case execution"), make_option("--comm", dest="commodule", action="store", help="set commodule by default," "set \"localhost\" for local web testing"), make_option("--capability", dest="capability", action="store", help="set platform for sepecfic device capability"), make_option("--debug", dest="debug", action="store_true", help="run in debug mode,more log information print out"), make_option("--rerun", dest="rerun", action="store_true", help="check if rerun test mode"), make_option("--non-active", dest="non_active", action="store_true", help="Disable the ability to set the result of \ core manual cases from the console"), make_option("-d", "--debugip", dest="debugip", action="store", help="specify tizen xwalk debug ip ") ] OPTION_LIST.extend([ make_option("--%s" % flt, dest="w%s" % flt, action="callback", callback=varnarg, help="Select the specified filter-rules : %s" % flt) for flt in COMMON_FILTERS]) try: # untrusted behaviour of %%prog USAGE = "%%prog [options] -f [prefix:]\"\" \n\ forms: %%prog -f [prefix:]\"/test.xml\" \n\ %%prog -f [prefix:]\"/test.xml\" -D\n\ %%prog -f [prefix:]\"/test.xml\" -A\n\ %%prog -f [prefix:]\"/test.xml\" -M\n\ %%prog -f [prefix:]\"/test.xml\" --set \n\ %%prog -f [prefix:]\"/test.xml\" --type \n\ %%prog -f [prefix:]\"/test.xml\" --status \n\ %%prog -f [prefix:]\"/test.xml\" --priority \n\ %%prog -f [prefix:]\"/test.xml\" --component \n\ %%prog -f [prefix:]\"/test.xml\" --id \n\ %%prog -f [prefix:]\"/test.xml\" --capability --comm \n\ %%prog -f [prefix:]\"/test.xml\" --comm \n\ %%prog -f [prefix:]\"/test1.xml /test2.xml /test3.xml\" \n\ exmaples of \"prefix\" usage: \n\ run a web test with a test definition (XML file) from device side: \n\ %%prog -f device:\"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\ run a web test with a test definition (XML file) from localhost: \n\ %%prog -f \"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\ exmaples of \"-e\" usage: \n\ run a web test package with TIZEN web-runtime, launcher provided in tests.xml, so \"-e\" is omitted: \n\ %%prog -f device:\"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\ run a web test package with chrome browser: \n\ %%prog -f \"/usr/share/webapi-webkit-tests/tests.xml\" -e \ 'google-chrome --allow-file-access-from-files --disable-web-security --start-maximized --user-data-dir=/home/test/data /home/test/webrunner/index.html' -A --comm localhost \n\ \n\ Note: \n\ 1) Proxy settings should be disabled when execute webapi packages\n\ 2) TestLog is stored to %s/latest\n\ 3) %%prog enables both auto and manual tests by default\n\ 4) Obviously -A and -M are conflict options\n\ 5) -e option does not support -D mode\n\ 6) The test cases' order in the result files might be arbitrary,\ when running same tests.xml with same options. This is caused \ by python's API 'getiterator' from module 'xml.etree.ElementTree'\n\ 7) run command 'testkit-lite', \ it might not be able to locate module 'testkitlite.engines.\ default.runner', \ run command 'export PYTHONPATH=/usr/local/lib/python2.7/dist-packages' or \ run command 'export PYTHONPATH=/usr/local/lib/python2.7/site-packages' \ to resolve this issue" % (LOG_DIR) except Exception: USAGE = None # detect non-params if len(sys.argv) == 1: sys.argv.append("-h") PARSERS = OptionParser(option_list=OPTION_LIST, usage=USAGE) (OPTIONS, ARGS) = PARSERS.parse_args() # init test engine here from testkitlite.util.connector import ConnectorBuilder from testkitlite.util.process import get_device_lock, release_device_lock, clean_testxml from testkitlite.util.session import TestSession from testkitlite.util.errors import TestCaseNotFoundException, TestEngineException #execute_type exec_types = ["auto","manual"] if OPTIONS.bautoonly and OPTIONS.bmanualonly: raise ValueError("-A and -M are conflict") elif OPTIONS.bautoonly: exec_types.remove("manual") elif OPTIONS.bmanualonly: exec_types.remove("auto") # connector options conn_opt = {} conn_opt['commodule'] = OPTIONS.commodule or "tizenmobile" conn_opt['deviceid'] = OPTIONS.device_serial CONNECTOR = ConnectorBuilder(conn_opt).get_connector() if CONNECTOR == None: sys.exit(1) device_id = CONNECTOR.get_device_info()['device_id'] os.environ['DEVICE_ID'] = device_id if not OPTIONS.non_active: device_locked = get_device_lock(device_id) if not device_locked: LOGGER.error("[ Error: Failed to get device for current session... ]\n") sys.exit(1) # load profile for wedrvier if OPTIONS.targetplatform: webdriver_vars = {} exec 'from testkitlite.capability.%s import initCapability' % OPTIONS.targetplatform if OPTIONS.targetplatform.upper().find('TIZEN') >= 0: if not OPTIONS.debugip: raise ValueError("For tizen xwalk, option --debugip is needed!") webdriver_vars = initCapability('TEST_APP_ID', OPTIONS.debugip) elif OPTIONS.targetplatform.upper().find('ANDROID') >= 0: webdriver_vars = initCapability('TEST_PKG_NAME', 'TEST_ACTIVITY_NAME') else: webdriver_vars = initCapability() os.environ['WEBDRIVER_VARS'] = json.dumps(webdriver_vars) # process test environ if OPTIONS.test_env: envs = OPTIONS.test_env.replace(';',' ').split(' ') for env_t in envs: env_t = env_t.strip() if not env_t: continue k, v = env_t.split('=') os.environ[k.strip()] = v.strip() # load test defintion files if "device:" in OPTIONS.testxml[0]: if not CONNECTOR.is_support_remote(): raise ValueError("For '%s' mode, please test file without prefix 'device:' " % conn_opt['commodule']) remote_test = True try: if not EXISTS(TEST_PACKAGES_DIR): os.makedirs(TEST_PACKAGES_DIR) except OSError, err: LOGGER.error("[ Error: " "can't create test package directory: %s, error: %s ]\n" % (TEST_PACKAGES_DIR, err)) unlock_and_exit() REMOTE_TESTLITS = OPTIONS.testxml[0] REMOTE_TESTLITS = REMOTE_TESTLITS.split(':')[1] TESTLISTARRARY = REMOTE_TESTLITS.split() LOCALARRY = [] for remote_file in TESTLISTARRARY: tmp_remote_file = SPLIT(remote_file) tmp_remote_folder = BASENAME(tmp_remote_file[0]) tmp_remote_test_xml = JOIN( tmp_remote_folder, tmp_remote_file[1]) local_test_package = JOIN( TEST_PACKAGES_DIR, tmp_remote_test_xml) down_status = CONNECTOR.download_file(remote_file, local_test_package) if not down_status: LOGGER.error("Failed to get test file '%s' from device" % remote_file) unlock_and_exit() LOCALARRY.append(local_test_package) OPTIONS.testxml = LOCALARRY else: if len(OPTIONS.testxml) == 1: i, start, end = 0, 0, 0 LOCAL_TESTLISTS = [] temp_xml = OPTIONS.testxml[0] while(i < len(temp_xml)): tmp = temp_xml[i:len(temp_xml)] if ".xml" in tmp: index = tmp.index(".xml") + 4 + i end = index i = index + 1 LOCAL_TESTLISTS.append(temp_xml[start:end]) start = index + 1 else: LOGGER.error("No xml found") break OPTIONS.testxml = LOCAL_TESTLISTS # load test engine workername = OPTIONS.worker or 'default' try: exec "from testkitlite.engines.%s import TestWorker" % workername except Exception as error: raise TestEngineException(workername) WORKER = TestWorker(CONNECTOR) # create runner RUNNER = TestSession(CONNECTOR, WORKER) # apply all options RUNNER.set_global_parameters(OPTIONS) # set capability if not RUNNER.get_capability(OPTIONS.capability): unlock_and_exit() # apply filter WFILTERS = {} for flt in COMMON_FILTERS: if eval('OPTIONS.w%s' % flt): WFILTERS[flt] = eval('OPTIONS.w%s' % flt) RUNNER.add_filter_rules(**WFILTERS) if not OPTIONS.testxml: LOGGER.error("[ Error: not specify a test xml... ]\n") unlock_and_exit() # 1) prepare log dir if OS_VER == "Linux" or OS_VER == "Darwin": SESSION = datetime.today().isoformat('-') else: SESSION = datetime.today().strftime("%Y-%m-%d_%H_%M_%S") CURRENT_LOG_DIR = JOIN(LOG_DIR, SESSION) LATEST_DIR = JOIN(LOG_DIR, "latest") try: if EXISTS(LATEST_DIR): os.remove(LATEST_DIR) if ISLINK(LATEST_DIR): os.remove(LATEST_DIR) os.makedirs(CURRENT_LOG_DIR) if os.name == "posix": os.symlink(CURRENT_LOG_DIR, LATEST_DIR) except IOError, err: LOGGER.error("[ Error: create session log directory: " "%s failed, error: %s ]\n" % (CURRENT_LOG_DIR, err)) # 2) prepare run test # run more than one tests.xml # 1. run all auto cases from the xmls # 2. run all manual cases from the xmls TESTXMLS = set(OPTIONS.testxml) for t in TESTXMLS: if EXISTS(t): filename = t filename = os.path.splitext(filename)[0] if OS_VER == "Linux" or OS_VER == "Darwin": if not filename.startswith('/'): LOGGER.error("[ Error:" " xml file %s should start with '/' ]" % filename) unlock_and_exit() file_items = filename.split('/') else: file_items = filename.split('\\') if len(file_items) < 2 or file_items[-2] == "" or file_items[-1] == "": LOGGER.error("[ Error:" " unable to find package name from %s ]" % t) unlock_and_exit() filename = file_items[-2] + '_' + file_items[-1] filename = "%s.total" % BASENAME(filename) resultfile = "%s.xml" % filename resultfile = JOIN(CURRENT_LOG_DIR, resultfile) try: ep = etree.parse(t) suiteparent = ep.getroot() except etree.ParseError: LOGGER.error("[ Error: no case found in testxml, " "pls check the test package ]\n") unlock_and_exit() no_test_definition = 1 for tf in ep.getiterator('test_definition'): no_test_definition = 0 if no_test_definition: suiteparent = etree.Element('test_definition') suiteparent.tail = "\n" for suite in ep.getiterator('suite'): suite.tail = "\n" suiteparent.append(suite) WFILTERS['execution_type'] = exec_types RUNNER.add_filter_rules(**WFILTERS) RUNNER.apply_filter(suiteparent) # merge duplicated test set under suite node tset_list = set() for suite in ep.getiterator('suite'): for tset in suite.getiterator('set'): for testcase in tset.getiterator('testcase'): tset.remove(testcase) if tset.get('name') in tset_list: suite.remove(tset) else: tset_list.add(tset.get('name')) try: with open(resultfile, 'w') as output: tree = etree.ElementTree(element=suiteparent) tree.write(output) except IOError, err: LOGGER.error( "[ Error: create filtered total result file: %s failed, " "error: %s ]\n" % (resultfile, err)) else: LOGGER.error("[ test xml file '%s' not found ]" % t) unlock_and_exit() for t in TESTXMLS: for e_type in exec_types: try: WFILTERS['execution_type'] = [e_type] RUNNER.add_filter_rules(**WFILTERS) RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR) except IOError, err: LOGGER.error("[ Error: prepare_run test xml: " "%s from testkit-lite failed, error: %s ]\n" % (t, err)) START_TIME = datetime.today().strftime("%Y-%m-%d_%H_%M_%S") try: can_merge_result = True RUNNER.run_case(CURRENT_LOG_DIR) except TestCaseNotFoundException, err: LOGGER.info("\n[ Error: exiting testkit-lite on error: %s ]\n" % err) unlock_and_exit() except Exception, err: clean_testxml(TESTXMLS, remote_test) traceback.print_exc() LOGGER.error("[ Error: run test failed, error: %s ]\n" % err) try: RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR) clean_testxml(TESTXMLS, remote_test) LOGGER.info("[ all tasks for testkit lite are accomplished, goodbye ]") unlock_and_exit(0) except Exception, err: traceback.print_exc() clean_testxml(TESTXMLS,remote_test) LOGGER.error("[ Error: merge result failed, error: %s ]\n" % err) unlock_and_exit() except (TestEngineException, KeyboardInterrupt), err: final_clean_test() LOGGER.info("\n[ exiting testkit-lite on user cancel ]\n") unlock_and_exit() except Exception, err: final_clean_test() LOGGER.error("\n[ Error: exiting testkit-lite due to critical error: %s ]\n" % err) traceback.print_exc() unlock_and_exit()