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
=== modified file 'autopilot/__init__.py'
--- autopilot/__init__.py 2013-11-26 22:54:36 +0000
+++ autopilot/__init__.py 2013-12-02 02:12:42 +0000
@@ -20,20 +20,12 @@
20from argparse import ArgumentParser, REMAINDER, Action20from argparse import ArgumentParser, REMAINDER, Action
21import subprocess21import subprocess
2222
23from autopilot.testresult import get_output_formats, get_default_format
24from autopilot.exceptions import BackendException
25
23version = '1.4.0'26version = '1.4.0'
2427
2528
26class BackendException(RuntimeError):
27
28 """An error occured while trying to initialise an autopilot backend."""
29
30 def __init__(self, original_exception):
31 super(BackendException, self).__init__(
32 "Error while initialising backend. Original exception was: " +
33 str(original_exception))
34 self.original_exception = original_exception
35
36
37def parse_arguments(argv=None):29def parse_arguments(argv=None):
38 """Parse command-line arguments, and return an argparse arguments30 """Parse command-line arguments, and return an argparse arguments
39 object.31 object.
@@ -57,8 +49,9 @@
57 If given a directory instead of a file will \49 If given a directory instead of a file will \
58 write to a file in that directory named: \50 write to a file in that directory named: \
59 <hostname>_<dd.mm.yyy_HHMMSS>.log')51 <hostname>_<dd.mm.yyy_HHMMSS>.log')
60 parser_run.add_argument('-f', "--format", choices=['text', 'xml'],52 available_formats = get_output_formats().keys()
61 default='text',53 parser_run.add_argument('-f', "--format", choices=available_formats,
54 default=get_default_format(),
62 required=False,55 required=False,
63 help='Specify desired output format. \56 help='Specify desired output format. \
64 Default is "text".')57 Default is "text".')
6558
=== modified file 'autopilot/run.py'
--- autopilot/run.py 2013-11-28 04:03:49 +0000
+++ autopilot/run.py 2013-12-02 02:12:42 +0000
@@ -41,11 +41,10 @@
41from unittest import TestLoader, TestSuite41from unittest import TestLoader, TestSuite
4242
43from testtools import iterate_tests43from testtools import iterate_tests
44from testtools import TextTestResult
4544
46from autopilot import get_version_string, parse_arguments45from autopilot import get_version_string, parse_arguments
47import autopilot.globals46import autopilot.globals
48from autopilot.testresult import AutopilotVerboseResult47from autopilot.testresult import get_output_formats
49from autopilot.utilities import DebugLogFilter, LogFormatter48from autopilot.utilities import DebugLogFilter, LogFormatter
5049
5150
@@ -69,29 +68,13 @@
69 root_logger.info(get_version_string())68 root_logger.info(get_version_string())
7069
7170
72def construct_test_runner(args):71def construct_test_result(args):
73 kwargs = dict(72 formats = get_output_formats()
74 stdout=get_output_stream(args),73 return formats[args.format](
75 output_format=get_output_format(args.format),74 stream=get_output_stream(args),
75 failfast=args.failfast,
76 )76 )
7777
78 return ConfigurableTestRunner(**kwargs)
79
80
81def get_output_format(format):
82 """Return a Result object for each format we support."""
83
84 if format == "text":
85 return type('VerboseTextTestResult', (TextTestResult,),
86 dict(AutopilotVerboseResult.__dict__))
87
88 elif format == "xml":
89 from junitxml import JUnitXmlResult
90 return type('VerboseXmlResult', (JUnitXmlResult,),
91 dict(AutopilotVerboseResult.__dict__))
92
93 raise KeyError("Unknown format name '%s'" % format)
94
9578
96def get_output_stream(args):79def get_output_stream(args):
97 global _output_stream80 global _output_stream
@@ -118,30 +101,6 @@
118 return _output_stream101 return _output_stream
119102
120103
121class ConfigurableTestRunner(object):
122 """A configurable test runner class.
123
124 This class alows us to configure the output format and whether of not we
125 collect coverage information for the test run.
126
127 """
128
129 def __init__(self, stdout, output_format):
130 self.stdout = stdout
131 self.result_class = output_format
132
133 def run(self, test, failfast=False):
134 "Run the given test case or test suite."
135 result = self.result_class(self.stdout)
136 result.startTestRun()
137 result.failfast = failfast
138 try:
139 test_result = test.run(result)
140 finally:
141 result.stopTestRun()
142 return test_result
143
144
145def get_package_location(import_name):104def get_package_location(import_name):
146 """Get the on-disk location of a package from a test id name.105 """Get the on-disk location of a package from a test id name.
147106
@@ -218,6 +177,7 @@
218 self.args = parse_arguments()177 self.args = parse_arguments()
219178
220 def run(self):179 def run(self):
180 setup_logging(self.args.verbose)
221 if self.args.mode == 'list':181 if self.args.mode == 'list':
222 self.list_tests()182 self.list_tests()
223 elif self.args.mode == 'run':183 elif self.args.mode == 'run':
@@ -228,7 +188,6 @@
228 self.launch_app()188 self.launch_app()
229189
230 def run_vis(self):190 def run_vis(self):
231 setup_logging(self.args.verbose)
232 # importing this requires that DISPLAY is set. Since we don't always191 # importing this requires that DISPLAY is set. Since we don't always
233 # want that requirement, do the import here:192 # want that requirement, do the import here:
234 from autopilot.vis import vis_main193 from autopilot.vis import vis_main
@@ -251,7 +210,6 @@
251 get_application_launcher_from_string_hint,210 get_application_launcher_from_string_hint,
252 )211 )
253212
254 setup_logging(self.args.verbose)
255 app_name = self.args.application[0]213 app_name = self.args.application[0]
256 if not os.path.isabs(app_name) or not os.path.exists(app_name):214 if not os.path.isabs(app_name) or not os.path.exists(app_name):
257 try:215 try:
@@ -325,9 +283,13 @@
325 if self.args.verbose:283 if self.args.verbose:
326 autopilot.globals.set_log_verbose(True)284 autopilot.globals.set_log_verbose(True)
327285
328 setup_logging(self.args.verbose)286 result = construct_test_result(self.args)
329 runner = construct_test_runner(self.args)287 result.startTestRun()
330 test_result = runner.run(test_suite, self.args.failfast)288 try:
289 test_result = test_suite.run(result)
290 finally:
291 result.stopTestRun()
292
331 if not test_result.wasSuccessful():293 if not test_result.wasSuccessful():
332 exit(1)294 exit(1)
333295
334296
=== modified file 'autopilot/testresult.py'
--- autopilot/testresult.py 2013-09-16 17:26:27 +0000
+++ autopilot/testresult.py 2013-12-02 02:12:42 +0000
@@ -25,11 +25,17 @@
25import logging25import logging
2626
27from autopilot.globals import get_log_verbose27from autopilot.globals import get_log_verbose
2828from testtools import (
2929 ExtendedToOriginalDecorator,
30class AutopilotVerboseResult(object):30 ExtendedToStreamDecorator,
31 """A result class that logs failures, errors and success via the python31 TestResultDecorator,
32 logging framework."""32 TextTestResult,
33 try_import,
34)
35
36
37class LoggedTestResultDecorator(TestResultDecorator):
38 """A decorator that logs messages to python's logging system."""
3339
34 def _log(self, level, message):40 def _log(self, level, message):
35 """Performs the actual message logging"""41 """Performs the actual message logging"""
@@ -46,40 +52,61 @@
46 self._log(level, text)52 self._log(level, text)
4753
48 def addSuccess(self, test, details=None):54 def addSuccess(self, test, details=None):
49 """Called for a successful test"""
50 # Allow for different calling syntax used by the base class.
51 if details is None:
52 super(type(self), self).addSuccess(test)
53 else:
54 super(type(self), self).addSuccess(test, details)
55 self._log(logging.INFO, "OK: %s" % (test.id()))55 self._log(logging.INFO, "OK: %s" % (test.id()))
56 return super(LoggedTestResultDecorator, self).addSuccess(test, details)
5657
57 def addError(self, test, err=None, details=None):58 def addError(self, test, err=None, details=None):
58 """Called for a test which failed with an error"""
59 # Allow for different calling syntax used by the base class.
60 # The xml path only uses 'err'. Use of 'err' can be
61 # forced by raising TypeError when it is not specified.
62 if err is None:
63 raise TypeError
64 if details is None:
65 super(type(self), self).addError(test, err)
66 else:
67 super(type(self), self).addError(test, err, details)
68 self._log(logging.ERROR, "ERROR: %s" % (test.id()))59 self._log(logging.ERROR, "ERROR: %s" % (test.id()))
69 if hasattr(test, "getDetails"):60 if hasattr(test, "getDetails"):
70 self._log_details(logging.ERROR, test.getDetails())61 self._log_details(logging.ERROR, test.getDetails())
62 return super(type(self), self).addError(test, err, details)
7163
72 def addFailure(self, test, err=None, details=None):64 def addFailure(self, test, err=None, details=None):
73 """Called for a test which failed an assert"""65 """Called for a test which failed an assert"""
74 # Allow for different calling syntax used by the base class.
75 # The xml path only uses 'err' or 'details'. Use of 'err' can be
76 # forced by raising TypeError when it is not specified.
77 if err is None:
78 raise TypeError
79 if details is None:
80 super(type(self), self).addFailure(test, err)
81 else:
82 super(type(self), self).addFailure(test, err, details)
83 self._log(logging.ERROR, "FAIL: %s" % (test.id()))66 self._log(logging.ERROR, "FAIL: %s" % (test.id()))
84 if hasattr(test, "getDetails"):67 if hasattr(test, "getDetails"):
85 self._log_details(logging.ERROR, test.getDetails())68 self._log_details(logging.ERROR, test.getDetails())
69 return super(type(self), self).addFailure(test, err, details)
70
71
72def get_output_formats():
73 """Get information regarding the different output formats supported."""
74 supported_formats = {}
75
76 supported_formats['text'] = _construct_text
77
78 if try_import('junitxml'):
79 supported_formats['xml'] = _construct_xml
80 if try_import('subunit'):
81 supported_formats['subunit'] = _construct_subunit
82 return supported_formats
83
84
85def get_default_format():
86 return 'text'
87
88
89def _construct_xml(**kwargs):
90 from junitxml import JUnitXmlResult
91 stream = kwargs.pop('stream')
92 return LoggedTestResultDecorator(
93 ExtendedToOriginalDecorator(
94 JUnitXmlResult(stream)
95 )
96 )
97
98
99def _construct_text(**kwargs):
100 stream = kwargs.pop('stream')
101 failfast = kwargs.pop('failfast')
102 return LoggedTestResultDecorator(TextTestResult(stream, failfast))
103
104
105def _construct_subunit(**kwargs):
106 from subunit import StreamResultToBytes
107 stream = kwargs.pop('stream')
108 return LoggedTestResultDecorator(
109 ExtendedToStreamDecorator(
110 StreamResultToBytes(stream)
111 )
112 )
86113
=== added file 'autopilot/tests/unit/test_testresults.py'
--- autopilot/tests/unit/test_testresults.py 1970-01-01 00:00:00 +0000
+++ autopilot/tests/unit/test_testresults.py 2013-12-02 02:12:42 +0000
@@ -0,0 +1,92 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
3# Autopilot Functional Test Tool
4# Copyright (C) 2013 Canonical
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
19
20from mock import Mock, patch
21from testtools import TestCase, PlaceHolder
22from testtools.content import text_content
23
24from autopilot.testresult import (
25 get_default_format,
26 get_output_formats,
27 LoggedTestResultDecorator,
28)
29
30
31class LoggedTestResultDecoratorTests(TestCase):
32
33 def construct_simple_content_object(self):
34 return text_content(self.getUniqueString)
35
36 def test_can_construct(self):
37 LoggedTestResultDecorator(Mock())
38
39 def test_addSuccess_calls_decorated_test(self):
40 wrapped = Mock()
41 result = LoggedTestResultDecorator(wrapped)
42 fake_test = PlaceHolder('fake_test')
43 fake_details = self.construct_simple_content_object()
44
45 result.addSuccess(fake_test, fake_details)
46
47 wrapped.addSuccess.assert_called_once_with(
48 fake_test,
49 details=fake_details
50 )
51
52 def test_addError_calls_decorated_test(self):
53 wrapped = Mock()
54 result = LoggedTestResultDecorator(wrapped)
55 fake_test = PlaceHolder('fake_test')
56 fake_error = object()
57 fake_details = self.construct_simple_content_object()
58
59 result.addError(fake_test, fake_error, fake_details)
60
61 wrapped.addError.assert_called_once_with(
62 fake_test,
63 fake_error,
64 details=fake_details
65 )
66
67 def test_addFailure_calls_decorated_test(self):
68 wrapped = Mock()
69 result = LoggedTestResultDecorator(wrapped)
70 fake_test = PlaceHolder('fake_test')
71 fake_error = object()
72 fake_details = self.construct_simple_content_object()
73
74 result.addFailure(fake_test, fake_error, fake_details)
75
76 wrapped.addFailure.assert_called_once_with(
77 fake_test,
78 fake_error,
79 details=fake_details
80 )
81
82
83class OutputFormatFactoryTests(TestCase):
84
85 def test_has_text_format(self):
86 self.assertTrue('text' in get_output_formats())
87
88 def test_has_xml_format(self):
89 self.assertTrue('xml' in get_output_formats())
90
91 def test_default_format_is_available(self):
92 self.assertTrue(get_default_format() in get_output_formats())
093
=== modified file 'debian/control'
--- debian/control 2013-11-27 20:15:34 +0000
+++ debian/control 2013-12-02 02:12:42 +0000
@@ -16,23 +16,25 @@
16 python-debian,16 python-debian,
17 python-dev,17 python-dev,
18 python-gi,18 python-gi,
19 python-junitxml,
19 python-mock,20 python-mock,
20 python-psutil,21 python-psutil,
21 python-setuptools,22 python-setuptools,
22 python-subunit,
23 python-six,23 python-six,
24 python-sphinx,24 python-sphinx,
25 python-subunit,
25 python-testscenarios,26 python-testscenarios,
26 python-testtools,27 python-testtools,
27 python-xlib,28 python-xlib,
28 python3-all-dev (>= 3.3),29 python3-all-dev (>= 3.3),
29 python3-dbus,30 python3-dbus,
30 python3-gi,31 python3-gi,
32 python3-junitxml,
31 python3-mock,33 python3-mock,
32 python3-psutil,34 python3-psutil,
33 python3-setuptools,35 python3-setuptools,
36 python3-six,
34 python3-subunit,37 python3-subunit,
35 python3-six,
36 python3-testscenarios,38 python3-testscenarios,
37 python3-testtools,39 python3-testtools,
38 python3-xlib,40 python3-xlib,

Subscribers

People subscribed via source and target branches