#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import argparse
import json
import os
import shutil
import sys
import subprocess

# we use symlinked files
# the directory structure breaks on the second run with .pyc files
sys.dont_write_bytecode = True

import testinfra
import testvm

topdir = os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
sys.path.append(os.path.abspath(os.path.dirname(os.path.realpath(__file__))))

machine_test_dir = "/tmp/avocado_tests"

# this is where the avocado results on the test machine will be stored
avocado_results_dir = "/root/avocado_results"

# this is where we publish logs on the local machine
arg_attachments_dir = os.environ.get("TEST_ATTACHMENTS", None)
if not arg_attachments_dir:
    arg_attachments_dir = os.getcwd()
if not os.path.exists(arg_attachments_dir):
    os.makedirs(arg_attachments_dir)

def prepare_avocado_tests(machine):
    """ Upload all avocado related files and create the results directory
    """
    machine.execute(command="mkdir -p " + avocado_results_dir)
    machine.upload([os.path.join(machine.test_dir, "avocado")], machine_test_dir)

def tap_output(machine):
    json_info = json.loads(machine.execute(command="cat " + os.path.join(avocado_results_dir, "latest/results.json"), quiet=True))
    print "\nTAP output -------------------------"
    print "1..%s" % len(json_info['tests'])
    counter = 1
    for t in json_info['tests']:
        test_status = t['status']
        test_name = t['test']
        test_log = t['logfile']
        test_time = t['time']
        print "# ----------------------------------------------------------------------"
        print "# %s (time %d secs)" % (test_name, test_time)
        print "#"
        print machine.execute(command="cat " + test_log, quiet=True)
        print "# TEST LOG END -------"
        if test_status == 'PASS':
            print "ok %s %s (time %d secs)" % (counter, test_name, test_time)
        else:
            print "not ok %s %s (time %d secs)" % (counter, test_name, test_time)
        counter = counter + 1
    if len(json_info['tests']) != json_info['pass']:
        print "# %d TESTS FAILED (time: %d secs)" % (len(json_info['tests']) - json_info['pass'], json_info['time'])
    else:
        print "# TESTS PASSED (time: %d secs)" %  json_info['time']

def run_avocado_tests(machine, avocado_tests, print_failed=True, env=[]):
    """ Execute avocado on the machine with the passed environment variables and run
        the specified tests. For example:
        run_avocado(machine,
                    ["checklogin-basic.py", "checklogin-raw.py"],
                    ["HUB=" + self.selenium.address, "BROWSER=firefox"]
                   )
        Return success of the tests (True: all passed, False: at least one failed)
        If 'print_failed' is True, attempt to print a list of failed tests
    """
    return_state = True
    cmd_parts = env + ["avocado run",
                 "--job-results-dir " + avocado_results_dir,
                 ' '.join([machine_test_dir + os.sep + x for x in avocado_tests]),
                 ">&2"
                 ]

    try:
        machine.execute(" ".join(cmd_parts))
    except:
        if print_failed:
            # try to get the list of failed tests
            try:
                failed_tests_info = machine.execute(
                        command="cat " + os.path.join(avocado_results_dir, "latest/results.json"),
                        quiet=True
                    )
                machine.execute("cp -v *.png %s/ 2>/dev/null || true" % avocado_results_dir)
                failed_tests = json.loads(failed_tests_info)
                for t in failed_tests['tests']:
                    test_status = t['status']
                    if test_status != 'PASS':
                        test_name = t['test']
                        if test_name.startswith(machine_test_dir + '/'):
                            test_name = test_name[(len(machine_test_dir) + 1):]
                        fail_reason = t['fail_reason']
                        print "[" + test_status + "] " + test_name + " (" + fail_reason + ")"
            except:
                print "Unable to show avocado test result summary"
        return_state = False
    tap_output(machine)
    return return_state

def copy_avocado_logs(machine, title):
    if machine and machine.address:
        dir = "%s-%s.avocado" % (machine.address, title)
        # check if the remote directory exists (if it doesn't, we want to quit silently)
        test_command = "if test -d '{0}'; then echo exists; fi".format(avocado_results_dir)
        if "exists" not in machine.execute(command=test_command,quiet=True):
            return
        machine.download_dir(avocado_results_dir, dir)
        if os.path.exists(dir):
            # avocado creates a "latest" symlink, we can delete that here
            shutil.rmtree(path=os.path.join(dir, "latest"), ignore_errors=True)
            print "Avocado results copied to %s" % (dir)

            # compress the logs and attach that
            archive = os.path.join(arg_attachments_dir, "{0}.tar.gz".format(dir))
            subprocess.call(["tar", "cfz", archive, dir])

def wait_for_selenium_running(machine, host, port=4444):
    WAIT_SELENIUM_RUNNING = """#!/bin/sh
until curl -s --connect-timeout 1 http://%s:%d >/dev/null; do
sleep 0.5;
done;
""" % (host, port)
    with testvm.stdchannel_redirected(sys.stdout, os.devnull):
        with testvm.Timeout(seconds=30, error_message="Timeout while waiting for selenium to start"):
            machine.execute(script=WAIT_SELENIUM_RUNNING)

def run_avocado(avocado_tests, verbose, browser, download_logs):
    use_selenium = (browser != 'none')
    machine = testvm.VirtMachine(verbose=verbose, label="avocado")
    selenium = testvm.VirtMachine(image="selenium", label="selenium", verbose=verbose) if use_selenium else None

    try:
        machine.start()
        if selenium:
            selenium.start()

        machine.wait_boot()
        machine.start_cockpit()
        if selenium:
            selenium.wait_boot()

            # start selenium on the server
            selenium.upload(["./guest/selenium_start.sh"], "/root")
            selenium.execute(command="/root/selenium_start.sh")

        machine.execute("adduser test")
        machine.execute("echo superhardpasswordtest5554 | passwd --stdin test")
        machine.execute("usermod -a -G wheel test")
        machine.execute("echo 'test        ALL=(ALL)       NOPASSWD: ALL' >> /etc/sudoers")
        machine.execute("sed -i 's/^Defaults.*requiretty/#&/g' /etc/sudoers")
        machine.execute("echo 'Defaults !requiretty' >> /etc/sudoers")

        if selenium:
            wait_for_selenium_running(machine, selenium.address)

        prepare_avocado_tests(machine)

        # Now actually run the tests
        selenium_address = selenium.address if selenium else ""
        env = [ "HUB=" + selenium_address, "GUEST=" + machine.address, "BROWSER=" + browser ]
        success = run_avocado_tests(machine, avocado_tests, env=env)

        if not success:
            copy_avocado_logs(machine, "FAILED")
        elif download_logs:
            copy_avocado_logs(machine, "PASSED")

        return success
    finally:
        machine.kill()
        if selenium:
            selenium.kill()

def main():
    parser = argparse.ArgumentParser(description='Run Cockpit Avocado test(s)')
    parser.add_argument('-v', '--verbose', dest="verbosity", action='store_true',
                        help='Verbose output')
    parser.add_argument('-q', '--quick', action='store_true', help='Build faster')
    parser.add_argument('--install', dest='install', action='store_true',
                        help='Build and install Cockpit into test VMs')
    parser.add_argument("-b", "--browser", choices=['none', 'firefox', 'chrome'],
                    default='none',
                    help="selenium browser choice - in case of none, selenium isn't started")
    parser.add_argument("-s", "--selenium-tests", dest='selenium_tests', action='store_true',
                        help="Run known browser/selenium tests")
    parser.add_argument("-t", "--tests", dest='regular_tests', action='store_true',
                        help="Run known regular (non-selenium) tests")
    parser.add_argument("-l", "--logs", dest='download_logs', action='store_true',
                        help="Always download avocado logs, even on success")
    parser.add_argument('tests', nargs='*')

    opts = parser.parse_args()
    if opts.install:
        sys.stderr.write("Building and installing Cockpit ...\n")
        subprocess.check_call([ os.path.join(topdir, "vm-download"), testinfra.DEFAULT_IMAGE, "selenium" ])
        subprocess.check_call([ os.path.join(topdir, "vm-reset") ])

        cmd = [ os.path.join(topdir, "vm-install") ]
        if opts.verbosity:
            cmd.append("--verbose")
        if opts.quick:
            cmd.append("--quick")
        subprocess.check_call(cmd)

    regular_tests = [ "checklogin-basic.py", "checklogin-raw.py" ]
    browser_tests = [ "selenium-login.py" ]

    if opts.regular_tests:
        opts.tests += regular_tests
    if opts.selenium_tests:
        opts.tests += browser_tests

    # if we don't have a browser but are supposed to run selenium related tests, fail
    if opts.browser is 'none':
        for t in browser_tests:
            if t in opts.tests:
                sys.stderr.write("Unable to run test {0} because browser isn't set.\n".format(t))
                return 1

    if len(opts.tests) is 0:
        sys.stderr.write("No tests specified.\n")
        return 0

    if run_avocado(opts.tests, opts.verbosity, opts.browser, opts.download_logs):
        return 0
    else:
        return 1

if __name__ == '__main__':
    sys.exit(main())
