Add subunit2disk which exports a stream to the fs.
This commit is contained in:
@@ -27,6 +27,7 @@ EXTRA_DIST = \
|
||||
python/subunit/tests/test_chunked.py \
|
||||
python/subunit/tests/test_details.py \
|
||||
python/subunit/tests/test_filters.py \
|
||||
python/subunit/tests/test_filter_to_disk.py \
|
||||
python/subunit/tests/test_output_filter.py \
|
||||
python/subunit/tests/test_progress_model.py \
|
||||
python/subunit/tests/test_run.py \
|
||||
@@ -58,6 +59,7 @@ dist_bin_SCRIPTS = \
|
||||
filters/subunit-stats \
|
||||
filters/subunit-tags \
|
||||
filters/subunit2csv \
|
||||
filters/subunit2disk \
|
||||
filters/subunit2gtk \
|
||||
filters/subunit2junitxml \
|
||||
filters/subunit2pyunit \
|
||||
@@ -81,7 +83,8 @@ pkgpython_PYTHON = \
|
||||
python/subunit/run.py \
|
||||
python/subunit/v2.py \
|
||||
python/subunit/test_results.py \
|
||||
python/subunit/_output.py
|
||||
python/subunit/_output.py \
|
||||
python/subunit/_to_disk.py
|
||||
|
||||
lib_LTLIBRARIES = libsubunit.la
|
||||
lib_LTLIBRARIES += libcppunit_subunit.la
|
||||
|
||||
9
NEWS
9
NEWS
@@ -5,6 +5,15 @@ subunit release notes
|
||||
NEXT (In development)
|
||||
---------------------
|
||||
|
||||
IMPROVEMENTS
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Added subunit2disk, which explodes a stream out to files on disk.
|
||||
(Robert Collins)
|
||||
|
||||
1.1.0
|
||||
-----
|
||||
|
||||
BUGFIXES
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ A number of useful things can be done easily with subunit:
|
||||
Subunit supplies the following filters:
|
||||
* tap2subunit - convert perl's TestAnythingProtocol to subunit.
|
||||
* subunit2csv - convert a subunit stream to csv.
|
||||
* subunit2disk - export a subunit stream to files on disk.
|
||||
* subunit2pyunit - convert a subunit stream to pyunit test results.
|
||||
* subunit2gtk - show a subunit stream in GTK.
|
||||
* subunit2junitxml - convert a subunit stream to JUnit's XML format.
|
||||
|
||||
23
filters/subunit2disk
Executable file
23
filters/subunit2disk
Executable file
@@ -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.
|
||||
|
||||
|
||||
"""Export a stream to files and directories on disk."""
|
||||
|
||||
from subunit._to_disk import to_disk
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(to_disk())
|
||||
131
python/subunit/_to_disk.py
Normal file
131
python/subunit/_to_disk.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# subunit: extensions to python unittest to get test results from subprocesses.
|
||||
# Copyright (C) 2009 Robert Collins <robertc@robertcollins.net>
|
||||
#
|
||||
# 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.
|
||||
|
||||
from errno import EEXIST
|
||||
import io
|
||||
import json
|
||||
import optparse
|
||||
import os.path
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
from testtools import StreamToDict
|
||||
|
||||
from subunit.filters import run_tests_from_stream
|
||||
|
||||
|
||||
def _allocate_path(root, sub):
|
||||
"""Figoure a path for sub under root.
|
||||
|
||||
If sub tries to escape root, squash it with prejuidice.
|
||||
|
||||
If the path already exists, a numeric suffix is appended.
|
||||
E.g. foo, foo-1, foo-2, etc.
|
||||
|
||||
:return: the full path to sub.
|
||||
"""
|
||||
# subpathss are allowed, but not parents.
|
||||
candidate = os.path.realpath(os.path.join(root, sub))
|
||||
realroot = os.path.realpath(root)
|
||||
if not candidate.startswith(realroot):
|
||||
sub = sub.replace('/', '_').replace('\\', '_')
|
||||
return _allocate_path(root, sub)
|
||||
|
||||
attempt = 0
|
||||
probe = candidate
|
||||
while os.path.exists(probe):
|
||||
attempt += 1
|
||||
probe = '%s-%s' % (candidate, attempt)
|
||||
return probe
|
||||
|
||||
|
||||
def _open_path(root, subpath):
|
||||
name = _allocate_path(root, subpath)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(name))
|
||||
except (OSError, IOError) as e:
|
||||
if e.errno != EEXIST:
|
||||
raise
|
||||
return io.open(name, 'wb')
|
||||
|
||||
|
||||
def _json_time(a_time):
|
||||
if a_time is None:
|
||||
return a_time
|
||||
return str(a_time)
|
||||
|
||||
|
||||
class DiskExporter:
|
||||
"""Exports tests to disk."""
|
||||
|
||||
def __init__(self, directory):
|
||||
self._directory = os.path.realpath(directory)
|
||||
|
||||
def export(self, test_dict):
|
||||
id = test_dict['id']
|
||||
tags = sorted(test_dict['tags'])
|
||||
details = test_dict['details']
|
||||
status = test_dict['status']
|
||||
start, stop = test_dict['timestamps']
|
||||
test_summary = {}
|
||||
test_summary['id'] = id
|
||||
test_summary['tags'] = tags
|
||||
test_summary['status'] = status
|
||||
test_summary['details'] = sorted(details.keys())
|
||||
test_summary['start'] = _json_time(start)
|
||||
test_summary['stop'] = _json_time(stop)
|
||||
root = _allocate_path(self._directory, id)
|
||||
with _open_path(root, 'test.json') as f:
|
||||
maybe_str = json.dumps(
|
||||
test_summary, sort_keys=True, ensure_ascii=False)
|
||||
if not isinstance(maybe_str, bytes):
|
||||
maybe_str = maybe_str.encode('utf-8')
|
||||
f.write(maybe_str)
|
||||
for name, detail in details.items():
|
||||
with _open_path(root, name) as f:
|
||||
for chunk in detail.iter_bytes():
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
def to_disk(argv=None, stdin=None, stdout=None):
|
||||
if stdout is None:
|
||||
stdout = sys.stdout
|
||||
if stdin is None:
|
||||
stdin = sys.stdin
|
||||
parser = optparse.OptionParser(
|
||||
description="Export a subunit stream to files on disk.",
|
||||
epilog=dedent("""\
|
||||
Creates a directory per test id, a JSON file with test
|
||||
metadata within that directory, and each attachment
|
||||
is written to their name relative to that directory.
|
||||
|
||||
Global packages (no test id) are discarded.
|
||||
|
||||
Exits 0 if the export was completed, or non-zero otherwise.
|
||||
"""))
|
||||
parser.add_option(
|
||||
"-d", "--directory", help="Root directory to export to.",
|
||||
default=".")
|
||||
options, args = parser.parse_args(argv)
|
||||
if len(args) > 1:
|
||||
raise Exception("Unexpected arguments.")
|
||||
if len(args):
|
||||
source = io.open(args[0], 'rb')
|
||||
else:
|
||||
source = stdin
|
||||
exporter = DiskExporter(options.directory)
|
||||
result = StreamToDict(exporter.export)
|
||||
run_tests_from_stream(source, result, protocol_version=2)
|
||||
return 0
|
||||
|
||||
@@ -31,6 +31,7 @@ from subunit.tests import (
|
||||
test_chunked,
|
||||
test_details,
|
||||
test_filters,
|
||||
test_filter_to_disk,
|
||||
test_output_filter,
|
||||
test_progress_model,
|
||||
test_run,
|
||||
@@ -54,6 +55,7 @@ def test_suite():
|
||||
result.addTest(loader.loadTestsFromModule(test_test_protocol))
|
||||
result.addTest(loader.loadTestsFromModule(test_test_protocol2))
|
||||
result.addTest(loader.loadTestsFromModule(test_tap2subunit))
|
||||
result.addTest(loader.loadTestsFromModule(test_filter_to_disk))
|
||||
result.addTest(loader.loadTestsFromModule(test_subunit_filter))
|
||||
result.addTest(loader.loadTestsFromModule(test_subunit_tags))
|
||||
result.addTest(loader.loadTestsFromModule(test_subunit_stats))
|
||||
|
||||
49
python/subunit/tests/test_filter_to_disk.py
Normal file
49
python/subunit/tests/test_filter_to_disk.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#
|
||||
# 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 io
|
||||
import os.path
|
||||
|
||||
from fixtures import TempDir
|
||||
from testtools import TestCase
|
||||
from testtools.matchers import (
|
||||
FileContains
|
||||
)
|
||||
|
||||
from subunit import _to_disk
|
||||
from subunit.v2 import StreamResultToBytes
|
||||
|
||||
class SmokeTest(TestCase):
|
||||
|
||||
def test_smoke(self):
|
||||
output = os.path.join(self.useFixture(TempDir()).path, 'output')
|
||||
stdin = io.BytesIO()
|
||||
stdout = io.StringIO()
|
||||
writer = StreamResultToBytes(stdin)
|
||||
writer.startTestRun()
|
||||
writer.status(
|
||||
'foo', 'success', set(['tag']), file_name='fred',
|
||||
file_bytes=b'abcdefg', eof=True, mime_type='text/plain')
|
||||
writer.stopTestRun()
|
||||
stdin.seek(0)
|
||||
_to_disk.to_disk(['-d', output], stdin=stdin, stdout=stdout)
|
||||
self.expectThat(
|
||||
os.path.join(output, 'foo/test.json'),
|
||||
FileContains(
|
||||
'{"details": ["fred"], "id": "foo", "start": null, '
|
||||
'"status": "success", "stop": null, "tags": ["tag"]}'))
|
||||
self.expectThat(
|
||||
os.path.join(output, 'foo/fred'),
|
||||
FileContains('abcdefg'))
|
||||
Reference in New Issue
Block a user