#!/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  <jing.j.wang@intel.com>
#              Yuanyuan,Zou  <yuanyuanx.zou@intel.com>
""" 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

from testkitlite.util.errors import TestCaseNotFoundException, TestEngineException


os.environ["DBUS_SESSION_BUS_ADDRESS"]='unix:path=/run/dbus/system_bus_socket'
os.environ["DISPLAY"]=':0.0'

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": [],
    "set_type": []}
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)
#try:
#    CONFIG = ConfigParser.ConfigParser()
#    CONFIG.read('/opt/testkit/lite/commodule/CONFIG')
#    tizen = CONFIG.get('TIZEN_USER','tizen_user')
#    if tizen:
#        os.environ['TIZEN_USER'] = tizen
#except:
#    pass   

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, use ';' to separator multi option"),
        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 "),
        make_option("--init-script", dest="initscript", action="store",
                   help="init script before script"),
        make_option("--post-script", dest="postscript", action="store",
                   help="post script"),
        make_option('--disable-dlog', dest="disabledlog", action="store_true",
                   help="disable dlog information")
       # make_option('--user', dest="user", action="store",
       #            help="setting test user")
    ]

    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:]\"<somewhere/test.xml>\" \n\
forms:    %%prog  -f [prefix:]\"<somewhere>/test.xml\" \n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -D\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -A\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" -M\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --set <set_name>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --type <type_name>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --status <status_name>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --priority <priority_value>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --component <component_name>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --id <case_id>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --capability <capability_file> --comm <comm_type>\n\
          %%prog  -f [prefix:]\"<somewhere>/test.xml\" --comm <comm_type>\n\
          %%prog  -f [prefix:]\"<somewhere>/test1.xml <somewhere>/test2.xml <somewhere>/test3.xml\" \n\
          %%prog  -f [prefix:]\"<somewhere>/test1.xml <somewhere>/test2.xml <somewhere>/test3.xml --testenvs 'wd-url=...;wd-debugip=...'\" \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")
   
    if OPTIONS.worker and OPTIONS.worker != 'webdriver':
        raise ValueError('-k is just service for webdriver, you can not set it ,using default')
  
    debugip = None
    if OPTIONS.test_env:
        #envs = OPTIONS.test_env.replace(';',' ').split(' ')
        envs = OPTIONS.test_env.split(';')
        for env_t in envs:
            env_t = env_t.strip()
            if not env_t:
                continue
            k, v = env_t.split('=')
            if 'app_launcher' in v:
                os.environ[k.strip()] = "app_launcher -s"
            else:
                os.environ[k.strip()] = v.strip()
            if cmp(k.strip(),"wd-debugip") == 0:
               debugip = v.strip()
    if not os.environ.has_key('TIZEN_USER'):    
        try:
            CONFIG = ConfigParser.ConfigParser()
            CONFIG.read('/opt/testkit/lite/commodule/CONFIG')
            tizen = CONFIG.get('TIZEN','tizen_user')
            if tizen:
                os.environ['TIZEN_USER'] = tizen
        except:
            pass   
    # connector options
    conn_opt = {}
    #conn_opt['commodule'] = OPTIONS.commodule or "tizenmobile"
    conn_opt['commodule'] = OPTIONS.commodule or "localhost"
    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)

    #modify at 2014.09.11
    targetplatform = None
    #debugip = None
    if OPTIONS.exttest and OPTIONS.worker and OPTIONS.worker.upper() == 'WEBDRIVER':
        if conn_opt['commodule'] == 'localhost':
            targetplatform = "chrome_ubuntu"
            os.environ['targetplatform'] = targetplatform
        elif conn_opt['commodule'] == 'tizenmobile':
            targetplatform = "xw_tizen"
            os.environ['targetplatform'] = targetplatform 
        elif conn_opt['commodule'] == 'androidmobile':
            targetplatform = "xw_android"
            os.environ['targetplatform'] = targetplatform 
    # process test environ
    if OPTIONS.test_env:
        #envs = OPTIONS.test_env.replace(';',' ').split(' ')
        envs = OPTIONS.test_env.split(';')
        for env_t in envs:
            env_t = env_t.strip()
            if not env_t:
                continue
            k, v = env_t.split('=')
            if 'app_launcher' in v:
                os.environ[k.strip()] = "app_launcher -s"
            else: 
                os.environ[k.strip()] = v.strip()
        
    # load profile for wedrvier
    if targetplatform:
        webdriver_vars = {}
        exec 'from testkitlite.capability.%s import initCapability' % targetplatform
        if targetplatform.upper().find('TIZEN') >= 0:
            if not debugip:
                raise ValueError("For tizen xwalk, option --debugip is needed!")
            webdriver_vars = initCapability('TEST_APP_ID', debugip)
        elif 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()
    if OPTIONS.initscript:
        os.environ['initscript'] = OPTIONS.initscript
    if OPTIONS.postscript:
        os.environ['postscript'] = OPTIONS.postscript
    # 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
    if OPTIONS.worker:
        os.environ['WORKER'] = OPTIONS.worker

    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()
