2 # subunit: extensions to Python unittest to get test results from subprocesses.
3 # Copyright (C) 2005 Robert Collins <robertc@robertcollins.net>
5 # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
6 # license at the users choice. A copy of both licenses are available in the
7 # project source as Apache-2.0 and BSD. You may not use this file except in
8 # compliance with one of these two licences.
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # license you chose for the specific language governing permissions and
14 # limitations under that license.
17 """Handlers for outcome details."""
19 from testtools
import content
, content_type
20 from testtools
.compat
import _b
, BytesIO
22 from subunit
import chunked
24 end_marker
= _b("]\n")
25 quoted_marker
= _b(" ]")
29 class DetailsParser(object):
30 """Base class/API reference for details parsing."""
33 class SimpleDetailsParser(DetailsParser
):
34 """Parser for single-part [] delimited details."""
36 def __init__(self
, state
):
37 self
._message
= _b("")
40 def lineReceived(self
, line
):
41 if line
== end_marker
:
42 self
._state
.endDetails()
44 if line
[0:2] == quoted_marker
:
46 self
._message
+= line
[1:]
50 def get_details(self
, style
=None):
53 # We know that subunit/testtools serialise [] formatted
54 # tracebacks as utf8, but perhaps we need a ReplacingContent
55 # or something like that.
56 result
['traceback'] = content
.Content(
57 content_type
.ContentType("text", "x-traceback",
59 lambda:[self
._message
])
65 result
[name
] = content
.Content(
66 content_type
.ContentType("text", "plain"),
67 lambda:[self
._message
])
70 def get_message(self
):
74 class MultipartDetailsParser(DetailsParser
):
75 """Parser for multi-part [] surrounded MIME typed chunked details."""
77 def __init__(self
, state
):
80 self
._parse
_state
= self
._look
_for
_content
82 def _look_for_content(self
, line
):
83 if line
== end_marker
:
84 self
._state
.endDetails()
87 field
, value
= line
[:-1].decode('utf8').split(' ', 1)
89 main
, sub
= value
.split('/')
91 raise ValueError("Invalid MIME type %r" % value
)
92 self
._content
_type
= content_type
.ContentType(main
, sub
)
93 self
._parse
_state
= self
._get
_name
95 def _get_name(self
, line
):
96 self
._name
= line
[:-1].decode('utf8')
97 self
._body
= BytesIO()
98 self
._chunk
_parser
= chunked
.Decoder(self
._body
)
99 self
._parse
_state
= self
._feed
_chunks
101 def _feed_chunks(self
, line
):
102 residue
= self
._chunk
_parser
.write(line
)
103 if residue
is not None:
104 # Line based use always ends on no residue.
105 assert residue
== empty
, 'residue: %r' % (residue
,)
107 self
._details
[self
._name
] = content
.Content(
108 self
._content
_type
, lambda:[body
.getvalue()])
109 self
._chunk
_parser
.close()
110 self
._parse
_state
= self
._look
_for
_content
112 def get_details(self
, for_skip
=False):
115 def get_message(self
):
118 def lineReceived(self
, line
):
119 self
._parse
_state
(line
)