Mercurial > parpg-core
comparison tests/test_dialogueprocessor.py @ 0:1fd2201f5c36
Initial commit of parpg-core.
author | M. George Hansen <technopolitica@gmail.com> |
---|---|
date | Sat, 14 May 2011 01:12:35 -0700 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1fd2201f5c36 |
---|---|
1 #!/usr/bin/env python | |
2 # | |
3 # This file is part of PARPG. | |
4 # | |
5 # PARPG is free software: you can redistribute it and/or modify | |
6 # it under the terms of the GNU General Public License as published by | |
7 # the Free Software Foundation, either version 3 of the License, or | |
8 # (at your option) any later version. | |
9 # | |
10 # PARPG is distributed in the hope that it will be useful, | |
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 # GNU General Public License for more details. | |
14 # | |
15 # You should have received a copy of the GNU General Public License | |
16 # along with PARPG. If not, see <http://www.gnu.org/licenses/>. | |
17 try: | |
18 # Python 2.6 | |
19 import unittest2 as unittest | |
20 except: | |
21 # Python 2.7 | |
22 import unittest | |
23 | |
24 from parpg.common.utils import dedent_chomp | |
25 from parpg.dialogueprocessor import DialogueProcessor | |
26 # NOTE Technomage 2010-12-08: Using the dialogue data structures might be a | |
27 # violation of unit test isolation, but ultimately they are just simple | |
28 # data structures that don't require much testing of their own so I feel | |
29 # that it isn't a mistake to use them. | |
30 from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse, | |
31 DialogueGreeting) | |
32 | |
33 class MockDialogueAction(object): | |
34 keyword = 'mock_action' | |
35 | |
36 def __init__(self, *args, **kwargs): | |
37 self.arguments = (args, kwargs) | |
38 self.was_called = False | |
39 self.call_arguments = [] | |
40 | |
41 def __call__(self, game_state): | |
42 self.was_called = True | |
43 self.call_arguments = ((game_state,), {}) | |
44 | |
45 | |
46 class TestDialogueProcessor(unittest.TestCase): | |
47 """Base class for tests of the L{DialogueProcessor} class.""" | |
48 def assertStateEqual(self, object_, **state): | |
49 """ | |
50 Assert that an object's attributes match an expected state. | |
51 | |
52 @param | |
53 """ | |
54 object_dict = {} | |
55 for key in state.keys(): | |
56 if (hasattr(object_, key)): | |
57 actual_value = getattr(object_, key) | |
58 object_dict[key] = actual_value | |
59 self.assertDictContainsSubset(state, object_dict) | |
60 | |
61 def setUp(self): | |
62 self.npc_id = 'mr_npc' | |
63 self.dialogue = Dialogue( | |
64 npc_name='Mr. NPC', | |
65 avatar_path='/some/path', | |
66 default_greeting=DialogueSection( | |
67 id_='greeting', | |
68 text='This is the root dialogue section.', | |
69 actions=[ | |
70 MockDialogueAction('foo'), | |
71 ], | |
72 responses=[ | |
73 DialogueResponse( | |
74 text='A response.', | |
75 next_section_id='another_section', | |
76 ), | |
77 DialogueResponse( | |
78 text='A conditional response evaluated to True.', | |
79 condition='True', | |
80 actions=[ | |
81 MockDialogueAction('foo'), | |
82 ], | |
83 next_section_id='another_section', | |
84 ), | |
85 DialogueResponse( | |
86 text='A conditional response evaluated to False.', | |
87 condition='False', | |
88 next_section_id='another_section', | |
89 ), | |
90 DialogueResponse( | |
91 text='A response that ends the dialogue.', | |
92 next_section_id='end', | |
93 ), | |
94 ], | |
95 ), | |
96 greetings=[ | |
97 DialogueGreeting( | |
98 id_='alternative_greeting', | |
99 condition='use_alternative_root is True', | |
100 text='This is an alternate root section.', | |
101 responses=[ | |
102 DialogueResponse( | |
103 text='End dialogue.', | |
104 next_section_id='end', | |
105 ), | |
106 ], | |
107 ), | |
108 ], | |
109 sections=[ | |
110 DialogueSection( | |
111 id_='another_section', | |
112 text='This is another dialogue section.', | |
113 responses=[ | |
114 DialogueResponse( | |
115 text='End dialogue.', | |
116 next_section_id='end', | |
117 ), | |
118 ], | |
119 ), | |
120 ] | |
121 ) | |
122 self.game_state = {'use_alternative_root': False} | |
123 | |
124 | |
125 class TestInitiateDialogue(TestDialogueProcessor): | |
126 """Tests of the L{DialogueProcessor.initiateDialogue} method.""" | |
127 def setUp(self): | |
128 TestDialogueProcessor.setUp(self) | |
129 self.dialogue = Dialogue( | |
130 npc_name='Mr. NPC', | |
131 avatar_path='/some/path', | |
132 default_greeting=DialogueSection( | |
133 id_='greeting', | |
134 text='This is the one (and only) dialogue section.', | |
135 responses=[ | |
136 DialogueResponse( | |
137 text=dedent_chomp(''' | |
138 A response that moves the dialogue to | |
139 another_section. | |
140 '''), | |
141 next_section_id='another_section' | |
142 ), | |
143 DialogueResponse( | |
144 text='A response that ends the dialogue.', | |
145 next_section_id='end', | |
146 ), | |
147 ], | |
148 ), | |
149 sections=[ | |
150 DialogueSection( | |
151 id_='another_section', | |
152 text='This is another section.', | |
153 responses=[ | |
154 DialogueResponse( | |
155 text='A response that ends the dialogue', | |
156 next_section_id='end', | |
157 ) | |
158 ], | |
159 ), | |
160 ] | |
161 ) | |
162 self.dialogue_processor = DialogueProcessor(self.dialogue, {}) | |
163 | |
164 def testSetsState(self): | |
165 """initiateDialogue correctly sets DialogueProcessor state""" | |
166 dialogue_processor = self.dialogue_processor | |
167 dialogue_processor.initiateDialogue() | |
168 | |
169 # Default root dialogue section should have been pushed onto the stack. | |
170 default_greeting = self.dialogue.default_greeting | |
171 self.assertStateEqual(dialogue_processor, in_dialogue=True, | |
172 dialogue=self.dialogue, | |
173 dialogue_section_stack=[default_greeting]) | |
174 | |
175 def testEndsExistingDialogue(self): | |
176 """initiateDialogue ends a previously initiated dialogue""" | |
177 dialogue_processor = self.dialogue_processor | |
178 dialogue_processor.initiateDialogue() | |
179 valid_responses = dialogue_processor.continueDialogue() | |
180 dialogue_processor.reply(valid_responses[0]) | |
181 | |
182 # Sanity check. | |
183 assert dialogue_processor.in_dialogue | |
184 dialogue_processor.initiateDialogue() | |
185 default_greeting = self.dialogue.default_greeting | |
186 self.assertStateEqual(dialogue_processor, in_dialogue=True, | |
187 dialogue=self.dialogue, | |
188 dialogue_section_stack=[default_greeting]) | |
189 | |
190 class TestEndDialogue(TestDialogueProcessor): | |
191 """Tests of the L{DialogueProcessor.endDialogue} method.""" | |
192 def setUp(self): | |
193 TestDialogueProcessor.setUp(self) | |
194 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
195 self.game_state) | |
196 | |
197 def testResetsState(self): | |
198 """endDialogue correctly resets DialogueProcessor state""" | |
199 dialogue_processor = self.dialogue_processor | |
200 # Case: No dialogue initiated. | |
201 assert not dialogue_processor.in_dialogue, \ | |
202 'assumption that dialogue_processor has not initiated a dialogue '\ | |
203 'violated' | |
204 self.assertStateEqual(dialogue_processor, in_dialogue=False, | |
205 dialogue=self.dialogue, | |
206 dialogue_section_stack=[]) | |
207 # Case: Dialogue previously initiated. | |
208 dialogue_processor.initiateDialogue() | |
209 assert dialogue_processor.in_dialogue, \ | |
210 'assumption that dialogue_processor initiated a dialogue violated' | |
211 dialogue_processor.endDialogue() | |
212 self.assertStateEqual(dialogue_processor, in_dialogue=False, | |
213 dialogue=self.dialogue, | |
214 dialogue_section_stack=[]) | |
215 | |
216 | |
217 class TestContinueDialogue(TestDialogueProcessor): | |
218 """Tests of the L{DialogueProcessor.continueDialogue} method.""" | |
219 def setUp(self): | |
220 TestDialogueProcessor.setUp(self) | |
221 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
222 self.game_state) | |
223 self.dialogue_processor.initiateDialogue() | |
224 self.dialogue_action = \ | |
225 self.dialogue.default_greeting.actions[0] | |
226 | |
227 def testRunsDialogueActions(self): | |
228 """continueDialogue executes all DialogueActions""" | |
229 dialogue_processor = self.dialogue_processor | |
230 dialogue_processor.continueDialogue() | |
231 self.assertTrue(self.dialogue_action.was_called) | |
232 expected_tuple = ((self.game_state,), {}) | |
233 self.assertTupleEqual(expected_tuple, | |
234 self.dialogue_action.call_arguments) | |
235 | |
236 def testReturnsValidResponses(self): | |
237 """continueDialogue returns list of valid DialogueResponses""" | |
238 dialogue_processor = self.dialogue_processor | |
239 valid_responses = \ | |
240 dialogue_processor.dialogue_section_stack[0].responses | |
241 valid_responses.pop(2) | |
242 # Sanity check, all "valid" responses should have a condition that | |
243 # evaluates to True. | |
244 for response in valid_responses: | |
245 if (response.condition is not None): | |
246 result = eval(response.condition, self.game_state, {}) | |
247 self.assertTrue(result) | |
248 responses = dialogue_processor.continueDialogue() | |
249 self.assertItemsEqual(responses, valid_responses) | |
250 | |
251 | |
252 class TestGetRootDialogueSection(TestDialogueProcessor): | |
253 """Tests of the L{DialogueProcessor.getDialogueGreeting} method.""" | |
254 def setUp(self): | |
255 TestDialogueProcessor.setUp(self) | |
256 self.dialogue_processor = DialogueProcessor( | |
257 self.dialogue, | |
258 {'use_alternative_root': True} | |
259 ) | |
260 self.dialogue_processor.initiateDialogue() | |
261 | |
262 def testReturnsCorrectDialogueSection(self): | |
263 """getDialogueGreeting returns first section with true condition""" | |
264 dialogue_processor = self.dialogue_processor | |
265 dialogue = self.dialogue | |
266 root_dialogue_section = dialogue_processor.getDialogueGreeting() | |
267 expected_dialogue_section = dialogue.greetings[0] | |
268 self.assertEqual(root_dialogue_section, expected_dialogue_section) | |
269 | |
270 | |
271 class TestGetCurrentDialogueSection(TestDialogueProcessor): | |
272 """Tests of the L{DialogueProcessor.getCurrentDialogueSection} method.""" | |
273 def setUp(self): | |
274 TestDialogueProcessor.setUp(self) | |
275 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
276 self.game_state) | |
277 self.dialogue_processor.initiateDialogue() | |
278 | |
279 def testReturnsCorrectDialogueSection(self): | |
280 """getCurrentDialogueSection returns section at top of stack""" | |
281 dialogue_processor = self.dialogue_processor | |
282 expected_dialogue_section = self.dialogue.default_greeting | |
283 actual_dialogue_section = \ | |
284 dialogue_processor.getCurrentDialogueSection() | |
285 self.assertEqual(expected_dialogue_section, actual_dialogue_section) | |
286 | |
287 | |
288 class TestRunDialogueActions(TestDialogueProcessor): | |
289 """Tests of the L{DialogueProcessor.runDialogueActions} method.""" | |
290 def setUp(self): | |
291 TestDialogueProcessor.setUp(self) | |
292 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
293 self.game_state) | |
294 self.dialogue_processor.initiateDialogue() | |
295 self.dialogue_section = DialogueSection( | |
296 id_='some_section', | |
297 text='Test dialogue section.', | |
298 actions=[ | |
299 MockDialogueAction('foo'), | |
300 ], | |
301 ) | |
302 self.dialogue_response = DialogueResponse( | |
303 text='A response.', | |
304 actions=[ | |
305 MockDialogueAction('foo'), | |
306 ], | |
307 next_section_id='end', | |
308 ) | |
309 | |
310 def testExecutesDialogueActions(self): | |
311 """runDialogueActions correctly executes DialogueActions""" | |
312 dialogue_processor = self.dialogue_processor | |
313 # Case: DialogueSection | |
314 dialogue_processor.runDialogueActions(self.dialogue_section) | |
315 dialogue_section_action = self.dialogue_section.actions[0] | |
316 self.assertTrue(dialogue_section_action.was_called) | |
317 expected_call_args = ((self.game_state,), {}) | |
318 self.assertTupleEqual(expected_call_args, | |
319 dialogue_section_action.call_arguments) | |
320 # Case: DialogueResponse | |
321 dialogue_processor.runDialogueActions(self.dialogue_response) | |
322 dialogue_response_action = self.dialogue_response.actions[0] | |
323 self.assertTrue(dialogue_response_action.was_called) | |
324 self.assertTupleEqual(expected_call_args, | |
325 dialogue_response_action.call_arguments) | |
326 | |
327 | |
328 class TestGetValidResponses(TestDialogueProcessor): | |
329 """Tests of the L{DialogueProcessor.getValidResponses} method.""" | |
330 def setUp(self): | |
331 TestDialogueProcessor.setUp(self) | |
332 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
333 self.game_state) | |
334 self.dialogue_processor.initiateDialogue() | |
335 | |
336 def testReturnsValidResponses(self): | |
337 """getValidResponses returns list of valid DialogueResponses""" | |
338 dialogue_processor = self.dialogue_processor | |
339 valid_responses = \ | |
340 dialogue_processor.dialogue_section_stack[0].responses | |
341 valid_responses.pop(2) | |
342 # Sanity check, all "valid" responses should have a condition that | |
343 # evaluates to True. | |
344 for response in valid_responses: | |
345 if (response.condition is not None): | |
346 result = eval(response.condition, {}, {}) | |
347 self.assertTrue(result) | |
348 responses = dialogue_processor.continueDialogue() | |
349 self.assertItemsEqual(responses, valid_responses) | |
350 | |
351 | |
352 class TestReply(TestDialogueProcessor): | |
353 """Tests of the L{DialogueProcessor.reply} method.""" | |
354 def setUp(self): | |
355 TestDialogueProcessor.setUp(self) | |
356 self.dialogue_processor = DialogueProcessor(self.dialogue, | |
357 self.game_state) | |
358 self.response = self.dialogue.default_greeting.responses[1] | |
359 self.ending_response = \ | |
360 self.dialogue.default_greeting.responses[3] | |
361 | |
362 def testRaisesExceptionWhenNotInitiated(self): | |
363 """reply raises exception when called before initiateDialogue""" | |
364 dialogue_processor = self.dialogue_processor | |
365 # Sanity check: A dialogue must not have been initiated beforehand. | |
366 self.assertFalse(dialogue_processor.in_dialogue) | |
367 with self.assertRaisesRegexp(RuntimeError, r'initiateDialogue'): | |
368 dialogue_processor.reply(self.response) | |
369 | |
370 def testExecutesDialogueActions(self): | |
371 """reply correctly executes DialogueActions in a DialogueResponse""" | |
372 dialogue_processor = self.dialogue_processor | |
373 dialogue_processor.initiateDialogue() | |
374 dialogue_processor.reply(self.response) | |
375 dialogue_action = self.response.actions[0] | |
376 self.assertTrue(dialogue_action.was_called) | |
377 expected_call_args = ((self.game_state,), {}) | |
378 self.assertTupleEqual(expected_call_args, | |
379 dialogue_action.call_arguments) | |
380 | |
381 def testJumpsToCorrectSection(self): | |
382 """reply pushes section specified by response onto stack""" | |
383 dialogue_processor = self.dialogue_processor | |
384 dialogue_processor.initiateDialogue() | |
385 # Sanity check: Test response's next_section_id attribute must be refer | |
386 # to a valid DialogueSection in the test Dialogue. | |
387 self.assertIn(self.response.next_section_id, | |
388 self.dialogue.sections.keys()) | |
389 dialogue_processor.reply(self.response) | |
390 greeting = self.dialogue.default_greeting | |
391 next_section = self.dialogue.sections[self.response.next_section_id] | |
392 self.assertStateEqual( | |
393 dialogue_processor, | |
394 in_dialogue=True, | |
395 dialogue=self.dialogue, | |
396 dialogue_section_stack=[greeting, next_section], | |
397 ) | |
398 | |
399 def testCorrectlyEndsDialogue(self): | |
400 """reply ends dialogue when DialogueResponse specifies 'end'""" | |
401 dialogue_processor = self.dialogue_processor | |
402 dialogue_processor.initiateDialogue() | |
403 # Sanity check: Test response must have a next_section_id of 'end'. | |
404 self.assertEqual(self.ending_response.next_section_id, 'end') | |
405 dialogue_processor.reply(self.ending_response) | |
406 self.assertStateEqual(dialogue_processor, in_dialogue=False, | |
407 dialogue=self.dialogue, | |
408 dialogue_section_stack=[]) | |
409 | |
410 | |
411 if __name__ == "__main__": | |
412 unittest.main() |