Mercurial > parpg-core
view tests/test_dialogueprocessor.py @ 12:d60f1dab8469
Fixed resource path dependencies issue that caused PARPG to crash on start.
* PARPG should now run without issue (system installation not tested).
* Utilized FIFE's VFS module to remove path dependencies from most PARPG modules.
* The new parpg.vfs module is a singleton with a single global variable, VFS, which is a reference to the global VFS instance. Although a singleton is not ideal it should be replaced once PARPG's core code is refactored.
* The parpg.vfs singleton is initialized in the parpg.applicaiton.PARPGApplication class with the absolute path to the data directory via the parpg.settings module and corresponding configuration file.
* A new DataPath entry was added to the default system configuration file template under the [parpg] section to support the new parpg.vfs module.
* Updated the parpg-assets subrepo to revision 3 to fix some dialog file format issues (for details see commit message for parpg-assets).
* Fixed a few bugs in the parpg.dialogueparsers.YAMLDialogueParser class related to exception handling.
author | M. George Hansen <technopolitica@gmail.com> |
---|---|
date | Mon, 06 Jun 2011 15:56:14 -1000 |
parents | 1fd2201f5c36 |
children |
line wrap: on
line source
#!/usr/bin/env python # # This file is part of PARPG. # # PARPG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # PARPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PARPG. If not, see <http://www.gnu.org/licenses/>. try: # Python 2.6 import unittest2 as unittest except: # Python 2.7 import unittest from parpg.common.utils import dedent_chomp from parpg.dialogueprocessor import DialogueProcessor # NOTE Technomage 2010-12-08: Using the dialogue data structures might be a # violation of unit test isolation, but ultimately they are just simple # data structures that don't require much testing of their own so I feel # that it isn't a mistake to use them. from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse, DialogueGreeting) class MockDialogueAction(object): keyword = 'mock_action' def __init__(self, *args, **kwargs): self.arguments = (args, kwargs) self.was_called = False self.call_arguments = [] def __call__(self, game_state): self.was_called = True self.call_arguments = ((game_state,), {}) class TestDialogueProcessor(unittest.TestCase): """Base class for tests of the L{DialogueProcessor} class.""" def assertStateEqual(self, object_, **state): """ Assert that an object's attributes match an expected state. @param """ object_dict = {} for key in state.keys(): if (hasattr(object_, key)): actual_value = getattr(object_, key) object_dict[key] = actual_value self.assertDictContainsSubset(state, object_dict) def setUp(self): self.npc_id = 'mr_npc' self.dialogue = Dialogue( npc_name='Mr. NPC', avatar_path='/some/path', default_greeting=DialogueSection( id_='greeting', text='This is the root dialogue section.', actions=[ MockDialogueAction('foo'), ], responses=[ DialogueResponse( text='A response.', next_section_id='another_section', ), DialogueResponse( text='A conditional response evaluated to True.', condition='True', actions=[ MockDialogueAction('foo'), ], next_section_id='another_section', ), DialogueResponse( text='A conditional response evaluated to False.', condition='False', next_section_id='another_section', ), DialogueResponse( text='A response that ends the dialogue.', next_section_id='end', ), ], ), greetings=[ DialogueGreeting( id_='alternative_greeting', condition='use_alternative_root is True', text='This is an alternate root section.', responses=[ DialogueResponse( text='End dialogue.', next_section_id='end', ), ], ), ], sections=[ DialogueSection( id_='another_section', text='This is another dialogue section.', responses=[ DialogueResponse( text='End dialogue.', next_section_id='end', ), ], ), ] ) self.game_state = {'use_alternative_root': False} class TestInitiateDialogue(TestDialogueProcessor): """Tests of the L{DialogueProcessor.initiateDialogue} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue = Dialogue( npc_name='Mr. NPC', avatar_path='/some/path', default_greeting=DialogueSection( id_='greeting', text='This is the one (and only) dialogue section.', responses=[ DialogueResponse( text=dedent_chomp(''' A response that moves the dialogue to another_section. '''), next_section_id='another_section' ), DialogueResponse( text='A response that ends the dialogue.', next_section_id='end', ), ], ), sections=[ DialogueSection( id_='another_section', text='This is another section.', responses=[ DialogueResponse( text='A response that ends the dialogue', next_section_id='end', ) ], ), ] ) self.dialogue_processor = DialogueProcessor(self.dialogue, {}) def testSetsState(self): """initiateDialogue correctly sets DialogueProcessor state""" dialogue_processor = self.dialogue_processor dialogue_processor.initiateDialogue() # Default root dialogue section should have been pushed onto the stack. default_greeting = self.dialogue.default_greeting self.assertStateEqual(dialogue_processor, in_dialogue=True, dialogue=self.dialogue, dialogue_section_stack=[default_greeting]) def testEndsExistingDialogue(self): """initiateDialogue ends a previously initiated dialogue""" dialogue_processor = self.dialogue_processor dialogue_processor.initiateDialogue() valid_responses = dialogue_processor.continueDialogue() dialogue_processor.reply(valid_responses[0]) # Sanity check. assert dialogue_processor.in_dialogue dialogue_processor.initiateDialogue() default_greeting = self.dialogue.default_greeting self.assertStateEqual(dialogue_processor, in_dialogue=True, dialogue=self.dialogue, dialogue_section_stack=[default_greeting]) class TestEndDialogue(TestDialogueProcessor): """Tests of the L{DialogueProcessor.endDialogue} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) def testResetsState(self): """endDialogue correctly resets DialogueProcessor state""" dialogue_processor = self.dialogue_processor # Case: No dialogue initiated. assert not dialogue_processor.in_dialogue, \ 'assumption that dialogue_processor has not initiated a dialogue '\ 'violated' self.assertStateEqual(dialogue_processor, in_dialogue=False, dialogue=self.dialogue, dialogue_section_stack=[]) # Case: Dialogue previously initiated. dialogue_processor.initiateDialogue() assert dialogue_processor.in_dialogue, \ 'assumption that dialogue_processor initiated a dialogue violated' dialogue_processor.endDialogue() self.assertStateEqual(dialogue_processor, in_dialogue=False, dialogue=self.dialogue, dialogue_section_stack=[]) class TestContinueDialogue(TestDialogueProcessor): """Tests of the L{DialogueProcessor.continueDialogue} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) self.dialogue_processor.initiateDialogue() self.dialogue_action = \ self.dialogue.default_greeting.actions[0] def testRunsDialogueActions(self): """continueDialogue executes all DialogueActions""" dialogue_processor = self.dialogue_processor dialogue_processor.continueDialogue() self.assertTrue(self.dialogue_action.was_called) expected_tuple = ((self.game_state,), {}) self.assertTupleEqual(expected_tuple, self.dialogue_action.call_arguments) def testReturnsValidResponses(self): """continueDialogue returns list of valid DialogueResponses""" dialogue_processor = self.dialogue_processor valid_responses = \ dialogue_processor.dialogue_section_stack[0].responses valid_responses.pop(2) # Sanity check, all "valid" responses should have a condition that # evaluates to True. for response in valid_responses: if (response.condition is not None): result = eval(response.condition, self.game_state, {}) self.assertTrue(result) responses = dialogue_processor.continueDialogue() self.assertItemsEqual(responses, valid_responses) class TestGetRootDialogueSection(TestDialogueProcessor): """Tests of the L{DialogueProcessor.getDialogueGreeting} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor( self.dialogue, {'use_alternative_root': True} ) self.dialogue_processor.initiateDialogue() def testReturnsCorrectDialogueSection(self): """getDialogueGreeting returns first section with true condition""" dialogue_processor = self.dialogue_processor dialogue = self.dialogue root_dialogue_section = dialogue_processor.getDialogueGreeting() expected_dialogue_section = dialogue.greetings[0] self.assertEqual(root_dialogue_section, expected_dialogue_section) class TestGetCurrentDialogueSection(TestDialogueProcessor): """Tests of the L{DialogueProcessor.getCurrentDialogueSection} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) self.dialogue_processor.initiateDialogue() def testReturnsCorrectDialogueSection(self): """getCurrentDialogueSection returns section at top of stack""" dialogue_processor = self.dialogue_processor expected_dialogue_section = self.dialogue.default_greeting actual_dialogue_section = \ dialogue_processor.getCurrentDialogueSection() self.assertEqual(expected_dialogue_section, actual_dialogue_section) class TestRunDialogueActions(TestDialogueProcessor): """Tests of the L{DialogueProcessor.runDialogueActions} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) self.dialogue_processor.initiateDialogue() self.dialogue_section = DialogueSection( id_='some_section', text='Test dialogue section.', actions=[ MockDialogueAction('foo'), ], ) self.dialogue_response = DialogueResponse( text='A response.', actions=[ MockDialogueAction('foo'), ], next_section_id='end', ) def testExecutesDialogueActions(self): """runDialogueActions correctly executes DialogueActions""" dialogue_processor = self.dialogue_processor # Case: DialogueSection dialogue_processor.runDialogueActions(self.dialogue_section) dialogue_section_action = self.dialogue_section.actions[0] self.assertTrue(dialogue_section_action.was_called) expected_call_args = ((self.game_state,), {}) self.assertTupleEqual(expected_call_args, dialogue_section_action.call_arguments) # Case: DialogueResponse dialogue_processor.runDialogueActions(self.dialogue_response) dialogue_response_action = self.dialogue_response.actions[0] self.assertTrue(dialogue_response_action.was_called) self.assertTupleEqual(expected_call_args, dialogue_response_action.call_arguments) class TestGetValidResponses(TestDialogueProcessor): """Tests of the L{DialogueProcessor.getValidResponses} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) self.dialogue_processor.initiateDialogue() def testReturnsValidResponses(self): """getValidResponses returns list of valid DialogueResponses""" dialogue_processor = self.dialogue_processor valid_responses = \ dialogue_processor.dialogue_section_stack[0].responses valid_responses.pop(2) # Sanity check, all "valid" responses should have a condition that # evaluates to True. for response in valid_responses: if (response.condition is not None): result = eval(response.condition, {}, {}) self.assertTrue(result) responses = dialogue_processor.continueDialogue() self.assertItemsEqual(responses, valid_responses) class TestReply(TestDialogueProcessor): """Tests of the L{DialogueProcessor.reply} method.""" def setUp(self): TestDialogueProcessor.setUp(self) self.dialogue_processor = DialogueProcessor(self.dialogue, self.game_state) self.response = self.dialogue.default_greeting.responses[1] self.ending_response = \ self.dialogue.default_greeting.responses[3] def testRaisesExceptionWhenNotInitiated(self): """reply raises exception when called before initiateDialogue""" dialogue_processor = self.dialogue_processor # Sanity check: A dialogue must not have been initiated beforehand. self.assertFalse(dialogue_processor.in_dialogue) with self.assertRaisesRegexp(RuntimeError, r'initiateDialogue'): dialogue_processor.reply(self.response) def testExecutesDialogueActions(self): """reply correctly executes DialogueActions in a DialogueResponse""" dialogue_processor = self.dialogue_processor dialogue_processor.initiateDialogue() dialogue_processor.reply(self.response) dialogue_action = self.response.actions[0] self.assertTrue(dialogue_action.was_called) expected_call_args = ((self.game_state,), {}) self.assertTupleEqual(expected_call_args, dialogue_action.call_arguments) def testJumpsToCorrectSection(self): """reply pushes section specified by response onto stack""" dialogue_processor = self.dialogue_processor dialogue_processor.initiateDialogue() # Sanity check: Test response's next_section_id attribute must be refer # to a valid DialogueSection in the test Dialogue. self.assertIn(self.response.next_section_id, self.dialogue.sections.keys()) dialogue_processor.reply(self.response) greeting = self.dialogue.default_greeting next_section = self.dialogue.sections[self.response.next_section_id] self.assertStateEqual( dialogue_processor, in_dialogue=True, dialogue=self.dialogue, dialogue_section_stack=[greeting, next_section], ) def testCorrectlyEndsDialogue(self): """reply ends dialogue when DialogueResponse specifies 'end'""" dialogue_processor = self.dialogue_processor dialogue_processor.initiateDialogue() # Sanity check: Test response must have a next_section_id of 'end'. self.assertEqual(self.ending_response.next_section_id, 'end') dialogue_processor.reply(self.ending_response) self.assertStateEqual(dialogue_processor, in_dialogue=False, dialogue=self.dialogue, dialogue_section_stack=[]) if __name__ == "__main__": unittest.main()