1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
import difflib
from copy import copy
from ..caching import WeakInstMeta
from ..formatters import PlainTextFormatter
class Exit(Exception):
"""Used to catch parser.exit."""
def __init__(self, status, message):
super().__init__(message)
self.status = status
self.message = message
class Error(Exception):
"""Used to catch parser.error."""
def __init__(self, message):
super().__init__(message)
self.message = message
def noexit(status=0, message=None):
raise Exit(status, message)
def noerror(message=None):
raise Error(message)
def mangle_parser(parser):
"""Make an argparser testable."""
# Return a copy to avoid the potential of modifying what we're working on.
parser = copy(parser)
parser.exit = noexit
parser.error = noerror
parser.out = FakeStreamFormatter()
parser.err = FakeStreamFormatter()
return parser
class FormatterObject(metaclass=WeakInstMeta):
__inst_caching__ = True
def __call__(self, formatter):
formatter.stream.write(self)
class Color(FormatterObject):
__inst_caching__ = True
def __init__(self, mode, color):
self.mode = mode
self.color = color
def __repr__(self):
return f"<Color: mode - {self.mode}; color - {self.color}>"
class Reset(FormatterObject):
__inst_caching__ = True
def __repr__(self):
return "<Reset>"
class Bold(FormatterObject):
__inst_caching__ = True
def __repr__(self):
return "<Bold>"
class ListStream(list):
def write(self, *args):
stringlist = []
objectlist = []
for arg in args:
if isinstance(arg, bytes):
stringlist.append(arg)
else:
objectlist.append(b"".join(stringlist))
stringlist = []
objectlist.append(arg)
objectlist.append(b"".join(stringlist))
# We use len because boolean ops shortcircuit
if (
len(self)
and isinstance(self[-1], bytes)
and isinstance(objectlist[0], bytes)
):
self[-1] = self[-1] + objectlist.pop(0)
self.extend(objectlist)
def flush(self):
"""Stub function to fake flush() support."""
class FakeStreamFormatter(PlainTextFormatter):
def __init__(self):
super().__init__(ListStream([]))
self.reset = Reset()
self.bold = Bold()
self.first_prefix = [None]
def resetstream(self):
self.stream = ListStream([])
def fg(self, color=None):
return Color("fg", color)
def bg(self, color=None):
return Color("bg", color)
def get_text_stream(self):
return b"".join(
(x for x in self.stream if not isinstance(x, FormatterObject))
).decode("ascii")
class ArgParseMixin:
"""Provide some utility methods for testing the parser and main.
:cvar parser: ArgumentParser subclass to test.
:cvar main: main function to test.
"""
def parse(self, *args, **kwargs):
"""Parse a commandline and return the Values object.
args are passed to parse_args
"""
return self.parser.parse_args(*args, **kwargs)
@property
def parser(self):
p = copy(self._argparser)
return mangle_parser(p)
def get_main(self, namespace):
return namespace.main_func
def assertError(self, message, *args, **kwargs):
"""Pass args to the argument parser and assert it errors message."""
try:
self.parse(*args, **kwargs)
except Error as e:
assert message == e.message
else:
raise AssertionError("no error triggered")
def assertExit(self, status, message, *args, **kwargs):
"""Pass args, assert they trigger the right exit condition."""
try:
self.parse(*args, **kwargs)
except Exit as e:
assert message == e.message.strip()
assert status == e.status
else:
raise AssertionError("no exit triggered")
def assertOut(self, out, *args, **kwargs):
"""Like :obj:`assertOutAndErr` but without err."""
return self.assertOutAndErr(out, (), *args, **kwargs)
def assertErr(self, err, *args, **kwargs):
"""Like :obj:`assertOutAndErr` but without out."""
return self.assertOutAndErr((), err, *args, **kwargs)
def assertOutAndErr(self, out, err, *args, **kwargs):
"""Parse options and run main.
Extra arguments are parsed by the argument parser.
:param out: list of strings produced as output on stdout.
:param err: list of strings produced as output on stderr.
"""
options = self.parse(*args, **kwargs)
outformatter = FakeStreamFormatter()
errformatter = FakeStreamFormatter()
main = self.get_main(options)
main(options, outformatter, errformatter)
diffs = []
for name, strings, formatter in [
("out", out, outformatter),
("err", err, errformatter),
]:
actual = formatter.get_text_stream()
if strings:
expected = "\n".join(strings)
else:
expected = ""
if expected != actual:
diffs.extend(
difflib.unified_diff(
strings,
actual.split("\n")[:-1],
"expected %s" % (name,),
"actual",
lineterm="",
)
)
if diffs:
raise AssertionError("\n" + "\n".join(diffs))
return options
|