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()