#!/usr/bin/python2

# Copyright (C) 2014 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/>.

#
# This is a test output compiler which produces TAP from GTest output
# if GTest output is detected.
#
# Versions of glib later than 2.38.x output TAP natively when tests are
# run with the --tap option. However we can't depend on such a recent
# version of glib for our purposes.
#
# This implements the Test Anything Protocol (ie: TAP)
# https://metacpan.org/pod/release/PETDANCE/Test-Harness-2.64/lib/Test/Harness/TAP.pod
#

import argparse
import os
import select
import signal
import subprocess
import sys

# Yes, it's dumb, but strsignal is not exposed in python
# In addition signal numbers varify heavily from arch to arch
def strsignal(sig):
    for name in dir(signal):
        if name.startswith("SIG") and sig == getattr(signal, name):
            return name
    return str(sig)


class NullCompiler:
    def __init__(self, command):
        self.command = command

    def input(self, line):
        sys.stdout.write(line)

    def process(self, proc):
        while True:
            line = proc.stdout.readline()
            if not line:
                break
            self.input(line)
        proc.wait()
        return proc.returncode

    def run(self, proc, line=None):
        if line:
            self.input(line)
        return self.process(proc)


class GTestCompiler(NullCompiler):
    def __init__(self, filename):
        NullCompiler.__init__(self, filename)
        self.test_num = 0
        self.test_name = None
        self.test_remaining = []

    def input(self, line):
        line = line.strip()
        if line.startswith("GTest: "):
           (cmd, unused, data) = line[7:].partition(": ")
           cmd = cmd.strip()
           data = data.strip()
           if cmd == "run":
               self.test_name = data
               assert self.test_name in self.test_remaining, "%s %s" % (self.test_name, repr(self.test_remaining))
               self.test_remaining.remove(self.test_name)
               self.test_num += 1
           elif cmd == "result":
               if self.test_name:
                   if data == "OK":
                       print("ok %d %s" % (self.test_num, self.test_name))
                   if data == "FAIL":
                       print("not ok %d %s", (self.test_num, self.test_name))
               self.test_name = None
           elif cmd == "skipping":
               if "/subprocess" not in data:
                   print("ok %d # skip -- %s" % (self.test_num, data))
               self.test_name = None
           elif data:
               print("# %s: %s" % (cmd, data))
           else:
               print("# %s" % cmd)
        elif line.startswith("(MSG: "):
            print("# %s" % line[6:-1])
        elif line:
            print("# %s" % line)
        sys.stdout.flush()

    def run(self, proc, output=""):
        # Complete retrieval of the list of tests
        output += proc.stdout.read()
        proc.wait()
        if proc.returncode:
            sys.stderr.write("tap-gtester: listing GTest tests failed: %d\n" % proc.returncode)
            return proc.returncode
        self.test_remaining = []
        for line in output.split("\n"):
            if line.startswith("/"):
                self.test_remaining.append(line.strip())
        if not self.test_remaining:
            print("Bail out! No tests found in GTest: %s" % self.command[0])
            return 0

        print("1..%d" % len(self.test_remaining))

        # First try to run all the tests in a batch
        proc = subprocess.Popen(self.command + ["--verbose" ], close_fds=True, stdout=subprocess.PIPE)
        result = self.process(proc)
        if result == 0:
            return 0

        if result < 0:
            sys.stderr.write("%s terminated with %s\n" % (self.command[0], strsignal(-result)))

        # Now pick up any stragglers due to failures
        while True:
            # Assume that the last test failed
            if self.test_name:
                print("not ok %d %s" % (self.test_num, self.test_name))
                self.test_name = None

            # Run any tests which didn't get run
            if not self.test_remaining:
                break

            proc = subprocess.Popen(self.command + ["--verbose", "-p", self.test_remaining[0]],
                                    close_fds=True, stdout=subprocess.PIPE)
            result = self.process(proc)

            # The various exit codes and signals we continue for
            if result not in [ 0, 1, -4, -5, -6, -7, -8, -11, 33 ]:
                break

        return result

def main(argv):
    parser = argparse.ArgumentParser(description='Automake TAP compiler',
                                     usage="tap-gtester [--format FORMAT] command ...")
    parser.add_argument('--format', metavar='FORMAT', choices=[ "auto", "gtest", "tap" ],
                        default="auto", help='The input format to compile')
    parser.add_argument('--verbose', action='store_true',
                        default=True, help='Verbose mode (ignored)')
    parser.add_argument('command', nargs=argparse.REMAINDER, help="A test command to run")
    args = parser.parse_args(argv[1:])

    output = None
    format = args.format
    cmd = args.command
    if not cmd:
        sys.stderr.write("tap-gtester: specify a command to run\n")
        return 2
    if cmd[0] == '--':
        cmd.pop(0)

    proc = None

    os.environ['HARNESS_ACTIVE'] = '1'

    if format in ["auto", "gtest"]:
        list_cmd = cmd + ["-l", "--verbose"]
        proc = subprocess.Popen(list_cmd, close_fds=True, stdout=subprocess.PIPE)
        output = proc.stdout.readline()
        # Smell whether we're dealing with GTest list output from first line
        if "random seed" in output or "GTest" in output or output.startswith("/"):
            format = "gtest"
        else:
            format = "tap"
    else:
        proc = subprocess.Popen(cmd, close_fds=True, stdout=subprocess.PIPE)

    if format == "gtest":
        compiler = GTestCompiler(cmd)
    elif format == "tap":
        compiler = NullCompiler(cmd)
    else:
        assert False, "not reached"

    return compiler.run(proc, output)

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