From fed81d0901a5783d34aaf8302392782bd390a604 Mon Sep 17 00:00:00 2001 From: Trevor McCasland Date: Mon, 25 Sep 2017 11:04:37 -0500 Subject: [PATCH] Add subunit2sql CLI option to use non_subunit_name This commit adds a new CLI option for allowing mixed subunit content. This is desired for subunit.stream files with non-subunit content mixed in but still want the subunit content converted to sql. However, this commit only adds support for passing the argument to ByteStreamToStreamResult. Nothing is added beyond that for managing the non-subunit content. Co-Authored-By: Matthew Treinish Change-Id: I68cbeb47e9093b98630f096fc5818a77932c8da4 --- doc/source/user/usage.rst | 4 +++ ..._subunit_name_option-9f898507bfadce16.yaml | 7 +++++ subunit2sql/read_subunit.py | 7 +++-- subunit2sql/shell.py | 9 ++++-- subunit2sql/tests/test_read_subunit.py | 29 +++++++++++++++++++ subunit2sql/tests/test_shell.py | 3 ++ 6 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add_non_subunit_name_option-9f898507bfadce16.yaml diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst index 7a0f2c3..5265606 100644 --- a/doc/source/user/usage.rst +++ b/doc/source/user/usage.rst @@ -49,6 +49,10 @@ path that points to any logs or other external test artifacts related to the run being added. The run_meta option takes in a dictionary which will be added to the database as key value pairs associated with the run being added. +If you want to use a subunit stream with non-subunit data mixed in you can do +this with the optional argument --non_subunit_name. This will treat all the +non-subunit data as a file attachment with the specified name. + .. _sql2subunit: sql2subunit diff --git a/releasenotes/notes/add_non_subunit_name_option-9f898507bfadce16.yaml b/releasenotes/notes/add_non_subunit_name_option-9f898507bfadce16.yaml new file mode 100644 index 0000000..69637d0 --- /dev/null +++ b/releasenotes/notes/add_non_subunit_name_option-9f898507bfadce16.yaml @@ -0,0 +1,7 @@ +--- +features: + - A new option is added to the subunit2sql CLI command, + --non_subunit_name, that is used to allow subunit files with + mixed content. The default is to raise an error containing the + non-subunit byte after it has been read from the stream. By + using this new option, the error will not be raised. diff --git a/subunit2sql/read_subunit.py b/subunit2sql/read_subunit.py index 02971e7..01824dc 100644 --- a/subunit2sql/read_subunit.py +++ b/subunit2sql/read_subunit.py @@ -34,14 +34,17 @@ def get_duration(start, end): class ReadSubunit(object): def __init__(self, stream_file, attachments=False, attr_regex=None, - targets=None, use_wall_time=False): + targets=None, use_wall_time=False, non_subunit_name=None): if targets is None: targets = [] else: targets = targets[:] self.use_wall_time = use_wall_time self.stream_file = stream_file - self.stream = subunit.ByteStreamToStreamResult(self.stream_file) + self.stream = subunit.ByteStreamToStreamResult( + self.stream_file, + non_subunit_name=non_subunit_name + ) starts = testtools.StreamResult() summary = testtools.StreamSummary() outcomes = testtools.StreamToDict(functools.partial( diff --git a/subunit2sql/shell.py b/subunit2sql/shell.py index 6f6d123..5ed5c91 100644 --- a/subunit2sql/shell.py +++ b/subunit2sql/shell.py @@ -60,6 +60,9 @@ SHELL_OPTS = [ help="When True the wall time of a run will be used for the " "run_time column in the runs table. By default the sum of" " the test executions are used instead."), + cfg.StrOpt('non_subunit_name', default=None, + help='Allows non-subunit content and stores it under this' + ' name'), ] _version_ = version.VersionInfo('subunit2sql').version_string_with_vcs() @@ -232,14 +235,16 @@ def main(): attachments=CONF.store_attachments, attr_regex=CONF.attr_regex, targets=targets, - use_wall_time=CONF.use_run_wall_time) + use_wall_time=CONF.use_run_wall_time, + non_subunit_name=CONF.non_subunit_name) for s in CONF.subunit_files] else: streams = [subunit.ReadSubunit(sys.stdin, attachments=CONF.store_attachments, attr_regex=CONF.attr_regex, targets=targets, - use_wall_time=CONF.use_run_wall_time)] + use_wall_time=CONF.use_run_wall_time, + non_subunit_name=CONF.non_subunit_name)] for stream in streams: process_results(stream.get_results()) diff --git a/subunit2sql/tests/test_read_subunit.py b/subunit2sql/tests/test_read_subunit.py index a3ed6ba..26c91dd 100644 --- a/subunit2sql/tests/test_read_subunit.py +++ b/subunit2sql/tests/test_read_subunit.py @@ -17,6 +17,7 @@ import datetime import io import mock +import subunit as subunit_lib from subunit2sql import read_subunit as subunit from subunit2sql.tests import base @@ -215,3 +216,31 @@ class TestReadSubunit(base.TestCase): self.assertEqual(ntargets1, ntargets2) self.assertEqual(targets, ['foo']) + + def test_non_subunit_name(self): + non_subunit_name = 'fake_non_subunit' + fake_subunit = subunit.ReadSubunit(mock.MagicMock(), + non_subunit_name=non_subunit_name) + self.assertEqual(fake_subunit.stream.non_subunit_name, + non_subunit_name) + + def test_not_subunit_no_subunit_name_set(self): + stream_buf = io.BytesIO() + stream = subunit_lib.StreamResultToBytes(stream_buf) + stream.status(test_id='test_a', test_status='inprogress') + stream.status(test_id='test_a', test_status='success') + stream_buf.write(b'I AM NOT SUBUNIT') + stream_buf.seek(0) + result = subunit.ReadSubunit(stream_buf) + exc_found = False + try: + result.get_results() + # NOTE(mtreinish): Subunit raises the generic Exception class + # so manually inspect the Exception object to check the error + # message + except Exception as e: + self.assertIsInstance(e, Exception) + self.assertEqual(e.args, ('Non subunit content', b'I')) + exc_found = True + self.assertTrue(exc_found, + 'subunit exception not raised on invalid content') diff --git a/subunit2sql/tests/test_shell.py b/subunit2sql/tests/test_shell.py index d6cb693..07a6b2f 100644 --- a/subunit2sql/tests/test_shell.py +++ b/subunit2sql/tests/test_shell.py @@ -159,6 +159,7 @@ class TestMain(base.TestCase): read_subunit_mock.assert_called_once_with(sys.stdin, attachments=False, attr_regex='\[(.*)\]', + non_subunit_name=None, targets=[], use_wall_time=False) process_results_mock.assert_called_once_with(fake_get_results) @@ -185,6 +186,7 @@ class TestMain(base.TestCase): read_subunit_mock.assert_called_with(mock.ANY, attachments=False, attr_regex='\[(.*)\]', + non_subunit_name=None, targets=[], use_wall_time=False) self.assertEqual(2, len(read_subunit_mock.call_args_list)) @@ -216,6 +218,7 @@ class TestMain(base.TestCase): shell.main() read_subunit_mock.assert_called_once_with( sys.stdin, attachments=False, attr_regex='\[(.*)\]', + non_subunit_name=None, targets=[mock.sentinel.extension], use_wall_time=False) process_results_mock.assert_called_once_with(fake_get_results)