#!/usr/bin/env python
#
# Copyright (C) 2010, 2011, 2012, 2013, 2014 Intel, Inc.
#
#    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; version 2
#    of the License.
#
#    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.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
"""
This script is used to test images on real target devices.
"""

import os
import sys
import glob
import logging
import tarfile
import subprocess
from lxml import etree
import xml.etree.ElementTree as ET
from common.buildtrigger import trigger_info, trigger_next


def fetch_image(url):
    """
    Tries to download locally the image that will be tested.
    """
    logging.info("Attempt retrieving flashable image from {0} ."
                 .format(url))
    try:
        command = ["/usr/bin/wget", "-r", "-l1", "-np", "-nd", "-q", "-A",
                   os.getenv("TESTABLE_IMAGE_DOWNLOADABLES"), url]
        subprocess.check_output(command)
        image = glob.glob("*.bz2")[0]
        logging.info("Image retrieved successfully.")
        return image
    except (IndexError, ValueError, subprocess.CalledProcessError,
            OSError) as error:
        logging.critical("Failure retrieving the image.")
        logging.critical(error)
        return None


def test_image(image):
    """
    Flashes the target device and runs the smoke test.
    """
    try:
        logging.info("Testing image {0}.".format(image))
        command = ["/usr/bin/tztestrobot.run", image]
        output = subprocess.check_output(command)
    except OSError as error:
        result = "TESTER_FAILURE"
        logging.critical("Failed to flash the image.\n" +
                         "Error: {0}".format(error))
    except (ValueError, subprocess.CalledProcessError) as error:
        result = "FAIL"
        logging.critical("The device is not reachable." +
                         "Error: {0}".format(error))
    else:
        result = "PASS"
        logging.info("The device can boot and all the tests were run." +
                     "Output: {0}".format(output))
    return result


def create_empty_results():
    """
    In case the testing is not possible (image not supported,
    not booting, etc.) create empty dataset.
    """
    results_folder = "empty_results_dir"
    try:
        os.makedirs(results_folder)
        with open(os.path.join(results_folder, "results.xml"), "w") as res:
            # Note: XXX
            # Jenkins will not accept a results.xml as valid JUnit file
            # unless it contains at least one testcase, even if empty.
            # Without it, the build will be marked as FAILED, which is
            # incorrect in some cases, ex: unsupported image.
            res.write('<?xml version="1.0" encoding="utf-8"?>\n'
                      '<testsuite errors="0" failures="0" '
                      'name="none" skips="0" tests="0" time="0">\n'
                      '<testcase />\n'
                      '</testsuite>\n')
    except OSError:
        logging.critical("Cannot create empty directory: " + results_folder)
    return results_folder


def pack_results(results_folder):
    """
    Creates a tarball containing the test results.
    """
    logging.info("Packing test results.")
    try:
        tar = tarfile.open("results.tar.gz", "w:gz")
        tar.add(results_folder)
    except (ValueError, OSError) as error:
        logging.critical("Failed packing test results." +
                         "Error: {0}".format(error))
    else:
        logging.info("Completed packing test results.")


def xml_tree_to_dict(tree):
    """
    Converts junit xml file to a dictionary.
    """
    # pylint: disable=
    dic = {tree.tag: map(xml_tree_to_dict, tree.iterchildren())}
    # pylint: enable=
    dic.update(('@' + k, v) for k, v in tree.attrib.iteritems())
    dic['text'] = tree.text
    return dic


def get_test_dict(xml_file):
    """
    Gets simplified list of test cases and their status.
    """
    full_results = xml_tree_to_dict(etree.parse(xml_file).getroot())
    dic = {}
    for test in full_results["testsuite"]:
        dic[test["@name"]] = 1 if test["testcase"] == [] else 0
    return dic


def image_supported(image):
    """
    Check if the tester can test this specific image.
    """
    try:
        command = ["/usr/bin/tztestrobot.run", "--testable", image]
        logging.info("Verifying testability of image {0}.".format(image))
        subprocess.check_output(command)
    except subprocess.CalledProcessError:
        logging.info("Image {0} not supported by the tester.".format(image))
        return False
    logging.info("Image {0} supported by the tester.".format(image))
    return True

def propagate_results(fields, image):
    """
    Store results to file and trigger publishing job.
    """
    logging.info("Packing results and triggering next job.")
    with open("Results.ENV", "w") as results:
        results.write("TEST_RESULT=" + fields["test_result"] + "\n")
        results.write("IMAGE=" + image + "\n")

    results_folder = glob.glob("aft_results.*")
    if results_folder != []:
        results_folder = results_folder[0]
        fields["test_details"] = get_test_dict(results_folder + "/results.xml")
    else:
        results_folder = create_empty_results()
        fields["test_details"] = ""
    pack_results(results_folder)
    trigger_next("RESULTS-PUBLISHING", fields)

def main():
    """
    The main body.
    """
    # Note: the script, by design, will not return errors
    # Rather, it will inject the error status in the environment
    # A Groovy post-build script will adjust the build status

    logging.basicConfig(filename='tester.log', level=logging.DEBUG)
    fields = trigger_info(os.getenv("TRIGGER_INFO"))

    try:
        file_name = ""
        xml = ET.fromstring(fields["image_xml"])
        logging.info("Extracted xml from environment {0}".format(xml))
        storage = xml.find("storage")
        logging.info("Storage {0}".format(storage))
        disk = storage.find("disk")
        logging.info("Disk {0}".format(disk))
        file_name = disk.get("file")
        logging.info("Image to test: {0}".format(file_name))
    except AttributeError as error:
        logging.critical("Error: {0}".format(error))
        fields["test_result"] = "ENV_PARAMETERS_ERRORS"
        propagate_results(image=file_name, fields=fields)
        return 0

    if not image_supported(file_name):
        fields["test_result"] = "IMAGE_NOT_SUPPORTED"
        propagate_results(image=file_name, fields=fields)
        return 0

    image = fetch_image(fields["url"])
    if image is "":
        fields["test_result"] = "IMAGE_NOT_FOUND"
        propagate_results(image=file_name, fields=fields)
        return 0

    fields["test_result"] = test_image(image)
    propagate_results(image=file_name, fields=fields)
    return 0




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