fix importing issue of selenium
[test/tools/testkit-lite.git] / testkit-lite
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2012 Intel Corporation
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # Authors:
16 #              Jing,Wang  <jing.j.wang@intel.com>
17 #              Yuanyuan,Zou  <yuanyuanx.zou@intel.com>
18 """ testkit lite tools"""
19
20 import os
21 import sys
22 import traceback
23 import platform
24 import signal
25 import ConfigParser
26 import xml.etree.ElementTree as etree
27 from optparse import OptionParser, make_option
28 from datetime import datetime
29 import json
30
31
32 try:
33     # import logger
34     from testkitlite.util.log import LOGGER
35 except ImportError, err:
36     print "[ Error: loading logging failed, error: %s ]\n" % err
37     print "try to run command " \
38         "'export PYTHONPATH=/usr/local/lib/python2.7/dist-packages' or " \
39         "'export PYTHONPATH=/usr/local/lib/python2.7/site-packages' to resolve module missed issue"
40     sys.exit(1)
41
42
43 # get platform version info
44 OS_VER = platform.system()
45 JOIN = os.path.join
46 EXISTS = os.path.exists
47 DIRNAME = os.path.dirname
48 BASENAME = os.path.basename
49 ABSPATH = os.path.abspath
50 SPLIT = os.path.split
51 ISLINK = os.path.islink
52
53 TESTKIT_DIR = "/opt/testkit/lite"
54 if not OS_VER == "Linux" and not OS_VER == "Darwin":
55     TESTKIT_DIR = DIRNAME(ABSPATH(__file__))
56     sys.path += [JOIN(TESTKIT_DIR)]
57     TESTKIT_DIR = JOIN(TESTKIT_DIR, "results")
58
59 LOG_DIR = TESTKIT_DIR
60 TEST_PACKAGES_DIR = JOIN(TESTKIT_DIR, "test_packages")
61 COMMON_FILTERS = {
62     "suite": [],
63     "set": [],
64     "priority": [],
65     "id": [],
66     "type": [],
67     "status": [],
68     "component": []}
69 down_status = False
70 remote_test = False
71 can_merge_result = False
72 device_id = ""
73 device_locked = False
74 RUNNER = None
75
76 # detect version option
77 if "--version" in sys.argv:
78     try:
79         CONFIG = ConfigParser.ConfigParser()
80         if platform.system() == "Linux":
81             CONFIG.read('/opt/testkit/lite/VERSION')
82         else:
83             VERSION_FILE = JOIN(sys.path[0], 'VERSION')
84             CONFIG.read(VERSION_FILE)
85         VERSION = CONFIG.get('public_version', 'version')
86         LOGGER.info("V%s" % VERSION)
87         sys.exit()
88     except ConfigParser.Error, err:
89         LOGGER.error(
90             "[ Error: fail to parse version info, error: %s ]\n" % err)
91         sys.exit(1)
92
93 # detect internal version option
94 if "--internal-version" in sys.argv:
95     try:
96         CONFIG = ConfigParser.ConfigParser()
97         if platform.system() == "Linux":
98             CONFIG.read('/opt/testkit/lite/VERSION')
99         else:
100             VERSION_FILE = JOIN(sys.path[0], 'VERSION')
101             CONFIG.read(VERSION_FILE)
102         VERSION = CONFIG.get('internal_version', 'version')
103         print VERSION
104         sys.exit()
105     except ConfigParser.Error, err:
106         print "[ Error: fail to parse version info, error: %s ]\n" % err
107         sys.exit(1)
108
109
110 def varnarg(option, opt_str, value, parser):
111     """ parser srg"""
112     value = []
113     import re
114     for arg in parser.rargs:
115         if re.search('^--.+', arg) or \
116                 re.search('^-[\D]', arg):
117             break
118         value.append(arg)
119
120     del parser.rargs[:len(value)]
121     setattr(parser.values, option.dest, value)
122
123 def unlock_and_exit(exit_code=signal.SIGINT):
124     if device_locked:
125         release_device_lock(device_id)
126     sys.exit(exit_code)
127
128 def final_clean_test():
129     try:
130         if RUNNER is not None:
131             if RUNNER.session_id:
132                 RUNNER.finalize_test(RUNNER.session_id)
133             if can_merge_result:
134                 RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR)
135             if down_status:
136                 clean_testxml(OPTIONS.testxml, remote_test)
137     except (KeyboardInterrupt, Exception), err:
138         pass
139
140 def sig_exit_handler(*args):
141     final_clean_test()
142     LOGGER.info("\n[ exiting testkit-lite on system signal ]\n")
143     unlock_and_exit()
144
145 signal.signal(signal.SIGTSTP, sig_exit_handler)
146 signal.signal(signal.SIGTERM, sig_exit_handler)
147 try:
148     OPTION_LIST = [
149         make_option("-f", "--testxml", dest="testxml",
150                     action="callback", callback=varnarg,
151                     help="Specify the path of test definition file (tests.xml)."
152                     " If run more one test package,just list the all the path "
153                     " of \"tests.xml\" and separate with a whitespace"),
154         make_option("-D", "--dryrun", dest="bdryrun",
155                     action="store_true",
156                     help="Dry-run the selected test cases"),
157         make_option("-M", "--manual-only", dest="bmanualonly",
158                     action="store_true",
159                     help="Enable only manual tests"),
160         make_option("-A", "--auto-only", dest="bautoonly",
161                     action="store_true",
162                     help="Enable only auto tests"),
163         make_option("-o", "--output", dest="resultfile",
164                     help="Specify output file for result xml. \
165                     If more than one testxml provided, \
166                     results will be merged together to this output file"),
167         make_option("-e", dest="exttest", action="store",
168                     help="Launch external test with a launcher,\
169                          supports browser or other web-runtime"),
170         make_option("-k", "--worker", dest="worker", action="store",
171                     help="Specify a test engine for execution, use value 'default' by default"),
172         make_option("-p", "--target-platform", dest="targetplatform",
173                     action="store",
174                     help="specify special test target platform, e.g. xw_android, chrome_ubuntu"),
175         make_option("--webdriver-url", dest="wdurl", action="store",
176                     help="specify special web driver listening url"),
177         make_option("--version", dest="version_info", action="store_true",
178                     help="Show version information"),
179         make_option("--internal-version", dest="internal_version_info",
180                     action="store_true",
181                     help="Show internal version information"),
182         make_option("--deviceid", dest="device_serial", action="store",
183                     help="set device serial information"),
184         make_option("--testprefix", dest="test_prefix", action="store",
185                     help="set prefix for test case entry"),
186         make_option("--testenvs", dest="test_env", action="store",
187                     help="set environs for test case execution"),
188         make_option("--comm", dest="commodule", action="store",
189                     help="set commodule by default,"
190                     "set \"localhost\" for local web testing"),
191         make_option("--capability", dest="capability", action="store",
192                     help="set platform for sepecfic device capability"),
193         make_option("--debug", dest="debug", action="store_true",
194                     help="run in debug mode,more log information print out"),
195         make_option("--rerun", dest="rerun", action="store_true",
196                     help="check if rerun test mode"),
197         make_option("--non-active", dest="non_active", action="store_true",
198                     help="Disable the ability to set the result of \
199                     core manual cases from the console"),
200         make_option("-d", "--debugip", dest="debugip", action="store",
201                     help="specify tizen xwalk debug ip ")
202
203     ]
204
205     OPTION_LIST.extend([
206         make_option("--%s" % flt,
207                     dest="w%s" % flt, action="callback", callback=varnarg,
208                     help="Select the specified filter-rules : %s" % flt)
209         for flt in COMMON_FILTERS])
210
211     try:
212         # untrusted behaviour of %%prog
213         USAGE = "%%prog [options] -f [prefix:]\"<somewhere/test.xml>\" \n\
214 forms:    %%prog  -f [prefix:]\"<somewhere>/test.xml\" \n\
215           %%prog  -f [prefix:]\"<somewhere>/test.xml\" -D\n\
216           %%prog  -f [prefix:]\"<somewhere>/test.xml\" -A\n\
217           %%prog  -f [prefix:]\"<somewhere>/test.xml\" -M\n\
218           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --set <set_name>\n\
219           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --type <type_name>\n\
220           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --status <status_name>\n\
221           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --priority <priority_value>\n\
222           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --component <component_name>\n\
223           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --id <case_id>\n\
224           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --capability <capability_file> --comm <comm_type>\n\
225           %%prog  -f [prefix:]\"<somewhere>/test.xml\" --comm <comm_type>\n\
226           %%prog  -f [prefix:]\"<somewhere>/test1.xml <somewhere>/test2.xml <somewhere>/test3.xml\" \n\
227 exmaples of \"prefix\" usage: \n\
228     run a web test with a test definition (XML file) from device side: \n\
229           %%prog -f device:\"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\
230     run a web test with a test definition (XML file) from localhost: \n\
231           %%prog -f \"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\
232 exmaples of \"-e\" usage: \n\
233     run a web test package with TIZEN web-runtime, launcher provided in tests.xml, so \"-e\" is omitted: \n\
234           %%prog -f device:\"/opt/usr/media/tct/opt/tct-websocket-w3c-tests/tests.xml\" -A \n\
235     run a web test package with chrome browser: \n\
236           %%prog -f \"/usr/share/webapi-webkit-tests/tests.xml\" -e \
237 '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\
238 \n\
239 Note: \n\
240           1) Proxy settings should be disabled when execute webapi packages\n\
241           2) TestLog is stored to %s/latest\n\
242           3) %%prog enables both auto and manual tests by default\n\
243           4) Obviously -A and -M are conflict options\n\
244           5) -e option does not support -D mode\n\
245           6) The test cases' order in the result files might be arbitrary,\
246 when running same tests.xml with same options. This is caused \
247 by python's API 'getiterator' from module 'xml.etree.ElementTree'\n\
248           7) run command 'testkit-lite', \
249 it might not be able to locate module 'testkitlite.engines.\
250 default.runner', \
251 run command 'export PYTHONPATH=/usr/local/lib/python2.7/dist-packages' or \
252 run command 'export PYTHONPATH=/usr/local/lib/python2.7/site-packages' \
253 to resolve this issue" % (LOG_DIR)
254     except Exception:
255         USAGE = None
256
257     # detect non-params
258     if len(sys.argv) == 1:
259         sys.argv.append("-h")
260
261     PARSERS = OptionParser(option_list=OPTION_LIST, usage=USAGE)
262     (OPTIONS, ARGS) = PARSERS.parse_args()
263
264     # init test engine here
265     from testkitlite.util.connector import ConnectorBuilder
266     from testkitlite.util.process import get_device_lock, release_device_lock, clean_testxml
267     from testkitlite.util.session import TestSession
268     from testkitlite.util.errors import TestCaseNotFoundException, TestEngineException
269
270     #execute_type
271     exec_types = ["auto","manual"]
272     if OPTIONS.bautoonly and OPTIONS.bmanualonly:
273         raise ValueError("-A and -M are conflict")
274     elif OPTIONS.bautoonly:
275         exec_types.remove("manual")
276     elif OPTIONS.bmanualonly:
277         exec_types.remove("auto")
278
279     # connector options
280     conn_opt = {}
281     conn_opt['commodule'] = OPTIONS.commodule or "tizenmobile"
282     conn_opt['deviceid'] = OPTIONS.device_serial
283     CONNECTOR = ConnectorBuilder(conn_opt).get_connector()
284     if CONNECTOR == None:
285         sys.exit(1)
286
287     device_id = CONNECTOR.get_device_info()['device_id']
288     os.environ['DEVICE_ID'] = device_id
289     if not OPTIONS.non_active:
290         device_locked = get_device_lock(device_id)
291         if not device_locked:
292             LOGGER.error("[ Error: Failed to get device for current session... ]\n")
293             sys.exit(1)
294
295     # load profile for wedrvier
296     if OPTIONS.targetplatform:
297         webdriver_vars = {}
298         exec 'from testkitlite.capability.%s import initCapability' % OPTIONS.targetplatform
299         if OPTIONS.targetplatform.upper().find('TIZEN') >= 0:
300             if not OPTIONS.debugip:
301                 raise ValueError("For tizen xwalk, option --debugip is needed!")
302             webdriver_vars = initCapability('TEST_APP_ID', OPTIONS.debugip)
303         elif OPTIONS.targetplatform.upper().find('ANDROID') >= 0:
304             webdriver_vars = initCapability('TEST_PKG_NAME', 'TEST_ACTIVITY_NAME')
305         else:
306             webdriver_vars = initCapability()
307         os.environ['WEBDRIVER_VARS'] = json.dumps(webdriver_vars)
308
309     # process test environ
310     if OPTIONS.test_env:
311         envs = OPTIONS.test_env.replace(';',' ').split(' ')
312         for env_t in envs:
313             env_t = env_t.strip()
314             if not env_t:
315                 continue
316             k, v = env_t.split('=')
317             os.environ[k.strip()] = v.strip()
318
319     # load test defintion files
320     if "device:" in OPTIONS.testxml[0]:
321         if not CONNECTOR.is_support_remote():
322             raise ValueError("For '%s' mode, please test file without prefix 'device:' " % conn_opt['commodule'])
323
324         remote_test = True
325         try:
326             if not EXISTS(TEST_PACKAGES_DIR):
327                 os.makedirs(TEST_PACKAGES_DIR)
328         except OSError, err:
329             LOGGER.error("[ Error: "
330                 "can't create test package directory: %s, error: %s ]\n" %
331                         (TEST_PACKAGES_DIR, err))
332             unlock_and_exit()
333         REMOTE_TESTLITS = OPTIONS.testxml[0]
334         REMOTE_TESTLITS = REMOTE_TESTLITS.split(':')[1]
335         TESTLISTARRARY = REMOTE_TESTLITS.split()
336         LOCALARRY = []
337
338         for remote_file in TESTLISTARRARY:
339             tmp_remote_file = SPLIT(remote_file)
340             tmp_remote_folder = BASENAME(tmp_remote_file[0])
341             tmp_remote_test_xml = JOIN(
342                 tmp_remote_folder, tmp_remote_file[1])
343             local_test_package = JOIN(
344                 TEST_PACKAGES_DIR, tmp_remote_test_xml)
345             down_status = CONNECTOR.download_file(remote_file, local_test_package)
346             if not down_status:
347                 LOGGER.error("Failed to get test file '%s' from device"
348                              % remote_file)
349                 unlock_and_exit()
350             LOCALARRY.append(local_test_package)
351         OPTIONS.testxml = LOCALARRY
352     else:
353         if len(OPTIONS.testxml) == 1:
354             i, start, end = 0, 0, 0
355             LOCAL_TESTLISTS = []
356             temp_xml = OPTIONS.testxml[0]
357             while(i < len(temp_xml)):
358                 tmp = temp_xml[i:len(temp_xml)]
359                 if ".xml" in tmp:
360                     index = tmp.index(".xml") + 4 + i
361                     end = index
362                     i = index + 1
363                     LOCAL_TESTLISTS.append(temp_xml[start:end])
364                     start = index + 1
365                 else:
366                     LOGGER.error("No xml found")
367                     break
368             OPTIONS.testxml = LOCAL_TESTLISTS
369
370
371     # load test engine
372     workername = OPTIONS.worker or 'default'
373     try:
374         exec "from testkitlite.engines.%s import TestWorker" % workername
375     except Exception as error:
376         raise TestEngineException(workername)
377     WORKER = TestWorker(CONNECTOR)
378
379     # create runner
380     RUNNER = TestSession(CONNECTOR, WORKER)
381     # apply all options
382     RUNNER.set_global_parameters(OPTIONS)
383     # set capability
384     if not RUNNER.get_capability(OPTIONS.capability):
385         unlock_and_exit()
386     # apply filter
387     WFILTERS = {}
388     for flt in COMMON_FILTERS:
389         if eval('OPTIONS.w%s' % flt):
390             WFILTERS[flt] = eval('OPTIONS.w%s' % flt)
391     RUNNER.add_filter_rules(**WFILTERS)
392
393     if not OPTIONS.testxml:
394         LOGGER.error("[ Error: not specify a test xml... ]\n")
395         unlock_and_exit()
396
397     # 1) prepare log dir
398     if OS_VER == "Linux" or OS_VER == "Darwin":
399         SESSION = datetime.today().isoformat('-')
400     else:
401         SESSION = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
402     CURRENT_LOG_DIR = JOIN(LOG_DIR, SESSION)
403     LATEST_DIR = JOIN(LOG_DIR, "latest")
404     try:
405         if EXISTS(LATEST_DIR):
406             os.remove(LATEST_DIR)
407         if ISLINK(LATEST_DIR):
408             os.remove(LATEST_DIR)
409         os.makedirs(CURRENT_LOG_DIR)
410         if os.name == "posix":
411             os.symlink(CURRENT_LOG_DIR, LATEST_DIR)
412     except IOError, err:
413         LOGGER.error("[ Error: create session log directory: "
414                      "%s failed, error: %s ]\n" % (CURRENT_LOG_DIR, err))
415
416     # 2) prepare run test
417     # run more than one tests.xml
418     # 1. run all auto cases from the xmls
419     # 2. run all manual cases from the xmls
420     TESTXMLS = set(OPTIONS.testxml)
421     for t in TESTXMLS:
422         if EXISTS(t):
423             filename = t
424             filename = os.path.splitext(filename)[0]
425             if OS_VER == "Linux" or OS_VER == "Darwin":
426                 if not filename.startswith('/'):
427                     LOGGER.error("[ Error:"
428                           " xml file %s should start with '/' ]" % filename)
429                     unlock_and_exit()
430                 file_items = filename.split('/')
431             else:
432                 file_items = filename.split('\\')
433             if len(file_items) < 2 or file_items[-2] == "" or file_items[-1] == "":
434                 LOGGER.error("[ Error:"
435                       " unable to find package name from %s ]" % t)
436                 unlock_and_exit()
437             filename = file_items[-2] + '_' + file_items[-1]
438             filename = "%s.total" % BASENAME(filename)
439             resultfile = "%s.xml" % filename
440             resultfile = JOIN(CURRENT_LOG_DIR, resultfile)
441             try:
442                 ep = etree.parse(t)
443                 suiteparent = ep.getroot()
444             except etree.ParseError:
445                 LOGGER.error("[ Error: no case found in testxml, "
446                              "pls check the test package ]\n")
447                 unlock_and_exit()
448             no_test_definition = 1
449             for tf in ep.getiterator('test_definition'):
450                 no_test_definition = 0
451             if no_test_definition:
452                 suiteparent = etree.Element('test_definition')
453                 suiteparent.tail = "\n"
454                 for suite in ep.getiterator('suite'):
455                     suite.tail = "\n"
456                     suiteparent.append(suite)
457             WFILTERS['execution_type'] = exec_types
458             RUNNER.add_filter_rules(**WFILTERS)
459             RUNNER.apply_filter(suiteparent)
460             # merge duplicated test set under suite node
461             tset_list = set()
462             for suite in ep.getiterator('suite'):
463                 for tset in suite.getiterator('set'):
464                     for testcase in tset.getiterator('testcase'):
465                         tset.remove(testcase)
466                     if tset.get('name') in tset_list:
467                         suite.remove(tset)
468                     else:
469                         tset_list.add(tset.get('name'))
470             try:
471                 with open(resultfile, 'w') as output:
472                     tree = etree.ElementTree(element=suiteparent)
473                     tree.write(output)
474             except IOError, err:
475                 LOGGER.error(
476                     "[ Error: create filtered total result file: %s failed, "
477                     "error: %s ]\n" % (resultfile, err))
478         else:
479             LOGGER.error("[ test xml file '%s' not found ]" % t)
480             unlock_and_exit()
481
482     for t in TESTXMLS:
483         for e_type in exec_types:
484             try:
485                 WFILTERS['execution_type'] = [e_type]
486                 RUNNER.add_filter_rules(**WFILTERS)
487                 RUNNER.prepare_run(t, resultdir=CURRENT_LOG_DIR)
488             except IOError, err:
489                 LOGGER.error("[ Error: prepare_run test xml: "
490                   "%s from testkit-lite failed, error: %s ]\n" % (t, err))
491
492     START_TIME = datetime.today().strftime("%Y-%m-%d_%H_%M_%S")
493     try:
494         can_merge_result = True
495         RUNNER.run_case(CURRENT_LOG_DIR)
496     except TestCaseNotFoundException, err:
497         LOGGER.info("\n[ Error: exiting testkit-lite on error: %s ]\n" % err)
498         unlock_and_exit()
499     except Exception, err:
500         clean_testxml(TESTXMLS, remote_test)
501         traceback.print_exc()
502         LOGGER.error("[ Error: run test failed, error: %s ]\n" % err)
503
504     try:
505         RUNNER.merge_resultfile(START_TIME, CURRENT_LOG_DIR)
506         clean_testxml(TESTXMLS, remote_test)
507         LOGGER.info("[ all tasks for testkit lite are accomplished, goodbye ]")
508         unlock_and_exit(0)
509     except Exception, err:
510         traceback.print_exc()
511         clean_testxml(TESTXMLS,remote_test)
512         LOGGER.error("[ Error: merge result failed, error: %s ]\n" % err)
513         unlock_and_exit()
514 except (TestEngineException, KeyboardInterrupt), err:
515     final_clean_test()
516     LOGGER.info("\n[ exiting testkit-lite on user cancel ]\n")
517     unlock_and_exit()
518 except Exception, err:
519     final_clean_test()
520     LOGGER.error("\n[ Error: exiting testkit-lite due to critical error: %s ]\n" % err)
521     traceback.print_exc()
522     unlock_and_exit()