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 <mtreinish@kortar.org>

Change-Id: I68cbeb47e9093b98630f096fc5818a77932c8da4
This commit is contained in:
Trevor McCasland 2017-09-25 11:04:37 -05:00
parent 37fc3f5f38
commit fed81d0901
6 changed files with 55 additions and 4 deletions

View File

@ -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 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. 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:
sql2subunit sql2subunit

View File

@ -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.

View File

@ -34,14 +34,17 @@ def get_duration(start, end):
class ReadSubunit(object): class ReadSubunit(object):
def __init__(self, stream_file, attachments=False, attr_regex=None, 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: if targets is None:
targets = [] targets = []
else: else:
targets = targets[:] targets = targets[:]
self.use_wall_time = use_wall_time self.use_wall_time = use_wall_time
self.stream_file = stream_file 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() starts = testtools.StreamResult()
summary = testtools.StreamSummary() summary = testtools.StreamSummary()
outcomes = testtools.StreamToDict(functools.partial( outcomes = testtools.StreamToDict(functools.partial(

View File

@ -60,6 +60,9 @@ SHELL_OPTS = [
help="When True the wall time of a run will be used for the " 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" "run_time column in the runs table. By default the sum of"
" the test executions are used instead."), " 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() _version_ = version.VersionInfo('subunit2sql').version_string_with_vcs()
@ -232,14 +235,16 @@ def main():
attachments=CONF.store_attachments, attachments=CONF.store_attachments,
attr_regex=CONF.attr_regex, attr_regex=CONF.attr_regex,
targets=targets, 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] for s in CONF.subunit_files]
else: else:
streams = [subunit.ReadSubunit(sys.stdin, streams = [subunit.ReadSubunit(sys.stdin,
attachments=CONF.store_attachments, attachments=CONF.store_attachments,
attr_regex=CONF.attr_regex, attr_regex=CONF.attr_regex,
targets=targets, 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: for stream in streams:
process_results(stream.get_results()) process_results(stream.get_results())

View File

@ -17,6 +17,7 @@ import datetime
import io import io
import mock import mock
import subunit as subunit_lib
from subunit2sql import read_subunit as subunit from subunit2sql import read_subunit as subunit
from subunit2sql.tests import base from subunit2sql.tests import base
@ -215,3 +216,31 @@ class TestReadSubunit(base.TestCase):
self.assertEqual(ntargets1, ntargets2) self.assertEqual(ntargets1, ntargets2)
self.assertEqual(targets, ['foo']) 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')

View File

@ -159,6 +159,7 @@ class TestMain(base.TestCase):
read_subunit_mock.assert_called_once_with(sys.stdin, read_subunit_mock.assert_called_once_with(sys.stdin,
attachments=False, attachments=False,
attr_regex='\[(.*)\]', attr_regex='\[(.*)\]',
non_subunit_name=None,
targets=[], targets=[],
use_wall_time=False) use_wall_time=False)
process_results_mock.assert_called_once_with(fake_get_results) 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, read_subunit_mock.assert_called_with(mock.ANY,
attachments=False, attachments=False,
attr_regex='\[(.*)\]', attr_regex='\[(.*)\]',
non_subunit_name=None,
targets=[], targets=[],
use_wall_time=False) use_wall_time=False)
self.assertEqual(2, len(read_subunit_mock.call_args_list)) self.assertEqual(2, len(read_subunit_mock.call_args_list))
@ -216,6 +218,7 @@ class TestMain(base.TestCase):
shell.main() shell.main()
read_subunit_mock.assert_called_once_with( read_subunit_mock.assert_called_once_with(
sys.stdin, attachments=False, attr_regex='\[(.*)\]', sys.stdin, attachments=False, attr_regex='\[(.*)\]',
non_subunit_name=None,
targets=[mock.sentinel.extension], targets=[mock.sentinel.extension],
use_wall_time=False) use_wall_time=False)
process_results_mock.assert_called_once_with(fake_get_results) process_results_mock.assert_called_once_with(fake_get_results)