#!/usr/bin/python
#
# 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.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Authors: Nicolas Zingile <nicolas.zingile@open.eurogiciel.org>

import argparse
import io
import os
import shutil
import subprocess
import sys
from lxml import etree

#---- Global variables ----#
XSDFILE = '/usr/share/testkit-lite/xsd/test_definition.xsd'
GLOBALSUITEPATH = '/usr/share/tests/'
KNOWNPROFILES = ['ivi', 'common']
RESXMLDIR = 'result'
RESXMLNAME ='result.xml'

#---- Helper functions of the launch_suites function ----#
def check_runtest(suitedir):
    """Check if a test suite contains a valid runtest script.

    Check if suitedir contains a runtest script and
    if this script is executable.

    Args:
	suitedir: Directory of the suite to check

    Returns:
	A boolean
	True if a runtest script is present and executable
	False if no runtest script or runtest script is not executable
    """
    result = False
    runtestfile = os.path.join(suitedir, 'runtest')
    if os.path.isfile(runtestfile) and os.access(runtestfile, os.X_OK):
	result = True

    return result

def check_xmlfile(suitedir, testxml):
    """Check if a testkit xml file is well formed and valid.

    Check if the syntax of testxml is in accordance with
    the xml format. Then, check if testxml is in accordance
    with the testkit lite schema definition.
    Exits the program if testxml is not well formed.

    Args:
	suitedir: Directory of the suite
	testxml: Testkit xml file to check

    Returns:
	A boolean if the document is well formed.
	True if testxml is valid
	False if testxml is not valid
    """
    xmlschema_doc = etree.parse(XSDFILE)
    xmlschema = etree.XMLSchema(xmlschema_doc)

    try:
	xmlfile = etree.parse(os.path.join(suitedir,testxml))
    except etree.XMLSyntaxError as e:
	print '-- xml file ' + testxml + ' of ' + os.path.basename(suitedir) + ' is not well-formed !'
	print 'Error : ' + str(e)
	sys.exit(1)

    valid = xmlschema.validate(xmlfile)

    return(valid, str(xmlschema.error_log))

def get_xmlfiles(folder):
    """Retrieves all .xml files of a directory.

    Get all the .xml files of folder. Sub folders
    are not checked.

    Args:
	folder: directory to borowse

    Returns:
	A list of the xml files in folder
    """
    xmlfiles = []
    for afile in os.listdir(folder):
	if afile.endswith('.xml'):
	   xmlfiles.append(afile)

    return xmlfiles

def print_output(process):
    """Print the output of a process in stdout.

    Args:
	process: process for which to print the stdout

    Returns:
    """
    while True:
	nextline = process.stdout.readline()
	if nextline == '' and process.poll() != None:
	    break
	sys.stdout.write(nextline)
	sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
	return output
    else:
	exit(1)

def format_resultfile(xmlfile, outdir):
    """Transforms the testkit xml result file in a txt file.

    The transformation is performed with the result-format tool.

    Args:
	xmlfile: xml file to transform.
	outdir: output directory of the future txt result file.

    Returns:
    """
    format_cmd = ['result-format', '-f', xmlfile, '-o', outdir]
    subprocess.call(format_cmd)

def merge_resultfile(xmlfile, outdir, name):
    """Merges testkit-lite xml result files.

    The merging is performed with the testkit-merge tool.

    Args:
	xmlfile: testkit-lit xml result files to merge.
	outdir: Output directory.
	name: Name of the xml result file that will be generated.

    Returns:
    """
    resxmlpath = os.path.join(outdir, name)
    if os.path.isfile(resxmlpath):
	merge_cmd = ['testkit-merge', '-f', xmlfile, resxmlpath, '-o', outdir, '-n', name]
    else:
	merge_cmd = ['testkit-merge', '-f', xmlfile, '-o', outdir, '-n', name]
    subprocess.call(merge_cmd)

#---- subcommand function ----#

def list_suites(args):
    profiles = []

    print '#---------- Available test suites ----------#'
    if args.profile == 'all':
	try:
	    for profile in os.listdir(GLOBALSUITEPATH):
		if not profile.startswith('.') and isinstance(KNOWNPROFILES.index(profile), int):
		    profiles.append(profile)
	except OSError:
	    print '-'
	except ValueError:
	    print 'Error : \'' + profile + '\' is not a supported Tizen profile !'
    else:
	profiles.append(args.profile)

    for profile in profiles:
	print '\n##-- List of ' + profile + ' test suites'
	suitepath = os.path.join(GLOBALSUITEPATH, profile)
	try:
	    suitelist = os.listdir(suitepath)
	    if not suitelist:
		print '-'
	    else:
		for suite in os.listdir(suitepath):
		    print suite
	except OSError:
	    print '-'

def launch_suites(args):
    mergedir = os.path.join(args.outdir, RESXMLDIR)
    resxmlpath = os.path.join(mergedir, RESXMLNAME)
    print '##-- Checking integrity of the test suites'
    if len(args.suites) != len(set(args.suites)):
	print '-- List of given test suites is invalid !'
	print 'Error : the list of the test suites to launch should not contains duplicates !'
	exit (1)
    ## remove and re-create final result directory
    if os.path.isdir(mergedir):
	shutil.rmtree(mergedir)
    os.makedirs(mergedir, 0755)
    for suite in args.suites:
	profile = suite.split('-', 2)[0]
	suitepath = os.path.join(GLOBALSUITEPATH, profile, suite)
	print suite
	if not os.path.isdir(suitepath):
	    print '-- The test suite is invalid !'
	    print 'Error : the test suite \''+ os.path.basename(suitepath) + '\' doesn\'t exist.'
	    exit(1)
	if check_runtest(suitepath):
	    print '-- runtest script of ' + suite + ' is present and executable. Ok'
	    testkitxmlfiles = get_xmlfiles(suitepath)
	    if not testkitxmlfiles:
		print '-- No xml file found !'
		print 'Error : there is no testkit xml file in the suite directory.'
	    for axmlfile in testkitxmlfiles:
	        result = check_xmlfile(suitepath, axmlfile)
		if result[0]:
		    print '-- xml file ' + axmlfile + ' of ' + suite + ' is valid. Ok'
		else:
		    print '-- xml file ' + axmlfile + ' of ' + suite + ' is invalid !'
		    print 'Error : ' + result[1]
		    exit(1)
	else:
	    print '-- runtest script of ' + suite + ' is corrupted !'
	    print 'Error : \'runtest\' script is not present and/or is not executable'
	    sys.exit(1)

	print '\n##-- Executing the suite : ' + suite + "\n"
	finaloutdir = os.path.join(args.outdir, suite)
	if not os.path.isdir(finaloutdir):
	    os.makedirs(finaloutdir, 0755)
	else:
	    shutil.rmtree(finaloutdir)

	process = subprocess.Popen([os.path.join(suitepath, 'runtest'), finaloutdir], cwd=suitepath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
	print_output(process)
	suiteresxmlfiles = get_xmlfiles(finaloutdir)
	for asuiteresxmlfile in suiteresxmlfiles:
	    suiteresxmlpath = os.path.join(finaloutdir, asuiteresxmlfile)
	    merge_resultfile(suiteresxmlpath, mergedir, RESXMLNAME)
	    format_resultfile(suiteresxmlpath, finaloutdir)

def main ():

    parser = argparse.ArgumentParser(description='Tool to manage Tizen test suites')
    subparser = parser.add_subparsers(dest='subcommand')
    sp_list = subparser.add_parser('list', help='list available test suites')
    sp_list.add_argument('--profile', help='list the available test suites of Tizen PROFILE', choices=['common', 'ivi', 'all'], default='all')
    sp_launch = subparser.add_parser('launch', help='launch a test suites')
    sp_launch.add_argument('--suites', help='test suite(s) to launch', required=True, nargs='+')
    sp_launch.add_argument('--outdir', help='output directory for the results', default='/var/log/qa')

    args = parser.parse_args()

    if args.subcommand == 'list':
	list_suites(args)
    elif args.subcommand == 'launch':
	launch_suites(args)
	resxmlpath = os.path.join(args.outdir, RESXMLDIR, RESXMLNAME)
	if os.path.isfile(resxmlpath):
	    format_resultfile(resxmlpath, os.path.dirname(resxmlpath))
    else:
	parser.print_usage()

if __name__ == "__main__":
    main()
