source: branches/active/character_customization/game/tests/test_dialogueprocessor.py @ 736

Revision 736, 17.0 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites:

  • renamed scripts package to parpg
  • renamed parpg module to application
  • removed packaging and other related files (kept locally for reference, will reintroduce similar scripts to resolve bug #275
  • updated all import statements to respect changes above
  • Property svn:eol-style set to native
  • Property svn:executable set to *
Line 
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/>.
17try:
18    # Python 2.6
19    import unittest2 as unittest
20except:
21    # Python 2.7
22    import unittest
23
24from parpg.common.utils import dedent_chomp
25from 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.
30from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse,
31    DialogueGreeting)
32
33class 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
46class 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
125class 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
190class 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
217class 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
252class 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
271class 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
288class 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
328class 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
352class 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
411if __name__ == "__main__":
412    unittest.main()
Note: See TracBrowser for help on using the repository browser.