test-webkitpy: prepare for better test run output
[WebKit-https.git] / Tools / Scripts / webkitpy / test / runner.py
1 # Copyright (C) 2012 Google, Inc.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions
5 # are met:
6 # 1.  Redistributions of source code must retain the above copyright
7 #     notice, this list of conditions and the following disclaimer.
8 # 2.  Redistributions in binary form must reproduce the above copyright
9 #     notice, this list of conditions and the following disclaimer in the
10 #     documentation and/or other materials provided with the distribution.
11 #
12 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
13 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
16 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
20 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
21 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
23 """code to actually run a list of python tests."""
24
25 import logging
26 import re
27 import time
28 import unittest
29
30
31 _log = logging.getLogger(__name__)
32
33
34 class TestRunner(object):
35     def __init__(self, stream, options, loader):
36         self.options = options
37         self.stream = stream
38         self.loader = loader
39         self.test_description = re.compile("(\w+) \(([\w.]+)\)")
40
41     def test_name(self, test):
42         m = self.test_description.match(str(test))
43         return "%s.%s" % (m.group(2), m.group(1))
44
45     def all_test_names(self, suite):
46         names = []
47         if hasattr(suite, '_tests'):
48             for t in suite._tests:
49                 names.extend(self.all_test_names(t))
50         else:
51             names.append(self.test_name(suite))
52         return names
53
54     def run(self, suite):
55         run_start_time = time.time()
56         all_test_names = self.all_test_names(suite)
57         result = unittest.TestResult()
58         stop = run_start_time
59         for test_name in all_test_names:
60             if self.options.verbose:
61                 self.stream.write(test_name)
62             num_failures = len(result.failures)
63             num_errors = len(result.errors)
64
65             start = time.time()
66             # FIXME: it's kinda lame that we re-load the test suites for each
67             # test, and this may slow things down, but this makes implementing
68             # the logging easy and will also allow us to parallelize nicely.
69             self.loader.loadTestsFromName(test_name, None).run(result)
70             stop = time.time()
71
72             err = None
73             failure = None
74             if len(result.failures) > num_failures:
75                 failure = result.failures[num_failures][1]
76             elif len(result.errors) > num_errors:
77                 err = result.errors[num_errors][1]
78             self.write_result(result, test_name, stop - start, failure, err)
79
80         self.write_summary(result, stop - run_start_time)
81
82         return result
83
84     def write_result(self, result, test_name, test_time, failure=None, err=None):
85         if self.options.verbose:
86             if failure:
87                 msg = ' failed'
88             elif err:
89                 msg = ' erred'
90             else:
91                 msg = ' passed'
92             self.stream.write(msg + '\n')
93         else:
94             if failure:
95                 msg = 'F'
96             elif err:
97                 msg = 'E'
98             else:
99                 msg = '.'
100             self.stream.write(msg)
101
102     def write_summary(self, result, run_time):
103         self.stream.write('\n')
104
105         for (test, err) in result.errors:
106             self.stream.write("=" * 80 + '\n')
107             self.stream.write("ERROR: " + self.test_name(test) + '\n')
108             self.stream.write("-" * 80 + '\n')
109             for line in err.splitlines():
110                 self.stream.write(line + '\n')
111             self.stream.write('\n')
112
113         for (test, failure) in result.failures:
114             self.stream.write("=" * 80 + '\n')
115             self.stream.write("FAILURE: " + self.test_name(test) + '\n')
116             self.stream.write("-" * 80 + '\n')
117             for line in failure.splitlines():
118                 self.stream.write(line + '\n')
119             self.stream.write('\n')
120
121         self.stream.write('-' * 80 + '\n')
122         self.stream.write('Ran %d test%s in %.3fs\n' %
123             (result.testsRun, result.testsRun != 1 and "s" or "", run_time))
124
125         if result.wasSuccessful():
126             self.stream.write('\nOK\n')
127         else:
128             self.stream.write('FAILED (failures=%d, errors=%d)\n' %
129                 (len(result.failures), len(result.errors)))