commit python-python-subunit for openSUSE:Factory
Hello community,
here is the log from the commit of package python-python-subunit for openSUSE:Factory checked in at 2014-01-30 17:45:10
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-subunit (Old)
and /work/SRC/openSUSE:Factory/.python-python-subunit.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-subunit"
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-subunit/python-python-subunit.changes 2014-01-23 15:54:39.000000000 +0100
+++ /work/SRC/openSUSE:Factory/.python-python-subunit.new/python-python-subunit.changes 2014-01-30 17:45:11.000000000 +0100
@@ -1,0 +2,15 @@
+Thu Jan 30 11:53:52 UTC 2014 - speilicke@suse.com
+
+- Update to version 0.0.18:
+ + Fix compatibility with testtools 0.9.35 which dropped the 'all' compat
+ symbol. This breaks support for Python versions lower than 2.6.
+- Changes from version 0.0.17:
+ + Add subunit-output tool that can generate a Subunit v2 bytestream from
+ arguments passed on the command line. (Thomi Richards, #1252084)
+- Add /usr/bin/subunit-output
+- Add python-subunit-filter-path.patch:
+ + Fix filters path in testsuite
+- Properly run testsuite
+- Fix requirements
+
+-------------------------------------------------------------------
@@ -12 +27 @@
- * When tests fail to import ``python subunit.run -l ...`` will now write a
+ * When tests fail to import python subunit.run -l ... will now write a
@@ -33,3 +48,3 @@
- * `subunit-stats` no longer outputs encapsulated stdout as subunit.
- * The logic for `subunit.run` is now importable via python -
- `subunit.run.main`. (Robert Collins, #606770)
+ * subunit-stats no longer outputs encapsulated stdout as subunit.
+ * The logic for subunit.run is now importable via python -
+ subunit.run.main. (Robert Collins, #606770)
Old:
----
python-subunit-0.0.16.tar.gz
New:
----
python-subunit-0.0.18.tar.gz
python-subunit-filter-path.patch
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-python-subunit.spec ++++++
--- /var/tmp/diff_new_pack.LlFNoN/_old 2014-01-30 17:45:12.000000000 +0100
+++ /var/tmp/diff_new_pack.LlFNoN/_new 2014-01-30 17:45:12.000000000 +0100
@@ -17,16 +17,21 @@
Name: python-python-subunit
-Version: 0.0.16
+Version: 0.0.18
Release: 0
Summary: Python implementation of subunit test streaming protocol
License: Apache-2.0 or BSD-3-Clause
Group: Development/Languages/Python
Url: http://launchpad.net/subunit
Source: http://pypi.python.org/packages/source/p/python-subunit/python-subunit-%{version}.tar.gz
+# PATCH-FIX-OPENSUSE speilicke@suse.com -- Filters are installed in /usr/bin.
+Patch0: python-subunit-filter-path.patch
BuildRequires: python-devel
-BuildRequires: python-nose
+# Test requriements
+BuildRequires: python-testscenarios
+BuildRequires: python-testtools >= 0.9.34
Requires: python-extras
+Requires: python-testscenarios
Requires: python-testtools >= 0.9.34
BuildRoot: %{_tmppath}/%{name}-%{version}-build
%if 0%{?suse_version} && 0%{?suse_version} <= 1110
@@ -46,15 +51,23 @@
%prep
%setup -q -n python-subunit-%{version}
+%patch0 -p1
%build
python setup.py build
%install
python setup.py install --prefix=%{_prefix} --root=%{buildroot}
+#NOTE(saschpe): The testsuite demands executables:
+chmod +x %{buildroot}%{python_sitelib}/subunit/tests/sample-*.py
%check
-nosetests
+#NOTE(saschpe): Pesky testsuite doesn't know about buildroot
+sed -i "s|'/',|'%{buildroot}',|" %{buildroot}%{python_sitelib}/subunit/tests/test_subunit_filter.py
+PYTHONPATH=%{buildroot}%{python_sitelib} python -m testtools.run subunit.test_suite
+sed -i "s|'%{buildroot}',|'/',|" %{buildroot}%{python_sitelib}/subunit/tests/test_subunit_filter.py
+# Fixup compiled python module afterwards:
+%py_compile %{buildroot}%{python_sitelib}/subunit/tests
%files
%defattr(-,root,root,-)
@@ -66,10 +79,12 @@
%{_bindir}/subunit-notify
%{_bindir}/subunit-stats
%{_bindir}/subunit-tags
+%{_bindir}/subunit-output
%{_bindir}/subunit2gtk
%{_bindir}/subunit2junitxml
%{_bindir}/subunit2pyunit
%{_bindir}/tap2subunit
-%{python_sitelib}/*
+%{python_sitelib}/subunit
+%{python_sitelib}/python_subunit-%{version}-py%{py_ver}.egg-info
%changelog
++++++ python-subunit-0.0.16.tar.gz -> python-subunit-0.0.18.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/NEWS new/python-subunit-0.0.18/NEWS
--- old/python-subunit-0.0.16/NEWS 2013-11-30 03:04:01.000000000 +0100
+++ new/python-subunit-0.0.18/NEWS 2014-01-29 20:13:19.000000000 +0100
@@ -5,6 +5,25 @@
NEXT (In development)
---------------------
+0.0.18
+------
+
+IMPROVMENTS
+~~~~~~~~~~~
+
+* Fix compatibility with testtools 0.9.35 which dropped the 'all' compat
+ symbol. This breaks support for Python versions lower than 2.6.
+ (Robert Collins, #1274056)
+
+0.0.17
+------
+
+IMPROVMENTS
+~~~~~~~~~~~
+
+* Add ``subunit-output`` tool that can generate a Subunit v2 bytestream from
+ arguments passed on the command line. (Thomi Richards, #1252084)
+
0.0.16
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/PKG-INFO new/python-subunit-0.0.18/PKG-INFO
--- old/python-subunit-0.0.16/PKG-INFO 2013-11-30 10:17:05.000000000 +0100
+++ new/python-subunit-0.0.18/PKG-INFO 2014-01-29 20:16:59.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: python-subunit
-Version: 0.0.16
+Version: 0.0.18
Summary: Python implementation of subunit test streaming protocol
Home-page: http://launchpad.net/subunit
Author: Robert Collins
@@ -470,6 +470,8 @@
========
* Update versions in configure.ac and python/subunit/__init__.py.
+ * Update Makefile in the root or do an inplace configure to get an updated Makefile.
+ * Update NEWS.
* Make PyPI and regular tarball releases. Upload the regular one to LP, the
PyPI one to PyPI.
* Push a tagged commit.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/README new/python-subunit-0.0.18/README
--- old/python-subunit-0.0.16/README 2013-11-25 02:50:04.000000000 +0100
+++ new/python-subunit-0.0.18/README 2014-01-29 10:32:50.000000000 +0100
@@ -462,6 +462,8 @@
========
* Update versions in configure.ac and python/subunit/__init__.py.
+* Update Makefile in the root or do an inplace configure to get an updated Makefile.
+* Update NEWS.
* Make PyPI and regular tarball releases. Upload the regular one to LP, the
PyPI one to PyPI.
* Push a tagged commit.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/filters/subunit-output new/python-subunit-0.0.18/filters/subunit-output
--- old/python-subunit-0.0.16/filters/subunit-output 1970-01-01 01:00:00.000000000 +0100
+++ new/python-subunit-0.0.18/filters/subunit-output 2014-01-13 01:07:19.000000000 +0100
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2013 Subunit Contributors
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+
+"""A command-line tool to generate a subunit result byte-stream."""
+
+from subunit._output import output_main
+
+
+if __name__ == '__main__':
+ exit(output_main())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/python/subunit/__init__.py new/python-subunit-0.0.18/python/subunit/__init__.py
--- old/python-subunit-0.0.16/python/subunit/__init__.py 2013-11-30 03:02:19.000000000 +0100
+++ new/python-subunit-0.0.18/python/subunit/__init__.py 2014-01-29 20:14:12.000000000 +0100
@@ -153,7 +153,7 @@
# If the releaselevel is 'final', then the tarball will be major.minor.micro.
# Otherwise it is major.minor.micro~$(revno).
-__version__ = (0, 0, 16, 'final', 0)
+__version__ = (0, 0, 18, 'final', 0)
PROGRESS_SET = 0
PROGRESS_CUR = 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/python/subunit/_output.py new/python-subunit-0.0.18/python/subunit/_output.py
--- old/python-subunit-0.0.16/python/subunit/_output.py 1970-01-01 01:00:00.000000000 +0100
+++ new/python-subunit-0.0.18/python/subunit/_output.py 2014-01-13 01:07:19.000000000 +0100
@@ -0,0 +1,203 @@
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2013 Subunit Contributors
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+import datetime
+from functools import partial
+from optparse import (
+ OptionGroup,
+ OptionParser,
+ OptionValueError,
+)
+import sys
+
+from subunit import make_stream_binary
+from subunit.iso8601 import UTC
+from subunit.v2 import StreamResultToBytes
+
+
+_FINAL_ACTIONS = frozenset([
+ 'exists',
+ 'fail',
+ 'skip',
+ 'success',
+ 'uxsuccess',
+ 'xfail',
+])
+_ALL_ACTIONS = _FINAL_ACTIONS.union(['inprogress'])
+_CHUNK_SIZE=3670016 # 3.5 MiB
+
+
+def output_main():
+ args = parse_arguments()
+ output = StreamResultToBytes(sys.stdout)
+ generate_stream_results(args, output)
+ return 0
+
+
+def parse_arguments(args=None, ParserClass=OptionParser):
+ """Parse arguments from the command line.
+
+ If specified, args must be a list of strings, similar to sys.argv[1:].
+
+ ParserClass may be specified to override the class we use to parse the
+ command-line arguments. This is useful for testing.
+ """
+ parser = ParserClass(
+ prog="subunit-output",
+ description="A tool to generate a subunit v2 result byte-stream",
+ usage="subunit-output [-h] [status TEST_ID] [options]",
+ )
+ parser.set_default('tags', None)
+ parser.set_default('test_id', None)
+
+ status_commands = OptionGroup(
+ parser,
+ "Status Commands",
+ "These options report the status of a test. TEST_ID must be a string "
+ "that uniquely identifies the test."
+ )
+ for action_name in _ALL_ACTIONS:
+ status_commands.add_option(
+ "--%s" % action_name,
+ nargs=1,
+ action="callback",
+ callback=set_status_cb,
+ callback_args=(action_name,),
+ dest="action",
+ metavar="TEST_ID",
+ help="Report a test status."
+ )
+ parser.add_option_group(status_commands)
+
+ file_commands = OptionGroup(
+ parser,
+ "File Options",
+ "These options control attaching data to a result stream. They can "
+ "either be specified with a status command, in which case the file "
+ "is attached to the test status, or by themselves, in which case "
+ "the file is attached to the stream (and not associated with any "
+ "test id)."
+ )
+ file_commands.add_option(
+ "--attach-file",
+ help="Attach a file to the result stream for this test. If '-' is "
+ "specified, stdin will be read instead. In this case, the file "
+ "name will be set to 'stdin' (but can still be overridden with "
+ "the --file-name option)."
+ )
+ file_commands.add_option(
+ "--file-name",
+ help="The name to give this file attachment. If not specified, the "
+ "name of the file on disk will be used, or 'stdin' in the case "
+ "where '-' was passed to the '--attach-file' argument. This option"
+ " may only be specified when '--attach-file' is specified.",
+ )
+ file_commands.add_option(
+ "--mimetype",
+ help="The mime type to send with this file. This is only used if the "
+ "--attach-file argument is used. This argument is optional. If it "
+ "is not specified, the file will be sent without a mime type. This "
+ "option may only be specified when '--attach-file' is specified.",
+ default=None
+ )
+ parser.add_option_group(file_commands)
+
+ parser.add_option(
+ "--tag",
+ help="Specifies a tag. May be used multiple times",
+ action="append",
+ dest="tags",
+ default=[]
+ )
+
+ (options, args) = parser.parse_args(args)
+ if options.mimetype and not options.attach_file:
+ parser.error("Cannot specify --mimetype without --attach-file")
+ if options.file_name and not options.attach_file:
+ parser.error("Cannot specify --file-name without --attach-file")
+ if options.attach_file:
+ if options.attach_file == '-':
+ if not options.file_name:
+ options.file_name = 'stdin'
+ options.attach_file = make_stream_binary(sys.stdin)
+ else:
+ try:
+ options.attach_file = open(options.attach_file, 'rb')
+ except IOError as e:
+ parser.error("Cannot open %s (%s)" % (options.attach_file, e.strerror))
+
+ return options
+
+
+def set_status_cb(option, opt_str, value, parser, status_name):
+ if getattr(parser.values, "action", None) is not None:
+ raise OptionValueError("argument %s: Only one status may be specified at once." % opt_str)
+
+ if len(parser.rargs) == 0:
+ raise OptionValueError("argument %s: must specify a single TEST_ID." % opt_str)
+ parser.values.action = status_name
+ parser.values.test_id = parser.rargs.pop(0)
+
+
+def generate_stream_results(args, output_writer):
+ output_writer.startTestRun()
+
+ if args.attach_file:
+ reader = partial(args.attach_file.read, _CHUNK_SIZE)
+ this_file_hunk = reader()
+ next_file_hunk = reader()
+
+ is_first_packet = True
+ is_last_packet = False
+ while not is_last_packet:
+ write_status = output_writer.status
+
+ if is_first_packet:
+ if args.attach_file:
+ if args.mimetype:
+ write_status = partial(write_status, mime_type=args.mimetype)
+ if args.tags:
+ write_status = partial(write_status, test_tags=set(args.tags))
+ write_status = partial(write_status, timestamp=create_timestamp())
+ if args.action not in _FINAL_ACTIONS:
+ write_status = partial(write_status, test_status=args.action)
+ is_first_packet = False
+
+ if args.attach_file:
+ filename = args.file_name or args.attach_file.name
+ write_status = partial(write_status, file_name=filename, file_bytes=this_file_hunk)
+ if next_file_hunk == b'':
+ write_status = partial(write_status, eof=True)
+ is_last_packet = True
+ else:
+ this_file_hunk = next_file_hunk
+ next_file_hunk = reader()
+ else:
+ is_last_packet = True
+
+ if args.test_id:
+ write_status = partial(write_status, test_id=args.test_id)
+
+ if is_last_packet:
+ if args.action in _FINAL_ACTIONS:
+ write_status = partial(write_status, test_status=args.action)
+
+ write_status()
+
+ output_writer.stopTestRun()
+
+
+def create_timestamp():
+ return datetime.datetime.now(UTC)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/python/subunit/test_results.py new/python-subunit-0.0.18/python/subunit/test_results.py
--- old/python-subunit-0.0.16/python/subunit/test_results.py 2013-04-08 12:28:36.000000000 +0200
+++ new/python-subunit-0.0.18/python/subunit/test_results.py 2014-01-29 20:10:33.000000000 +0100
@@ -20,7 +20,6 @@
import datetime
import testtools
-from testtools.compat import all
from testtools.content import (
text_content,
TracebackContent,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/python/subunit/tests/__init__.py new/python-subunit-0.0.18/python/subunit/tests/__init__.py
--- old/python-subunit-0.0.16/python/subunit/tests/__init__.py 2013-11-25 03:34:32.000000000 +0100
+++ new/python-subunit-0.0.18/python/subunit/tests/__init__.py 2014-01-13 01:07:19.000000000 +0100
@@ -17,6 +17,8 @@
import sys
from unittest import TestLoader
+from testscenarios import generate_scenarios
+
# Before the test module imports to avoid circularity.
# For testing: different pythons have different str() implementations.
@@ -34,6 +36,7 @@
test_chunked,
test_details,
test_filters,
+ test_output_filter,
test_progress_model,
test_run,
test_subunit_filter,
@@ -60,4 +63,7 @@
result.addTest(loader.loadTestsFromModule(test_subunit_tags))
result.addTest(loader.loadTestsFromModule(test_subunit_stats))
result.addTest(loader.loadTestsFromModule(test_run))
+ result.addTests(
+ generate_scenarios(loader.loadTestsFromModule(test_output_filter))
+ )
return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-subunit-0.0.16/python/subunit/tests/test_output_filter.py new/python-subunit-0.0.18/python/subunit/tests/test_output_filter.py
--- old/python-subunit-0.0.16/python/subunit/tests/test_output_filter.py 1970-01-01 01:00:00.000000000 +0100
+++ new/python-subunit-0.0.18/python/subunit/tests/test_output_filter.py 2014-01-13 01:07:19.000000000 +0100
@@ -0,0 +1,596 @@
+#
+# subunit: extensions to python unittest to get test results from subprocesses.
+# Copyright (C) 2013 Subunit Contributors
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+#
+
+import datetime
+from functools import partial
+from io import BytesIO, StringIO, TextIOWrapper
+import optparse
+import sys
+from tempfile import NamedTemporaryFile
+
+from contextlib import contextmanager
+from testtools import TestCase
+from testtools.compat import _u
+from testtools.matchers import (
+ Equals,
+ Matcher,
+ MatchesAny,
+ MatchesListwise,
+ Mismatch,
+ raises,
+)
+from testtools.testresult.doubles import StreamResult
+
+from subunit.iso8601 import UTC
+from subunit.v2 import StreamResultToBytes, ByteStreamToStreamResult
+from subunit._output import (
+ _ALL_ACTIONS,
+ _FINAL_ACTIONS,
+ generate_stream_results,
+ parse_arguments,
+)
+import subunit._output as _o
+
+
+class SafeOptionParser(optparse.OptionParser):
+ """An ArgumentParser class that doesn't call sys.exit."""
+
+ def exit(self, status=0, message=""):
+ raise RuntimeError(message)
+
+ def error(self, message):
+ raise RuntimeError(message)
+
+
+safe_parse_arguments = partial(parse_arguments, ParserClass=SafeOptionParser)
+
+
+class TestStatusArgParserTests(TestCase):
+
+ scenarios = [
+ (cmd, dict(command=cmd, option='--' + cmd)) for cmd in _ALL_ACTIONS
+ ]
+
+ def test_can_parse_all_commands_with_test_id(self):
+ test_id = self.getUniqueString()
+ args = safe_parse_arguments(args=[self.option, test_id])
+
+ self.assertThat(args.action, Equals(self.command))
+ self.assertThat(args.test_id, Equals(test_id))
+
+ def test_all_commands_parse_file_attachment(self):
+ with NamedTemporaryFile() as tmp_file:
+ args = safe_parse_arguments(
+ args=[self.option, 'foo', '--attach-file', tmp_file.name]
+ )
+ self.assertThat(args.attach_file.name, Equals(tmp_file.name))
+
+ def test_all_commands_accept_mimetype_argument(self):
+ with NamedTemporaryFile() as tmp_file:
+ args = safe_parse_arguments(
+ args=[self.option, 'foo', '--attach-file', tmp_file.name, '--mimetype', "text/plain"]
+ )
+ self.assertThat(args.mimetype, Equals("text/plain"))
+
+ def test_all_commands_accept_file_name_argument(self):
+ with NamedTemporaryFile() as tmp_file:
+ args = safe_parse_arguments(
+ args=[self.option, 'foo', '--attach-file', tmp_file.name, '--file-name', "foo"]
+ )
+ self.assertThat(args.file_name, Equals("foo"))
+
+ def test_all_commands_accept_tags_argument(self):
+ args = safe_parse_arguments(
+ args=[self.option, 'foo', '--tag', "foo", "--tag", "bar", "--tag", "baz"]
+ )
+ self.assertThat(args.tags, Equals(["foo", "bar", "baz"]))
+
+ def test_attach_file_with_hyphen_opens_stdin(self):
+ self.patch(_o.sys, 'stdin', TextIOWrapper(BytesIO(b"Hello")))
+ args = safe_parse_arguments(
+ args=[self.option, "foo", "--attach-file", "-"]
+ )
+
+ self.assertThat(args.attach_file.read(), Equals(b"Hello"))
+
+ def test_attach_file_with_hyphen_sets_filename_to_stdin(self):
+ args = safe_parse_arguments(
+ args=[self.option, "foo", "--attach-file", "-"]
+ )
+
+ self.assertThat(args.file_name, Equals("stdin"))
+
+ def test_can_override_stdin_filename(self):
+ args = safe_parse_arguments(
+ args=[self.option, "foo", "--attach-file", "-", '--file-name', 'foo']
+ )
+
+ self.assertThat(args.file_name, Equals("foo"))
+
+ def test_requires_test_id(self):
+ fn = lambda: safe_parse_arguments(args=[self.option])
+ self.assertThat(
+ fn,
+ raises(RuntimeError('argument %s: must specify a single TEST_ID.' % self.option))
+ )
+
+
+class ArgParserTests(TestCase):
+
+ def test_can_parse_attach_file_without_test_id(self):
+ with NamedTemporaryFile() as tmp_file:
+ args = safe_parse_arguments(
+ args=["--attach-file", tmp_file.name]
+ )
+ self.assertThat(args.attach_file.name, Equals(tmp_file.name))
+
+ def test_can_run_without_args(self):
+ args = safe_parse_arguments([])
+
+ def test_cannot_specify_more_than_one_status_command(self):
+ fn = lambda: safe_parse_arguments(['--fail', 'foo', '--skip', 'bar'])
+ self.assertThat(
+ fn,
+ raises(RuntimeError('argument --skip: Only one status may be specified at once.'))
+ )
+
+ def test_cannot_specify_mimetype_without_attach_file(self):
+ fn = lambda: safe_parse_arguments(['--mimetype', 'foo'])
+ self.assertThat(
+ fn,
+ raises(RuntimeError('Cannot specify --mimetype without --attach-file'))
+ )
+
+ def test_cannot_specify_filename_without_attach_file(self):
+ fn = lambda: safe_parse_arguments(['--file-name', 'foo'])
+ self.assertThat(
+ fn,
+ raises(RuntimeError('Cannot specify --file-name without --attach-file'))
+ )
+
+ def test_can_specify_tags_without_status_command(self):
+ args = safe_parse_arguments(['--tag', 'foo'])
+ self.assertEqual(['foo'], args.tags)
+
+ def test_must_specify_tags_with_tags_options(self):
+ fn = lambda: safe_parse_arguments(['--fail', 'foo', '--tag'])
+ self.assertThat(
+ fn,
+ MatchesAny(
+ raises(RuntimeError('--tag option requires 1 argument')),
+ raises(RuntimeError('--tag option requires an argument')),
+ )
+ )
+
+def get_result_for(commands):
+ """Get a result object from *commands.
+
+ Runs the 'generate_stream_results' function from subunit._output after
+ parsing *commands as if they were specified on the command line. The
+ resulting bytestream is then converted back into a result object and
+ returned.
+ """
+ result = StreamResult()
+ args = safe_parse_arguments(commands)
+ generate_stream_results(args, result)
+ return result
+
+
+@contextmanager
+def temp_file_contents(data):
+ """Create a temporary file on disk containing 'data'."""
+ with NamedTemporaryFile() as f:
+ f.write(data)
+ f.seek(0)
+ yield f
+
+
+class StatusStreamResultTests(TestCase):
+
+ scenarios = [
+ (s, dict(status=s, option='--' + s)) for s in _ALL_ACTIONS
+ ]
+
+ _dummy_timestamp = datetime.datetime(2013, 1, 1, 0, 0, 0, 0, UTC)
+
+ def setUp(self):
+ super(StatusStreamResultTests, self).setUp()
+ self.patch(_o, 'create_timestamp', lambda: self._dummy_timestamp)
+ self.test_id = self.getUniqueString()
+
+ def test_only_one_packet_is_generated(self):
+ result = get_result_for([self.option, self.test_id])
+ self.assertThat(
+ len(result._events),
+ Equals(3) # startTestRun and stopTestRun are also called, making 3 total.
+ )
+
+ def test_correct_status_is_generated(self):
+ result = get_result_for([self.option, self.test_id])
+
+ self.assertThat(
+ result._events[1],
+ MatchesStatusCall(test_status=self.status)
+ )
+
+ def test_all_commands_generate_tags(self):
+ result = get_result_for([self.option, self.test_id, '--tag', 'hello', '--tag', 'world'])
+ self.assertThat(
+ result._events[1],
+ MatchesStatusCall(test_tags=set(['hello', 'world']))
+ )
+
+ def test_all_commands_generate_timestamp(self):
+ result = get_result_for([self.option, self.test_id])
+
+ self.assertThat(
+ result._events[1],
+ MatchesStatusCall(timestamp=self._dummy_timestamp)
+ )
+
+ def test_all_commands_generate_correct_test_id(self):
+ result = get_result_for([self.option, self.test_id])
+
+ self.assertThat(
+ result._events[1],
+ MatchesStatusCall(test_id=self.test_id)
+ )
+
+ def test_file_is_sent_in_single_packet(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_can_read_binary_files(self):
+ with temp_file_contents(b"\xDE\xAD\xBE\xEF") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_bytes=b"\xDE\xAD\xBE\xEF", eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_can_read_empty_files(self):
+ with temp_file_contents(b"") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_bytes=b"", file_name=f.name, eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_can_read_stdin(self):
+ self.patch(_o.sys, 'stdin', TextIOWrapper(BytesIO(b"\xFE\xED\xFA\xCE")))
+ result = get_result_for([self.option, self.test_id, '--attach-file', '-'])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_bytes=b"\xFE\xED\xFA\xCE", file_name='stdin', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_is_sent_with_test_id(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_is_sent_with_test_status(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_status=self.status, file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_chunk_size_is_honored(self):
+ with temp_file_contents(b"Hello") as f:
+ self.patch(_o, '_CHUNK_SIZE', 1)
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'H', eof=False),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'e', eof=False),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'l', eof=False),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'l', eof=False),
+ MatchesStatusCall(test_id=self.test_id, file_bytes=b'o', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_mimetype_specified_once_only(self):
+ with temp_file_contents(b"Hi") as f:
+ self.patch(_o, '_CHUNK_SIZE', 1)
+ result = get_result_for([
+ self.option,
+ self.test_id,
+ '--attach-file',
+ f.name,
+ '--mimetype',
+ 'text/plain',
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=self.test_id, mime_type='text/plain', file_bytes=b'H', eof=False),
+ MatchesStatusCall(test_id=self.test_id, mime_type=None, file_bytes=b'i', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_tags_specified_once_only(self):
+ with temp_file_contents(b"Hi") as f:
+ self.patch(_o, '_CHUNK_SIZE', 1)
+ result = get_result_for([
+ self.option,
+ self.test_id,
+ '--attach-file',
+ f.name,
+ '--tag',
+ 'foo',
+ '--tag',
+ 'bar',
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=self.test_id, test_tags=set(['foo', 'bar'])),
+ MatchesStatusCall(test_id=self.test_id, test_tags=None),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_timestamp_specified_once_only(self):
+ with temp_file_contents(b"Hi") as f:
+ self.patch(_o, '_CHUNK_SIZE', 1)
+ result = get_result_for([
+ self.option,
+ self.test_id,
+ '--attach-file',
+ f.name,
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=self.test_id, timestamp=self._dummy_timestamp),
+ MatchesStatusCall(test_id=self.test_id, timestamp=None),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_test_status_specified_once_only(self):
+ with temp_file_contents(b"Hi") as f:
+ self.patch(_o, '_CHUNK_SIZE', 1)
+ result = get_result_for([
+ self.option,
+ self.test_id,
+ '--attach-file',
+ f.name,
+ ])
+
+ # 'inprogress' status should be on the first packet only, all other
+ # statuses should be on the last packet.
+ if self.status in _FINAL_ACTIONS:
+ first_call = MatchesStatusCall(test_id=self.test_id, test_status=None)
+ last_call = MatchesStatusCall(test_id=self.test_id, test_status=self.status)
+ else:
+ first_call = MatchesStatusCall(test_id=self.test_id, test_status=self.status)
+ last_call = MatchesStatusCall(test_id=self.test_id, test_status=None)
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ first_call,
+ last_call,
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_filename_can_be_overridden(self):
+ with temp_file_contents(b"Hello") as f:
+ specified_file_name = self.getUniqueString()
+ result = get_result_for([
+ self.option,
+ self.test_id,
+ '--attach-file',
+ f.name,
+ '--file-name',
+ specified_file_name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_name=specified_file_name, file_bytes=b'Hello'),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_name_is_used_by_default(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for([self.option, self.test_id, '--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_name=f.name, file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+
+class FileDataTests(TestCase):
+
+ def test_can_attach_file_without_test_id(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for(['--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_id=None, file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_file_name_is_used_by_default(self):
+ with temp_file_contents(b"Hello") as f:
+ result = get_result_for(['--attach-file', f.name])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_name=f.name, file_bytes=b'Hello', eof=True),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_filename_can_be_overridden(self):
+ with temp_file_contents(b"Hello") as f:
+ specified_file_name = self.getUniqueString()
+ result = get_result_for([
+ '--attach-file',
+ f.name,
+ '--file-name',
+ specified_file_name
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_name=specified_file_name, file_bytes=b'Hello'),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_files_have_timestamp(self):
+ _dummy_timestamp = datetime.datetime(2013, 1, 1, 0, 0, 0, 0, UTC)
+ self.patch(_o, 'create_timestamp', lambda: _dummy_timestamp)
+
+ with temp_file_contents(b"Hello") as f:
+ specified_file_name = self.getUniqueString()
+ result = get_result_for([
+ '--attach-file',
+ f.name,
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(file_bytes=b'Hello', timestamp=_dummy_timestamp),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+ def test_can_specify_tags_without_test_status(self):
+ result = get_result_for([
+ '--tag',
+ 'foo',
+ ])
+
+ self.assertThat(
+ result._events,
+ MatchesListwise([
+ MatchesStatusCall(call='startTestRun'),
+ MatchesStatusCall(test_tags=set(['foo'])),
+ MatchesStatusCall(call='stopTestRun'),
+ ])
+ )
+
+
+class MatchesStatusCall(Matcher):
+
+ _position_lookup = {
+ 'call': 0,
+ 'test_id': 1,
+ 'test_status': 2,
+ 'test_tags': 3,
+ 'runnable': 4,
+ 'file_name': 5,
+ 'file_bytes': 6,
+ 'eof': 7,
+ 'mime_type': 8,
+ 'route_code': 9,
+ 'timestamp': 10,
+ }
+
+ def __init__(self, **kwargs):
+ unknown_kwargs = list(filter(
+ lambda k: k not in self._position_lookup,
+ kwargs
+ ))
+ if unknown_kwargs:
+ raise ValueError("Unknown keywords: %s" % ','.join(unknown_kwargs))
+ self._filters = kwargs
+
+ def match(self, call_tuple):
+ for k, v in self._filters.items():
+ try:
+ pos = self._position_lookup[k]
+ if call_tuple[pos] != v:
+ return Mismatch(
+ "Value for key is %r, not %r" % (call_tuple[pos], v)
+ )
+ except IndexError:
+ return Mismatch("Key %s is not present." % k)
+
+ def __str__(self):
+ return "
participants (1)
-
root@hilbert.suse.de