Merge lp:~thomir-deactivatedaccount/autopilot/trunk-add-new-formats into lp:autopilot

Proposed by Thomi Richards
Status: Merged
Approved by: Christopher Lee
Approved revision: 383
Merged at revision: 378
Proposed branch: lp:~thomir-deactivatedaccount/autopilot/trunk-add-new-formats
Merge into: lp:autopilot
Diff against target: 406 lines (+173/-97)
5 files modified
autopilot/__init__.py (+6/-13)
autopilot/run.py (+14/-52)
autopilot/testresult.py (+57/-30)
autopilot/tests/unit/test_testresults.py (+92/-0)
debian/control (+4/-2)
To merge this branch: bzr merge lp:~thomir-deactivatedaccount/autopilot/trunk-add-new-formats
Reviewer Review Type Date Requested Status
Christopher Lee (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+197306@code.launchpad.net

Commit message

Make autopilot support subunit bytestream output.

Description of the change

Make autopilot support subunit bytestream output.

To post a comment you must log in.
380. By Thomi Richards

Merged trunk.

381. By Thomi Richards

Fix commented out code and incorrect copyright header date.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
382. By Thomi Richards

Add junitxml as a build-dep.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
383. By Thomi Richards

Fix PEP8.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Christopher Lee (veebers) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'autopilot/__init__.py'
2--- autopilot/__init__.py 2013-11-26 22:54:36 +0000
3+++ autopilot/__init__.py 2013-12-02 02:12:42 +0000
4@@ -20,20 +20,12 @@
5 from argparse import ArgumentParser, REMAINDER, Action
6 import subprocess
7
8+from autopilot.testresult import get_output_formats, get_default_format
9+from autopilot.exceptions import BackendException
10+
11 version = '1.4.0'
12
13
14-class BackendException(RuntimeError):
15-
16- """An error occured while trying to initialise an autopilot backend."""
17-
18- def __init__(self, original_exception):
19- super(BackendException, self).__init__(
20- "Error while initialising backend. Original exception was: " +
21- str(original_exception))
22- self.original_exception = original_exception
23-
24-
25 def parse_arguments(argv=None):
26 """Parse command-line arguments, and return an argparse arguments
27 object.
28@@ -57,8 +49,9 @@
29 If given a directory instead of a file will \
30 write to a file in that directory named: \
31 <hostname>_<dd.mm.yyy_HHMMSS>.log')
32- parser_run.add_argument('-f', "--format", choices=['text', 'xml'],
33- default='text',
34+ available_formats = get_output_formats().keys()
35+ parser_run.add_argument('-f', "--format", choices=available_formats,
36+ default=get_default_format(),
37 required=False,
38 help='Specify desired output format. \
39 Default is "text".')
40
41=== modified file 'autopilot/run.py'
42--- autopilot/run.py 2013-11-28 04:03:49 +0000
43+++ autopilot/run.py 2013-12-02 02:12:42 +0000
44@@ -41,11 +41,10 @@
45 from unittest import TestLoader, TestSuite
46
47 from testtools import iterate_tests
48-from testtools import TextTestResult
49
50 from autopilot import get_version_string, parse_arguments
51 import autopilot.globals
52-from autopilot.testresult import AutopilotVerboseResult
53+from autopilot.testresult import get_output_formats
54 from autopilot.utilities import DebugLogFilter, LogFormatter
55
56
57@@ -69,29 +68,13 @@
58 root_logger.info(get_version_string())
59
60
61-def construct_test_runner(args):
62- kwargs = dict(
63- stdout=get_output_stream(args),
64- output_format=get_output_format(args.format),
65+def construct_test_result(args):
66+ formats = get_output_formats()
67+ return formats[args.format](
68+ stream=get_output_stream(args),
69+ failfast=args.failfast,
70 )
71
72- return ConfigurableTestRunner(**kwargs)
73-
74-
75-def get_output_format(format):
76- """Return a Result object for each format we support."""
77-
78- if format == "text":
79- return type('VerboseTextTestResult', (TextTestResult,),
80- dict(AutopilotVerboseResult.__dict__))
81-
82- elif format == "xml":
83- from junitxml import JUnitXmlResult
84- return type('VerboseXmlResult', (JUnitXmlResult,),
85- dict(AutopilotVerboseResult.__dict__))
86-
87- raise KeyError("Unknown format name '%s'" % format)
88-
89
90 def get_output_stream(args):
91 global _output_stream
92@@ -118,30 +101,6 @@
93 return _output_stream
94
95
96-class ConfigurableTestRunner(object):
97- """A configurable test runner class.
98-
99- This class alows us to configure the output format and whether of not we
100- collect coverage information for the test run.
101-
102- """
103-
104- def __init__(self, stdout, output_format):
105- self.stdout = stdout
106- self.result_class = output_format
107-
108- def run(self, test, failfast=False):
109- "Run the given test case or test suite."
110- result = self.result_class(self.stdout)
111- result.startTestRun()
112- result.failfast = failfast
113- try:
114- test_result = test.run(result)
115- finally:
116- result.stopTestRun()
117- return test_result
118-
119-
120 def get_package_location(import_name):
121 """Get the on-disk location of a package from a test id name.
122
123@@ -218,6 +177,7 @@
124 self.args = parse_arguments()
125
126 def run(self):
127+ setup_logging(self.args.verbose)
128 if self.args.mode == 'list':
129 self.list_tests()
130 elif self.args.mode == 'run':
131@@ -228,7 +188,6 @@
132 self.launch_app()
133
134 def run_vis(self):
135- setup_logging(self.args.verbose)
136 # importing this requires that DISPLAY is set. Since we don't always
137 # want that requirement, do the import here:
138 from autopilot.vis import vis_main
139@@ -251,7 +210,6 @@
140 get_application_launcher_from_string_hint,
141 )
142
143- setup_logging(self.args.verbose)
144 app_name = self.args.application[0]
145 if not os.path.isabs(app_name) or not os.path.exists(app_name):
146 try:
147@@ -325,9 +283,13 @@
148 if self.args.verbose:
149 autopilot.globals.set_log_verbose(True)
150
151- setup_logging(self.args.verbose)
152- runner = construct_test_runner(self.args)
153- test_result = runner.run(test_suite, self.args.failfast)
154+ result = construct_test_result(self.args)
155+ result.startTestRun()
156+ try:
157+ test_result = test_suite.run(result)
158+ finally:
159+ result.stopTestRun()
160+
161 if not test_result.wasSuccessful():
162 exit(1)
163
164
165=== modified file 'autopilot/testresult.py'
166--- autopilot/testresult.py 2013-09-16 17:26:27 +0000
167+++ autopilot/testresult.py 2013-12-02 02:12:42 +0000
168@@ -25,11 +25,17 @@
169 import logging
170
171 from autopilot.globals import get_log_verbose
172-
173-
174-class AutopilotVerboseResult(object):
175- """A result class that logs failures, errors and success via the python
176- logging framework."""
177+from testtools import (
178+ ExtendedToOriginalDecorator,
179+ ExtendedToStreamDecorator,
180+ TestResultDecorator,
181+ TextTestResult,
182+ try_import,
183+)
184+
185+
186+class LoggedTestResultDecorator(TestResultDecorator):
187+ """A decorator that logs messages to python's logging system."""
188
189 def _log(self, level, message):
190 """Performs the actual message logging"""
191@@ -46,40 +52,61 @@
192 self._log(level, text)
193
194 def addSuccess(self, test, details=None):
195- """Called for a successful test"""
196- # Allow for different calling syntax used by the base class.
197- if details is None:
198- super(type(self), self).addSuccess(test)
199- else:
200- super(type(self), self).addSuccess(test, details)
201 self._log(logging.INFO, "OK: %s" % (test.id()))
202+ return super(LoggedTestResultDecorator, self).addSuccess(test, details)
203
204 def addError(self, test, err=None, details=None):
205- """Called for a test which failed with an error"""
206- # Allow for different calling syntax used by the base class.
207- # The xml path only uses 'err'. Use of 'err' can be
208- # forced by raising TypeError when it is not specified.
209- if err is None:
210- raise TypeError
211- if details is None:
212- super(type(self), self).addError(test, err)
213- else:
214- super(type(self), self).addError(test, err, details)
215 self._log(logging.ERROR, "ERROR: %s" % (test.id()))
216 if hasattr(test, "getDetails"):
217 self._log_details(logging.ERROR, test.getDetails())
218+ return super(type(self), self).addError(test, err, details)
219
220 def addFailure(self, test, err=None, details=None):
221 """Called for a test which failed an assert"""
222- # Allow for different calling syntax used by the base class.
223- # The xml path only uses 'err' or 'details'. Use of 'err' can be
224- # forced by raising TypeError when it is not specified.
225- if err is None:
226- raise TypeError
227- if details is None:
228- super(type(self), self).addFailure(test, err)
229- else:
230- super(type(self), self).addFailure(test, err, details)
231 self._log(logging.ERROR, "FAIL: %s" % (test.id()))
232 if hasattr(test, "getDetails"):
233 self._log_details(logging.ERROR, test.getDetails())
234+ return super(type(self), self).addFailure(test, err, details)
235+
236+
237+def get_output_formats():
238+ """Get information regarding the different output formats supported."""
239+ supported_formats = {}
240+
241+ supported_formats['text'] = _construct_text
242+
243+ if try_import('junitxml'):
244+ supported_formats['xml'] = _construct_xml
245+ if try_import('subunit'):
246+ supported_formats['subunit'] = _construct_subunit
247+ return supported_formats
248+
249+
250+def get_default_format():
251+ return 'text'
252+
253+
254+def _construct_xml(**kwargs):
255+ from junitxml import JUnitXmlResult
256+ stream = kwargs.pop('stream')
257+ return LoggedTestResultDecorator(
258+ ExtendedToOriginalDecorator(
259+ JUnitXmlResult(stream)
260+ )
261+ )
262+
263+
264+def _construct_text(**kwargs):
265+ stream = kwargs.pop('stream')
266+ failfast = kwargs.pop('failfast')
267+ return LoggedTestResultDecorator(TextTestResult(stream, failfast))
268+
269+
270+def _construct_subunit(**kwargs):
271+ from subunit import StreamResultToBytes
272+ stream = kwargs.pop('stream')
273+ return LoggedTestResultDecorator(
274+ ExtendedToStreamDecorator(
275+ StreamResultToBytes(stream)
276+ )
277+ )
278
279=== added file 'autopilot/tests/unit/test_testresults.py'
280--- autopilot/tests/unit/test_testresults.py 1970-01-01 00:00:00 +0000
281+++ autopilot/tests/unit/test_testresults.py 2013-12-02 02:12:42 +0000
282@@ -0,0 +1,92 @@
283+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
284+#
285+# Autopilot Functional Test Tool
286+# Copyright (C) 2013 Canonical
287+#
288+# This program is free software: you can redistribute it and/or modify
289+# it under the terms of the GNU General Public License as published by
290+# the Free Software Foundation, either version 3 of the License, or
291+# (at your option) any later version.
292+#
293+# This program is distributed in the hope that it will be useful,
294+# but WITHOUT ANY WARRANTY; without even the implied warranty of
295+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
296+# GNU General Public License for more details.
297+#
298+# You should have received a copy of the GNU General Public License
299+# along with this program. If not, see <http://www.gnu.org/licenses/>.
300+#
301+
302+from mock import Mock, patch
303+from testtools import TestCase, PlaceHolder
304+from testtools.content import text_content
305+
306+from autopilot.testresult import (
307+ get_default_format,
308+ get_output_formats,
309+ LoggedTestResultDecorator,
310+)
311+
312+
313+class LoggedTestResultDecoratorTests(TestCase):
314+
315+ def construct_simple_content_object(self):
316+ return text_content(self.getUniqueString)
317+
318+ def test_can_construct(self):
319+ LoggedTestResultDecorator(Mock())
320+
321+ def test_addSuccess_calls_decorated_test(self):
322+ wrapped = Mock()
323+ result = LoggedTestResultDecorator(wrapped)
324+ fake_test = PlaceHolder('fake_test')
325+ fake_details = self.construct_simple_content_object()
326+
327+ result.addSuccess(fake_test, fake_details)
328+
329+ wrapped.addSuccess.assert_called_once_with(
330+ fake_test,
331+ details=fake_details
332+ )
333+
334+ def test_addError_calls_decorated_test(self):
335+ wrapped = Mock()
336+ result = LoggedTestResultDecorator(wrapped)
337+ fake_test = PlaceHolder('fake_test')
338+ fake_error = object()
339+ fake_details = self.construct_simple_content_object()
340+
341+ result.addError(fake_test, fake_error, fake_details)
342+
343+ wrapped.addError.assert_called_once_with(
344+ fake_test,
345+ fake_error,
346+ details=fake_details
347+ )
348+
349+ def test_addFailure_calls_decorated_test(self):
350+ wrapped = Mock()
351+ result = LoggedTestResultDecorator(wrapped)
352+ fake_test = PlaceHolder('fake_test')
353+ fake_error = object()
354+ fake_details = self.construct_simple_content_object()
355+
356+ result.addFailure(fake_test, fake_error, fake_details)
357+
358+ wrapped.addFailure.assert_called_once_with(
359+ fake_test,
360+ fake_error,
361+ details=fake_details
362+ )
363+
364+
365+class OutputFormatFactoryTests(TestCase):
366+
367+ def test_has_text_format(self):
368+ self.assertTrue('text' in get_output_formats())
369+
370+ def test_has_xml_format(self):
371+ self.assertTrue('xml' in get_output_formats())
372+
373+ def test_default_format_is_available(self):
374+ self.assertTrue(get_default_format() in get_output_formats())
375
376=== modified file 'debian/control'
377--- debian/control 2013-11-27 20:15:34 +0000
378+++ debian/control 2013-12-02 02:12:42 +0000
379@@ -16,23 +16,25 @@
380 python-debian,
381 python-dev,
382 python-gi,
383+ python-junitxml,
384 python-mock,
385 python-psutil,
386 python-setuptools,
387- python-subunit,
388 python-six,
389 python-sphinx,
390+ python-subunit,
391 python-testscenarios,
392 python-testtools,
393 python-xlib,
394 python3-all-dev (>= 3.3),
395 python3-dbus,
396 python3-gi,
397+ python3-junitxml,
398 python3-mock,
399 python3-psutil,
400 python3-setuptools,
401+ python3-six,
402 python3-subunit,
403- python3-six,
404 python3-testscenarios,
405 python3-testtools,
406 python3-xlib,

Subscribers

People subscribed via source and target branches