summaryrefslogtreecommitdiff
path: root/build/tap-gtester
blob: e075281d8e4b0ea92177df9608767501eadca2b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#!/usr/bin/env python

# 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 subprocess
import sys

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

        # 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')
    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='+', help="A test command to run")
    args = parser.parse_args(argv[1:])

    output = None
    format = args.format
    cmd = args.command
    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))