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)