-
Notifications
You must be signed in to change notification settings - Fork 41
/
Copy pathtest_adventurelib.py
374 lines (277 loc) · 10 KB
/
test_adventurelib.py
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
from unittest.mock import patch
from contextlib import redirect_stdout, contextmanager
from io import StringIO
import pytest
import adventurelib
from adventurelib import Pattern, when, _handle_command, say, Room, Item, Bag
orig_commands = adventurelib.commands[:]
@contextmanager
def active_context(ctx):
"""Context manager to set the current command context."""
prev_ctx = adventurelib.current_context
adventurelib.set_context(ctx)
try:
yield
finally:
adventurelib.set_context(prev_ctx)
def teardown():
"""Reset the commands."""
adventurelib.commands[:] = orig_commands
def test_match():
matches = Pattern('apple').match(['apple'])
assert matches == {}
def test_no_match_different_words():
matches = Pattern('look').match(['apple'])
assert matches is None
def test_no_match_extra_words():
matches = Pattern('look').match(['look', 'at', 'butler'])
assert matches is None
def test_multiple_words():
pat = Pattern('take apple')
matches = pat.match(['take', 'apple'])
assert matches == {}
def test_multiple_words_mismatch():
matches = Pattern('take apple').match(['take', 'tortoise'])
assert matches is None
def test_multiple_words_mismatch_length():
matches = Pattern('take apple').match(['take', 'golden', 'apple'])
assert matches is None
def test_capturing_match():
matches = Pattern('take ITEM').match(['take', 'apple'])
assert matches == {'item': 'apple'}
def test_multiple_captures():
pat = Pattern('give ITEM to PERSON')
matches = pat.match(['give', 'apple', 'to', 'wizard'])
assert matches == {'item': 'apple', 'person': 'wizard'}
def test_capturing_multiword():
matches = Pattern('take ITEM').match(['take', 'golden', 'apple'])
assert matches == {'item': 'golden apple'}
def test_multiple_multiword_captures():
pat = Pattern('give ITEM to PERSON')
matches = pat.match(['give', 'golden', 'apple', 'to', 'evil', 'wizard'])
assert matches == {'item': 'golden apple', 'person': 'evil wizard'}
def test_word_combinations():
combos = Pattern.word_combinations(have=3, placeholders=2)
assert list(combos) == [
(2, 1),
(1, 2)
]
def test_word_combinations_2():
combos = Pattern.word_combinations(have=4, placeholders=2)
assert list(combos) == [
(3, 1),
(2, 2),
(1, 3)
]
def test_word_combinations_3():
combos = Pattern.word_combinations(have=4, placeholders=3)
assert list(combos) == [
(2, 1, 1),
(1, 2, 1),
(1, 1, 2)
]
def test_word_combinations_4():
combos = Pattern.word_combinations(have=5, placeholders=3)
assert list(combos) == [
(3, 1, 1),
(2, 2, 1),
(2, 1, 2),
(1, 3, 1),
(1, 2, 2),
(1, 1, 3),
]
def test_register():
called = False
@when('north')
def func():
nonlocal called
called = True
print(adventurelib.commands)
_handle_command('north')
assert called is True
def test_register_args():
args = None
@when('north', dir='north')
def func(dir):
nonlocal args
args = [dir]
_handle_command('north')
assert args == ['north']
@pytest.mark.parametrize('ctx,expected', [
(None, 'north'),
('confused', 'south'),
('confused.really', 'cauliflower'),
])
def test_register_context(ctx, expected):
"""The result of a command changes in different contexts."""
cmd = None
# Register out of order to test tie-breaking by context nesting depth
@when('north', dir='north')
@when('north', dir='cauliflower', context='confused.really')
@when('north', dir='south', context='confused')
def func(dir):
nonlocal cmd
cmd = dir
with active_context(ctx):
_handle_command('north')
assert cmd == expected
def test_register_match():
args = None
@when('hit TARGET with WEAPON', verb='hit')
def func(target, weapon, verb):
nonlocal args
args = [target, weapon, verb]
_handle_command('hit dragon with glass sword')
assert args == ['dragon', 'glass sword', 'hit']
def say_at_width(width, msg):
buf = StringIO()
with patch('adventurelib.get_terminal_size', return_value=(width, 24)):
with redirect_stdout(buf):
say(msg)
return buf.getvalue()
def test_say_room():
"""saw() will format input as strings."""
r = Room('You are standing in a hallway.')
buf = StringIO()
with redirect_stdout(buf):
say(r)
assert buf.getvalue() == 'You are standing in a hallway.\n'
def test_say_wrap():
"""The say() function will print output wrapped to the terminal width."""
out = say_at_width(40, """
This is a long sentence that the say command will wrap.
""")
assert out == (
"This is a long sentence that the say\n"
"command will wrap.\n"
)
def test_say_wrap2():
"""The say() function will print output wrapped to the terminal width."""
out = say_at_width(20, """
This is a long sentence that the say command will wrap.
""")
assert out == (
"This is a long\n"
"sentence that the\n"
"say command will\n"
"wrap.\n"
)
def test_say_multiple_paragraph():
"""Paragraphs separated by a blank line will be wrapped separately."""
out = say_at_width(40, """
This is a long sentence that the say command will wrap.
And this is a second paragraph that is separately wrapped.
""")
assert out == (
"This is a long sentence that the say\n"
"command will wrap.\n"
"\n"
"And this is a second paragraph that is\n"
"separately wrapped.\n"
)
def test_say_multiline_paragraph():
"""We can wrap a sentence written in multiple input lines."""
out = say_at_width(40, """
This is a long sentence that the say command will wrap,
and this clause is indented to match.
""")
assert out == (
"This is a long sentence that the say\n"
"command will wrap, and this clause is\n"
"indented to match.\n"
)
@patch('random.randrange', return_value=0)
def test_bag_get_random(randrange):
"""We can select an item from a bag at random."""
bag = Bag(map(Item, 'abc'))
assert bag.get_random() == list(bag)[0]
randrange.assert_called_once_with(3)
@patch('random.randrange', return_value=1)
def test_bag_get_random2(randrange):
"""We can select an item from a bag at random."""
bag = Bag(map(Item, 'abc'))
assert bag.get_random() == list(bag)[1]
randrange.assert_called_once_with(3)
def test_empty_bag_get_random():
"""Choosing from an empty bag returns None."""
bag = Bag()
assert bag.get_random() is None
@patch('random.randrange', return_value=0)
def test_bag_take_random(randrange):
"""We can select and remove an item from a bag at random."""
bag = Bag(map(Item, 'abc'))
items = list(bag)
assert bag.take_random() == items[0]
assert bag == Bag(items[1:])
randrange.assert_called_once_with(3)
def test_bag_find():
"""We can find items in a bag by name, case insensitively."""
name, *aliases = ['Name', 'UPPER ALIAS', 'lower alias']
named_item = Item(name, *aliases)
appellative_item = Item('appellation', *aliases)
nameless_item = Item('noname', 'none at all')
bag = Bag({named_item, appellative_item, nameless_item})
assert bag.find('name') is named_item
assert bag.find('Name') is named_item
assert bag.find('NAME') is named_item
assert bag.find('appellation') is appellative_item
assert bag.find('upper alias') in {named_item, appellative_item}
assert bag.find('LOWER ALIAS') in {named_item, appellative_item}
assert not bag.find('other')
@pytest.mark.parametrize(
'current_context',
['foo', 'foo.bar', 'foo.bar.baz']
)
def test_match_context(current_context):
"""We can match contexts."""
assert adventurelib._match_context('foo', current_context)
@pytest.mark.parametrize(
'current_context',
[None, 'bar', 'bar.foo'],
)
def test_no_match_context(current_context):
"""A context doesn't match if it is not "within" the pattern context."""
assert not adventurelib._match_context('foo', current_context)
def test_match_context_none():
"""The current context matches if the pattern context is None."""
assert adventurelib._match_context(None, 'foo.bar')
@pytest.mark.parametrize(
'context',
[None, 'foo', 'foo.bar']
)
def test_validate_context(context):
"""We can validate valid contexts."""
adventurelib._validate_context(context)
def test_validate_context_empty():
"""An empty string is not a valid context."""
with pytest.raises(ValueError) as exc:
adventurelib._validate_context("")
assert str(exc.value) == "Context '' may not be empty"
def test_validate_context_start_dot():
"""A context that starts with . is invalid."""
with pytest.raises(ValueError) as exc:
adventurelib._validate_context(".foo")
assert str(exc.value) == "Context '.foo' may not start with ."
def test_validate_context_end_dot():
"""A context that ends with . is invalid."""
with pytest.raises(ValueError) as exc:
adventurelib._validate_context("foo.bar.")
assert str(exc.value) == "Context 'foo.bar.' may not end with ."
def test_validate_context_double_dot():
"""A context that contains .. is invalid."""
with pytest.raises(ValueError) as exc:
adventurelib._validate_context("foo..bar")
assert str(exc.value) == "Context 'foo..bar' may not contain .."
def test_validate_context_wrong():
"""A context that is wrong in various ways has a custom message."""
with pytest.raises(ValueError) as exc:
adventurelib._validate_context(".foo.bar.")
err = str(exc.value)
assert err == "Context '.foo.bar.' may not start with . or end with ."
def test_validate_pattern_double_ident():
"""A pattern with identifier used twice is incorrect"""
with pytest.raises(adventurelib.InvalidCommand) as exc:
Pattern("take I with I")
err = str(exc.value)
assert err == "Invalid command 'take I with I'"\
" Identifiers may only be used once"