Merge lp:~lifeless/subunit/subunit2junitxml into lp:~subunit/subunit/trunk
- subunit2junitxml
- Merge into trunk
Proposed by
Robert Collins
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~lifeless/subunit/subunit2junitxml |
Merge into: | lp:~subunit/subunit/trunk |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~lifeless/subunit/subunit2junitxml |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+9534@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'NEWS' |
2 | --- NEWS 2009-07-28 21:44:28 +0000 |
3 | +++ NEWS 2009-08-01 08:01:27 +0000 |
4 | @@ -15,10 +15,17 @@ |
5 | about completion, when such information is available. See the README |
6 | under ``progress`` for more details. |
7 | |
8 | + * ``subunit2gtk`` has been added, a filter that shows a GTK summary of a |
9 | + test stream. |
10 | + |
11 | * ``subunit2pyunit`` has a --progress flag which will cause the bzrlib |
12 | test reporter to be used, which has a textual progress bar. This requires |
13 | a recent bzrlib as a minor bugfix was required in bzrlib to support this. |
14 | |
15 | + * ``subunit2junitxml`` has been added. This filter converts a subunit |
16 | + stream to a single JUnit style XML stream using the pyjunitxml |
17 | + python library. |
18 | + |
19 | BUG FIXES: |
20 | |
21 | API CHANGES: |
22 | |
23 | === modified file 'README' |
24 | --- README 2009-07-28 13:32:10 +0000 |
25 | +++ README 2009-08-01 08:01:27 +0000 |
26 | @@ -47,6 +47,8 @@ |
27 | Subunit supplies the following filters: |
28 | * tap2subunit - convert perl's TestAnythingProtocol to subunit. |
29 | * subunit2pyunit - convert a subunit stream to pyunit test results. |
30 | + * subunit2gtk - show a subunit stream in GTK. |
31 | + * subunit2junitxml - convert a subunit stream to JUnit's XML format. |
32 | * subunit-filter - filter out tests from a subunit stream. |
33 | * subunit-ls - list info about tests present in a subunit stream. |
34 | * subunit-stats - generate a summary of a subunit stream. |
35 | |
36 | === added file 'filters/subunit2gtk' |
37 | --- filters/subunit2gtk 1970-01-01 00:00:00 +0000 |
38 | +++ filters/subunit2gtk 2009-07-29 12:49:03 +0000 |
39 | @@ -0,0 +1,232 @@ |
40 | +#!/usr/bin/env python |
41 | +# subunit: extensions to python unittest to get test results from subprocesses. |
42 | +# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> |
43 | +# |
44 | +# This program is free software; you can redistribute it and/or modify |
45 | +# it under the terms of the GNU General Public License as published by |
46 | +# the Free Software Foundation; either version 2 of the License, or |
47 | +# (at your option) any later version. |
48 | +# |
49 | +# This program is distributed in the hope that it will be useful, |
50 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
51 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
52 | +# GNU General Public License for more details. |
53 | +# |
54 | +# You should have received a copy of the GNU General Public License |
55 | +# along with this program; if not, write to the Free Software |
56 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
57 | +# |
58 | + |
59 | +### The GTK progress bar __init__ function is derived from the pygtk tutorial: |
60 | +# The PyGTK Tutorial is Copyright (C) 2001-2005 John Finlay. |
61 | +# |
62 | +# The GTK Tutorial is Copyright (C) 1997 Ian Main. |
63 | +# |
64 | +# Copyright (C) 1998-1999 Tony Gale. |
65 | +# |
66 | +# Permission is granted to make and distribute verbatim copies of this manual |
67 | +# provided the copyright notice and this permission notice are preserved on all |
68 | +# copies. |
69 | +# |
70 | +# Permission is granted to copy and distribute modified versions of this |
71 | +# document under the conditions for verbatim copying, provided that this |
72 | +# copyright notice is included exactly as in the original, and that the entire |
73 | +# resulting derived work is distributed under the terms of a permission notice |
74 | +# identical to this one. |
75 | +# |
76 | +# Permission is granted to copy and distribute translations of this document |
77 | +# into another language, under the above conditions for modified versions. |
78 | +# |
79 | +# If you are intending to incorporate this document into a published work, |
80 | +# please contact the maintainer, and we will make an effort to ensure that you |
81 | +# have the most up to date information available. |
82 | +# |
83 | +# There is no guarantee that this document lives up to its intended purpose. |
84 | +# This is simply provided as a free resource. As such, the authors and |
85 | +# maintainers of the information provided within can not make any guarantee |
86 | +# that the information is even accurate. |
87 | + |
88 | +"""Display a subunit stream in a gtk progress window.""" |
89 | + |
90 | +import os |
91 | +import sys |
92 | +import unittest |
93 | + |
94 | +import pygtk |
95 | +pygtk.require('2.0') |
96 | +import gtk, gtk.gdk, gobject |
97 | + |
98 | +from subunit import ProtocolTestCase, TestProtocolServer |
99 | + |
100 | +class GTKTestResult(unittest.TestResult): |
101 | + |
102 | + def __init__(self): |
103 | + super(GTKTestResult, self).__init__() |
104 | + # Instance variables (in addition to TestResult) |
105 | + self.window = None |
106 | + self.run_label = None |
107 | + self.ok_label = None |
108 | + self.not_ok_label = None |
109 | + self.total_tests = None |
110 | + |
111 | + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
112 | + self.window.set_resizable(True) |
113 | + |
114 | + self.window.connect("destroy", gtk.main_quit) |
115 | + self.window.set_title("Tests...") |
116 | + self.window.set_border_width(0) |
117 | + |
118 | + vbox = gtk.VBox(False, 5) |
119 | + vbox.set_border_width(10) |
120 | + self.window.add(vbox) |
121 | + vbox.show() |
122 | + |
123 | + # Create a centering alignment object |
124 | + align = gtk.Alignment(0.5, 0.5, 0, 0) |
125 | + vbox.pack_start(align, False, False, 5) |
126 | + align.show() |
127 | + |
128 | + # Create the ProgressBar |
129 | + self.pbar = gtk.ProgressBar() |
130 | + align.add(self.pbar) |
131 | + self.pbar.set_text("Running") |
132 | + self.pbar.show() |
133 | + |
134 | + separator = gtk.HSeparator() |
135 | + vbox.pack_start(separator, False, False, 0) |
136 | + separator.show() |
137 | + |
138 | + # rows, columns, homogeneous |
139 | + table = gtk.Table(2, 3, False) |
140 | + vbox.pack_start(table, False, True, 0) |
141 | + table.show() |
142 | + # Show summary details about the run. Could use an expander. |
143 | + label = gtk.Label("Run:") |
144 | + table.attach(label, 0, 1, 1, 2, gtk.EXPAND | gtk.FILL, |
145 | + gtk.EXPAND | gtk.FILL, 5, 5) |
146 | + label.show() |
147 | + self.run_label = gtk.Label("N/A") |
148 | + table.attach(self.run_label, 1, 2, 1, 2, gtk.EXPAND | gtk.FILL, |
149 | + gtk.EXPAND | gtk.FILL, 5, 5) |
150 | + self.run_label.show() |
151 | + |
152 | + label = gtk.Label("OK:") |
153 | + table.attach(label, 0, 1, 2, 3, gtk.EXPAND | gtk.FILL, |
154 | + gtk.EXPAND | gtk.FILL, 5, 5) |
155 | + label.show() |
156 | + self.ok_label = gtk.Label("N/A") |
157 | + table.attach(self.ok_label, 1, 2, 2, 3, gtk.EXPAND | gtk.FILL, |
158 | + gtk.EXPAND | gtk.FILL, 5, 5) |
159 | + self.ok_label.show() |
160 | + |
161 | + label = gtk.Label("Not OK:") |
162 | + table.attach(label, 0, 1, 3, 4, gtk.EXPAND | gtk.FILL, |
163 | + gtk.EXPAND | gtk.FILL, 5, 5) |
164 | + label.show() |
165 | + self.not_ok_label = gtk.Label("N/A") |
166 | + table.attach(self.not_ok_label, 1, 2, 3, 4, gtk.EXPAND | gtk.FILL, |
167 | + gtk.EXPAND | gtk.FILL, 5, 5) |
168 | + self.not_ok_label.show() |
169 | + |
170 | + self.window.show() |
171 | + # For the demo. |
172 | + self.window.set_keep_above(True) |
173 | + self.window.present() |
174 | + |
175 | + def stopTest(self, test): |
176 | + super(GTKTestResult, self).stopTest(test) |
177 | + if not self.total_tests: |
178 | + self.pbar.pulse() |
179 | + else: |
180 | + self.pbar.set_fraction(self.testsRun/float(self.total_tests)) |
181 | + |
182 | + def stopTestRun(self): |
183 | + try: |
184 | + super(GTKTestResult, self).stopTestRun() |
185 | + except AttributeError: |
186 | + pass |
187 | + self.pbar.set_text('Finished') |
188 | + |
189 | + def addError(self, test, err): |
190 | + super(GTKTestResult, self).addError(test, err) |
191 | + self.update_counts() |
192 | + |
193 | + def addFailure(self, test, err): |
194 | + super(GTKTestResult, self).addFailure(test, err) |
195 | + self.update_counts() |
196 | + |
197 | + def addSuccess(self, test): |
198 | + super(GTKTestResult, self).addSuccess(test) |
199 | + self.update_counts() |
200 | + |
201 | + def addSkip(self, test, reason): |
202 | + try: |
203 | + super(GTKTestResult, self).addSkipSuccess(test, reason) |
204 | + except AttributeError: |
205 | + pass |
206 | + self.update_counts() |
207 | + |
208 | + def addExpectedFailure(self, test, err): |
209 | + super(GTKTestResult, self).addExpectedFailure(test, err) |
210 | + self.update_counts() |
211 | + |
212 | + def addUnexpectedSuccess(self, test): |
213 | + super(GTKTestResult, self).addUnexpectedSuccess(test) |
214 | + self.update_counts() |
215 | + |
216 | + def progress(self, offset, whence): |
217 | + if whence == os.SEEK_SET: |
218 | + self.total_tests = offset |
219 | + else: |
220 | + self.total_tests += offset |
221 | + |
222 | + def time(self, a_datetime): |
223 | + # We don't try to estimate completion yet. |
224 | + pass |
225 | + |
226 | + def update_counts(self): |
227 | + self.run_label.set_text(str(self.testsRun)) |
228 | + bad = len(self.failures + self.errors) |
229 | + self.ok_label.set_text(str(self.testsRun - bad)) |
230 | + self.not_ok_label.set_text(str(bad)) |
231 | + |
232 | + |
233 | +class GIOProtocolTestCase(object): |
234 | + |
235 | + def __init__(self, stream, result, on_finish): |
236 | + self.stream = stream |
237 | + self.schedule_read() |
238 | + self.hup_id = gobject.io_add_watch(stream, gobject.IO_HUP, self.hup) |
239 | + self.protocol = TestProtocolServer(result) |
240 | + self.on_finish = on_finish |
241 | + |
242 | + def read(self, source, condition): |
243 | + #NB: \o/ actually blocks |
244 | + line = source.readline() |
245 | + if not line: |
246 | + self.protocol.lostConnection() |
247 | + self.on_finish() |
248 | + return False |
249 | + self.protocol.lineReceived(line) |
250 | + # schedule more IO shortly - if we say we're willing to do it |
251 | + # immediately we starve things. |
252 | + source_id = gobject.timeout_add(1, self.schedule_read) |
253 | + return False |
254 | + |
255 | + def schedule_read(self): |
256 | + self.read_id = gobject.io_add_watch(self.stream, gobject.IO_IN, self.read) |
257 | + |
258 | + def hup(self, source, condition): |
259 | + self.protocol.lostConnection() |
260 | + gobject.source_remove(self.read_id) |
261 | + self.on_finish() |
262 | + |
263 | + |
264 | +result = GTKTestResult() |
265 | +test = GIOProtocolTestCase(sys.stdin, result, result.stopTestRun) |
266 | +gtk.main() |
267 | +if result.wasSuccessful(): |
268 | + exit_code = 0 |
269 | +else: |
270 | + exit_code = 1 |
271 | +sys.exit(exit_code) |
272 | |
273 | === added file 'filters/subunit2junitxml' |
274 | --- filters/subunit2junitxml 1970-01-01 00:00:00 +0000 |
275 | +++ filters/subunit2junitxml 2009-08-01 08:01:27 +0000 |
276 | @@ -0,0 +1,42 @@ |
277 | +#!/usr/bin/env python |
278 | +# subunit: extensions to python unittest to get test results from subprocesses. |
279 | +# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> |
280 | +# |
281 | +# This program is free software; you can redistribute it and/or modify |
282 | +# it under the terms of the GNU General Public License as published by |
283 | +# the Free Software Foundation; either version 2 of the License, or |
284 | +# (at your option) any later version. |
285 | +# |
286 | +# This program is distributed in the hope that it will be useful, |
287 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
288 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
289 | +# GNU General Public License for more details. |
290 | +# |
291 | +# You should have received a copy of the GNU General Public License |
292 | +# along with this program; if not, write to the Free Software |
293 | +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
294 | +# |
295 | + |
296 | +"""Filter a subunit stream to get aggregate statistics.""" |
297 | + |
298 | +import sys |
299 | +import unittest |
300 | + |
301 | +from subunit import ProtocolTestCase |
302 | +try: |
303 | + from junitxml import JUnitXmlResult |
304 | +except ImportError: |
305 | + sys.stderr.write("python-junitxml (https://launchpad.net/pyjunitxml or " |
306 | + "http://pypi.python.org/pypi/junitxml) is required for this filter.") |
307 | + raise |
308 | + |
309 | +result = JUnitXmlResult(sys.stdout) |
310 | +test = ProtocolTestCase(sys.stdin) |
311 | +result.startTestRun() |
312 | +test.run(result) |
313 | +result.stopTestRun() |
314 | +if result.wasSuccessful(): |
315 | + exit_code = 0 |
316 | +else: |
317 | + exit_code = 1 |
318 | +sys.exit(exit_code) |
junit outout. \o/