Changeset 685


Ignore:
Timestamp:
12/12/10 03:58:45 (8 years ago)
Author:
technomage
Message:

Patch by Technomage

  • Replaced the test_dialogue.py testsuite with the test_dialogueprocessor.py testsuite and added/updated unittest TestCases? for all public methods of the DialogueProcessor? class.
  • DialogueProcessor? was refactored to be much more liberal about raising exceptions instead of silently handling errors; the code documentation has been updated to describe this new behavior.
  • Refactored exception-handling code in the dialogueparsers.py gamemodel.py modules related to the dialogue engine to deal with the changes to the DialogueProcessor? class.
  • Added a new method to the Dialogue class, getRootSection, which returns the root DialogueSection? for the a particular dialogue; also added some error checking code for inputs to the constructor.
  • Updated the run_tests.py script to import config.py and attempt to read the FIFE Python module path from it, just like run.py.
Location:
trunk/game
Files:
5 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/game/run_tests.py

    r674 r685  
    1717import sys, os, unittest 
    1818 
     19#Check if config.py exists. Get 'fife_path' from config 
     20try: 
     21    import config 
     22    sys.path.append(config.fife_path) 
     23except: 
     24    pass 
     25 
    1926def _jp(path): 
    2027    return os.path.sep.join(path.split('/')) 
  • trunk/game/scripts/dialogue.py

    r684 r685  
    5252        self.avatar_path = avatar_path 
    5353        self.sections = OrderedDict() 
     54        if (__debug__): 
     55            section_ids = [section.id for section in dialogue_sections] 
    5456        if (dialogue_sections is not None): 
     57            # Sanity check: All DialogueResponses should have next_section_id 
     58            # attributes that refer to valid DialogueSections in the Dialogue. 
    5559            for section in dialogue_sections: 
     60                if (__debug__): 
     61                    for response in section.responses: 
     62                        assert response.next_section_id in section_ids + \ 
     63                            ['end', 'back'], \ 
     64                            '"{0}" is not a valid DialogueSection ID'\ 
     65                            .format(response.next_section_id) 
    5666                self.sections[section.id] = section 
    5767        assert start_section_id in self.sections.keys(), \ 
     
    6777        ) 
    6878        return string_representation 
     79     
     80    def getRootSection(self): 
     81        """ 
     82        Return the root DialogueSection. 
     83         
     84        @raise RuntimeError: L{start_section_id} contains an invalid section 
     85            ID. 
     86        """ 
     87        try: 
     88            root_section = self.sections[self.start_section_id] 
     89        except (IndexError,): 
     90            raise RuntimeError('the DialogueSection ID "{0}" is not valid or ' 
     91                               'does not refer to a valid DialogueSection') 
     92        return root_section 
    6993 
    7094 
  • trunk/game/scripts/dialogueparsers.py

    r683 r685  
    121121        """ 
    122122        loader = loader_class(stream) 
    123         dialogue = self._constructDialogue(loader, loader.get_single_node()) 
     123        try: 
     124            dialogue = \ 
     125                self._constructDialogue(loader, loader.get_single_node()) 
     126        except (AssertionError,) as error: 
     127            raise DialogueFormatError(str(error)) 
    124128        return dialogue 
    125129     
  • trunk/game/scripts/dialogueprocessor.py

    r684 r685  
    153153        Prepare the L{DialogueProcessor} to process the L{Dialogue} by pushing the 
    154154        starting L{DialogueSection} onto the L{dialogue_section_stack}. 
     155         
     156        @raise TypeError: Unable to determine the root L{DialogueSection} 
     157            defined by the L{Dialogue}. 
    155158        """ 
    156159        dialogue = self.dialogue 
    157160        try: 
    158             start_section_id = dialogue.start_section_id 
    159             self.dialogue_section_stack.append( 
    160                 dialogue.sections[start_section_id] 
    161             ) 
    162         except (AttributeError, KeyError): 
     161            root_dialogue_section = dialogue.getRootSection() 
     162        except (RuntimeError,) as error: 
     163            self._logger.error(str(error)) 
    163164            raise TypeError(('unable to determine start DialogueSection for ' 
    164165                             '{0}').format(dialogue)) 
    165166        else: 
     167            self.dialogue_section_stack.append(root_dialogue_section) 
    166168            self.in_dialogue = True 
    167169            self._logger.info('initiated dialogue {0}'.format(dialogue)) 
     
    177179        @returns: valid responses. 
    178180        @rtype: list of L{DialogueResponses<DialogueResponse>} 
    179         """ 
     181         
     182        @raise RuntimeError: Any preconditions are not met. 
     183         
     184        @precondition: dialogue has been initiated via L{initiateDialogue}. 
     185        """ 
     186        if (not self.in_dialogue): 
     187            raise RuntimeError('dialogue has not be initiated via ' 
     188                               'initiateDialogue yet') 
    180189        current_dialogue_section = self.getCurrentDialogueSection() 
    181190        self.runDialogueActions(current_dialogue_section) 
     
    191200        @returns: section of dialogue currently being processed. 
    192201        @rtype: L{DialogueSection} 
    193         """ 
     202         
     203        @raise RuntimeError: Any preconditions are not met. 
     204         
     205        @precondition: dialogue has been initiated via L{initiateDialogue} and 
     206            L{dialogue_section_stack} contains at least one L{DialogueSection}. 
     207        """ 
     208        if (not self.in_dialogue): 
     209            raise RuntimeError('getCurrentDialogueSection called but the ' 
     210                               'dialogue has not been initiated yet') 
    194211        try: 
    195212            current_dialogue_section = self.dialogue_section_stack[-1] 
    196213        except IndexError: 
    197             self._logger.error('getCurrentDialogueSection called by no ' 
     214            raise RuntimeError('getCurrentDialogueSection called but no ' 
    198215                               'DialogueSections are in the stack') 
    199             current_dialogue_section = None 
    200216         
    201217        return current_dialogue_section 
     
    214230            try: 
    215231                command(self.game_state) 
    216             except (Exception) as error: 
     232            except (Exception,) as error: 
    217233                self._logger.error('failed to execute DialogueAction {0}: {1}' 
    218                                  .format(command.keyword, error)) 
     234                                   .format(command.keyword, error)) 
    219235                # TODO Technomage 2010-11-18: Undo previous actions when an 
    220236                #     action fails to execute. 
     
    222238            else: 
    223239                self._logger.debug('ran {0} with arguments {1}' 
    224                                  .format(getattr(type(command), '__name__'), 
    225                                                  command.arguments)) 
     240                                   .format(getattr(type(command), '__name__'), 
     241                                           command.arguments)) 
    226242     
    227243    def getValidResponses(self, dialogue_section): 
     
    237253        @rtype: list of L{DialogueResponses<DialogueResponse>} 
    238254        """ 
    239         if (dialogue_section is None): 
    240             # die nicely when the dialogue_section doesn't exist 
    241             return 
    242255        valid_responses = [] 
    243256        for dialogue_response in dialogue_section.responses: 
     
    246259                condition_met = condition is None or \ 
    247260                                eval(condition, self.game_state) 
    248             except (Exception) as error: 
     261            except (Exception,) as error: 
    249262                self._logger.error( 
    250263                    ('evaluation of condition "{0}" for {1} failed with ' 
     
    271284        @param dialogue_response: response to reply with. 
    272285        @type dialogue_response: L{DialogueReponse} 
    273         """ 
     286         
     287        @raise RuntimeError: Any precondition is not met. 
     288         
     289        @precondition: L{initiateDialogue} must be called before this method 
     290            is used. 
     291        """ 
     292        if (not self.in_dialogue): 
     293            raise RuntimeError('reply cannot be called until the dialogue has ' 
     294                               'been initiated via initiateDialogue') 
    274295        self._logger.info('replied with {0}'.format(dialogue_response)) 
     296        # FIXME: Technomage 2010-12-11: What happens if runDialogueActions 
     297        #     raises an error? 
    275298        self.runDialogueActions(dialogue_response) 
    276299        next_section_id = dialogue_response.next_section_id 
    277300        if (next_section_id == 'back'): 
    278301            if (len(self.dialogue_section_stack) == 1): 
    279                 self._logger.error('attempted to run goto: back action but ' 
    280                                  'stack does not contain a previous ' 
    281                                  'DialogueSection') 
     302                raise RuntimeError('attempted to run goto: back action but ' 
     303                                   'stack does not contain a previous ' 
     304                                   'DialogueSection') 
    282305            else: 
    283306                try: 
    284307                    self.dialogue_section_stack.pop() 
    285                 except IndexError: 
    286                     self._logger.error('attempted to run goto: back action ' 
     308                except (IndexError,): 
     309                    raise RuntimeError('attempted to run goto: back action ' 
    287310                                       'but the stack was empty') 
    288311                else: 
     
    294317            self._logger.debug('ran goto: end action, ended dialogue') 
    295318        else: 
    296             # get a n 
    297319            try: 
    298320                next_dialogue_section = \ 
    299321                    self.dialogue.sections[next_section_id] 
    300322            except KeyError: 
    301                 self._logger.error( 
     323                raise RuntimeError( 
    302324                    ('"{0}" is not a recognized goto: action or ' 
    303325                     'DialogueSection identifier').format(next_section_id) 
  • trunk/game/scripts/gamemodel.py

    r669 r685  
    744744                try: 
    745745                    dialogue = dialogue_parser.load(dialogue_file) 
    746                 except DialogueFormatError as error: 
     746                except (DialogueFormatError,) as error: 
    747747                    logging.error('unable to load dialogue file {0}: {1}' 
    748748                                  .format(dialogue_filepath, error)) 
  • trunk/game/tests/test_dialogueprocessor.py

    r679 r685  
    11#!/usr/bin/env python 
    2  
     2# 
    33#   This file is part of PARPG. 
    4  
     4# 
    55#   PARPG is free software: you can redistribute it and/or modify 
    66#   it under the terms of the GNU General Public License as published by 
    77#   the Free Software Foundation, either version 3 of the License, or 
    88#   (at your option) any later version. 
    9  
     9# 
    1010#   PARPG is distributed in the hope that it will be useful, 
    1111#   but WITHOUT ANY WARRANTY; without even the implied warranty of 
    1212#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    1313#   GNU General Public License for more details. 
    14  
     14# 
    1515#   You should have received a copy of the GNU General Public License 
    1616#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>. 
    17  
    18 import os 
    19 import sys 
    20 import unittest 
    21 from scripts.dialogue import DialogueEngine 
    22 from scripts.dialoguevalidator import DialogueValidator, DialogueFormatException 
    23  
    24 class TestDialogue(unittest.TestCase): 
    25     def setUp(self): 
    26         self.tree = { 
    27             'NPC': 'Mr. Npc', 
    28             'START': 'main', 
    29             'AVATAR': 'gui/icons/npc.png', 
    30             'SECTIONS': { 
    31                 'main': [ 
    32                     { "say": "Greetings stranger" }, 
    33                     { "responses": [ 
    34                         ["Hi, can you tell me where I am?", "friendly"], 
    35                         ["Watch your words", "aggro"], 
    36                         ["This one toggles", "toggles", "show == True"], 
    37                         ["Always display this one", "display", "True and True"], 
    38                         ["response3", "end"], 
    39                     ] } 
    40                 ], 
    41                 'friendly': [ 
    42                     { "say": "You sure are lost" }, 
    43                     { "responses": [ 
    44                         ["Thanks, I know", "thanks"], 
    45                         ["Wait what did you say before?", "back"], 
    46                     ] } 
    47                 ], 
    48                 'aggro': [ 
    49                     { "say": "Die Pig! PAAAAR!!!!" }, 
    50                     { "responses": [ 
    51                         ["ruh-ro raggy!", "toggles"], 
    52                         ["Uh, just kidding??", "back"], 
    53                     ] } 
    54                 ], 
    55                 'toggles': [ 
    56                     { "say": "you turn me on!" }, 
    57                     { "responses": [ 
    58                         ["you turn me off", "back"], 
    59                     ] } 
    60                 ], 
    61                 'display': [ 
    62                     { "say": "Forever Young!" }, 
    63                     { "responses": [ 
    64                         ["Alphaville sucks!", "back"], 
    65                     ] } 
    66                 ], 
    67                 'thanks': [ 
    68                     { "say": "We haven't seen one of your kind in ages" }, 
    69                     { "responses": [ 
    70                         ["Blah blah blah", "display"], 
    71                         ["Say the other thing again", "back"], 
    72                     ] } 
    73                 ], 
    74             } 
    75         } 
    76         # record actions in test_vars 
    77         state = { "say": None, "responses": [] } 
     17try: 
     18    # Python 2.6 
     19    import unittest2 as unittest 
     20except: 
     21    # Python 2.7 
     22    import unittest 
     23 
     24from scripts.dialogueprocessor import DialogueProcessor 
     25# NOTE Technomage 2010-12-08: Using the dialogue data structures might be a 
     26#    violation of unit test isolation, but ultimately they are just simple 
     27#    data structures that don't require much testing of their own so I feel 
     28#    that it isn't a mistake to use them. 
     29from scripts.dialogue import Dialogue, DialogueSection, DialogueResponse 
     30 
     31 
     32class NotInDict(object): 
     33    """ 
     34    Object to return when a key could not be found in a dictionary and 
     35    None is not usable (e.g. because None can appear as a value in the dict). 
     36    """ 
     37    __slots__ = [] 
     38     
     39    def __init__(self): 
     40        raise TypeError('NotInDict cannot be instantiated.') 
     41 
     42 
     43class MockPlayerCharacter(object): 
     44    pass 
     45 
     46 
     47class MockQuestEngine(object): 
     48    pass 
     49 
     50 
     51class MockDialogueAction(object): 
     52    keyword = 'mock_action' 
     53     
     54    def __init__(self, *args, **kwargs): 
     55        self.arguments = (args, kwargs) 
     56        self.was_called = False 
     57        self.call_arguments = [] 
     58     
     59    def __call__(self, game_state): 
     60        self.was_called = True 
     61        self.call_arguments = ((game_state,), {}) 
     62 
     63 
     64class TestDialogueProcessor(unittest.TestCase): 
     65    """Base class for tests of the L{DialogueProcessor} class.""" 
     66    def assertStateEqual(self, object_, **state): 
     67        """ 
     68        Assert that an object's attributes match an expected state. 
    7869         
     70        @param  
     71        """ 
     72        object_dict = {} 
     73        for key in state.keys(): 
     74            actual_value = getattr(object_, key, NotInDict) 
     75            if (actual_value is not NotInDict): 
     76                object_dict[key] = actual_value 
     77        self.assertDictContainsSubset(state, object_dict) 
     78     
     79    def setUp(self): 
     80        self.npc_id = 'mr_npc' 
     81        self.dialogue = Dialogue( 
     82            npc_name='Mr. NPC', 
     83            avatar_path='/some/path', 
     84            start_section_id='main_section', 
     85            dialogue_sections=[ 
     86                DialogueSection( 
     87                    id='main_section', 
     88                    text='This is the main dialogue section.', 
     89                    actions=[ 
     90                        MockDialogueAction('foo'), 
     91                    ], 
     92                    responses=[ 
     93                        DialogueResponse( 
     94                            text='A response.', 
     95                            next_section_id='another_section', 
     96                        ), 
     97                        DialogueResponse( 
     98                            text='A conditional response evaluated to True.', 
     99                            condition='True', 
     100                            actions=[ 
     101                                MockDialogueAction('foo'), 
     102                            ], 
     103                            next_section_id='another_section', 
     104                        ), 
     105                        DialogueResponse( 
     106                            text='A conditional response evaluated to False.', 
     107                            condition='False', 
     108                            next_section_id='another_section', 
     109                        ), 
     110                        DialogueResponse( 
     111                            text='A response that ends the dialogue.', 
     112                            next_section_id='end', 
     113                        ), 
     114                    ], 
     115                ), 
     116                DialogueSection( 
     117                    id='another_section', 
     118                    text='This is another dialogue section.', 
     119                    responses=[ 
     120                        DialogueResponse( 
     121                            text='End dialogue.', 
     122                            next_section_id='end', 
     123                        ), 
     124                    ], 
     125                ), 
     126            ] 
     127        ) 
     128        self.game_state = {'pc': MockPlayerCharacter(), 
     129                           'quest': MockQuestEngine()} 
     130        self.dialogue_processor = DialogueProcessor(self.dialogue, 
     131                                                    self.game_state) 
     132 
     133 
     134class TestInitiateDialogue(TestDialogueProcessor): 
     135    """Tests of the L{DialogueProcessor.initiateDialogue} method.""" 
     136    def testInitiateDialogue_setsState(self): 
     137        """Test initiateDialogue correctly sets DialogueProcessor state""" 
     138        dialogue_processor = self.dialogue_processor 
     139        dialogue_processor.initiateDialogue() 
    79140         
    80         def sayCb(state, text): 
    81             state["say"] = text 
    82  
    83         self.replies = ["resp1", "back", "end"] 
    84  
    85         def npcAvatarCb(image): 
    86             state['npc_avatar'] = image 
    87  
    88         def responsesCb(state, responses): 
    89             state['responses'] = responses 
    90  
    91         callbacks = { 
    92             "say": sayCb, 
    93             "responses": responsesCb, 
    94             "npc_avatar": npcAvatarCb 
    95         } 
    96  
    97         self.dialogue = DialogueEngine(self.tree, callbacks, state) 
    98  
    99     def assertSay(self, text): 
    100         self.assertEqual(text, self.dialogue.state['say']) 
    101  
    102     def assertResponses(self, responses): 
    103         self.assertEqual(responses, self.dialogue.state['responses']) 
    104  
    105     def assertNpcImage(self, image): 
    106         self.assertEqual(image, self.dialogue.state['npc_avatar']) 
    107  
    108     def testSimple(self): 
    109         """Test basic dialogue interaction""" 
    110         self.dialogue.state['show'] = False 
    111         self.dialogue.run() 
    112  
    113         self.assertNpcImage('gui/icons/npc.png') 
    114         self.assertSay('Greetings stranger') 
    115         self.assertResponses([ 
    116             ["Hi, can you tell me where I am?", "friendly"], 
    117             ["Watch your words", "aggro"], 
    118             ["Always display this one", "display", "True and True"], 
    119             ["response3", "end"], 
    120         ]) 
    121         self.dialogue.reply(0) 
    122  
    123         self.assertSay('You sure are lost') 
    124         self.assertResponses([ 
    125             ["Thanks, I know", "thanks"], 
    126             ["Wait what did you say before?", "back"] 
    127         ]) 
    128         self.dialogue.state['show'] = True 
    129         self.dialogue.reply(1) 
    130  
    131         self.assertSay('Greetings stranger') 
    132         self.assertResponses([ 
    133             ["Hi, can you tell me where I am?", "friendly"], 
    134             ["Watch your words", "aggro"], 
    135             ["This one toggles", "toggles", "show == True"], 
    136             ["Always display this one", "display", "True and True"], 
    137             ["response3", "end"], 
    138         ]) 
    139         self.dialogue.reply(0) 
    140  
    141         self.assertSay('You sure are lost') 
    142         self.dialogue.reply(1) 
    143  
    144         self.assertSay('Greetings stranger') 
    145         self.dialogue.reply(0) 
    146  
    147         self.assertSay('You sure are lost') 
    148         self.dialogue.reply(0) 
    149  
    150         self.assertSay("We haven't seen one of your kind in ages") 
    151         self.dialogue.reply(1) 
    152  
    153         self.assertSay('You sure are lost') 
    154         self.dialogue.reply(1) 
    155  
    156         self.assertSay('Greetings stranger') 
    157  
    158     def testAllDialogueFiles(self): 
    159         """ Runs the validator on all dialogue files available. """ 
    160          
    161         val = DialogueValidator() 
    162         diag_dir = os.path.join(os.path.curdir, "dialogue"); 
    163         num_faulty_files = 0 
    164         ignore_ext = (".license") 
    165          
    166         # Test the dialogue files  
    167         for dialogue in os.listdir(diag_dir): 
    168             fname = os.path.join(diag_dir, dialogue) 
    169             ext = os.path.splitext(fname)[1] 
    170             if os.path.isfile(fname) and ext not in ignore_ext: 
    171                 try: 
    172                     assert(val.validateDialogueFromFile(fname,".")) 
    173                 except DialogueFormatException as dfe: 
    174                     print "\nError found in file: ", fname  
    175                     print "Error was: %s" % (dfe) 
    176                     num_faulty_files += 1 
    177          
    178         # Test the internal tree  
    179         try: 
    180             assert(val.validateDialogue(self.tree,".")) 
    181         except DialogueFormatException, dfe: 
    182             print "\nError found in internal tree: ", fname  
    183             print "Error was: %s" % (dfe) 
    184             num_faulty_files += 1             
    185          
    186         assert(num_faulty_files == 0) 
    187    
     141        # Root dialogue section should have been pushed onto the stack. 
     142        root_dialogue_section = \ 
     143            self.dialogue.sections[self.dialogue.start_section_id] 
     144        self.assertStateEqual(dialogue_processor, in_dialogue=True, 
     145                              dialogue=self.dialogue, 
     146                              dialogue_section_stack=[root_dialogue_section]) 
     147 
     148 
     149class TestEndDialogue(TestDialogueProcessor): 
     150    """Tests of the L{DialogueProcessor.endDialogue} method.""" 
     151    def testEndDialogue_resetsState(self): 
     152        """Test endDialogue correctly resets DialogueProcessor state""" 
     153        dialogue_processor = self.dialogue_processor 
     154        # Case: No dialogue initiated. 
     155        assert not dialogue_processor.in_dialogue, \ 
     156            'assumption that dialogue_processor has not initiated a dialogue '\ 
     157            'violated' 
     158        self.assertStateEqual(dialogue_processor, in_dialogue=False, 
     159                              dialogue=self.dialogue, 
     160                              dialogue_section_stack=[]) 
     161        # Case: Dialogue previously initiated. 
     162        dialogue_processor.initiateDialogue() 
     163        assert dialogue_processor.in_dialogue, \ 
     164            'assumption that dialogue_processor initiated a dialogue violated' 
     165        dialogue_processor.endDialogue() 
     166        self.assertStateEqual(dialogue_processor, in_dialogue=False, 
     167                              dialogue=self.dialogue, 
     168                              dialogue_section_stack=[]) 
     169 
     170 
     171class TestContinueDialogue(TestDialogueProcessor): 
     172    """Tests of the L{DialogueProcessor.continueDialogue} method.""" 
     173    def setUp(self): 
     174        TestDialogueProcessor.setUp(self) 
     175        self.dialogue_processor.initiateDialogue() 
     176        self.dialogue_action = self.dialogue.getRootSection().actions[0] 
     177     
     178    def testRunsDialogueActions(self): 
     179        """Test continueDialogue executes all DialogueActions""" 
     180        dialogue_processor = self.dialogue_processor 
     181        dialogue_processor.continueDialogue() 
     182        self.assertTrue(self.dialogue_action.was_called) 
     183        expected_tuple = ((self.game_state,), {}) 
     184        self.assertTupleEqual(expected_tuple, 
     185                              self.dialogue_action.call_arguments) 
     186     
     187    def testReturnsValidResponses(self): 
     188        """Test continueDialogue returns list of valid DialogueResponses""" 
     189        dialogue_processor = self.dialogue_processor 
     190        valid_responses = \ 
     191            dialogue_processor.dialogue_section_stack[0].responses 
     192        valid_responses.pop(2) 
     193        # Sanity check, all "valid" responses should have a condition that 
     194        # evaluates to True. 
     195        for response in valid_responses: 
     196            if (response.condition is not None): 
     197                result = eval(response.condition, self.game_state, {}) 
     198                self.assertTrue(result) 
     199        responses = dialogue_processor.continueDialogue() 
     200        self.assertItemsEqual(responses, valid_responses) 
     201 
     202class TestGetCurrentDialogueSection(TestDialogueProcessor): 
     203    """Tests of the L{DialogueProcessor.getCurrentDialogueSection} method.""" 
     204    def setUp(self): 
     205        TestDialogueProcessor.setUp(self) 
     206        self.dialogue_processor.initiateDialogue() 
     207     
     208    def testReturnsCorrectDialogueSection(self): 
     209        """Test getCurrentDialogueSection returns section at top of stack""" 
     210        dialogue_processor = self.dialogue_processor 
     211        expected_dialogue_section = \ 
     212            self.dialogue.sections[self.dialogue.start_section_id] 
     213        actual_dialogue_section = \ 
     214            dialogue_processor.getCurrentDialogueSection() 
     215        self.assertEqual(expected_dialogue_section, actual_dialogue_section) 
     216 
     217 
     218class TestRunDialogueActions(TestDialogueProcessor): 
     219    """Tests of the L{DialogueProcessor.runDialogueActions} method.""" 
     220    def setUp(self): 
     221        TestDialogueProcessor.setUp(self) 
     222        self.dialogue_processor.initiateDialogue() 
     223        self.dialogue_section = DialogueSection( 
     224            id='some_section', 
     225            text='Test dialogue section.', 
     226            actions=[ 
     227                MockDialogueAction('foo'), 
     228            ], 
     229        ) 
     230        self.dialogue_response = DialogueResponse( 
     231            text='A response.', 
     232            actions=[ 
     233                MockDialogueAction('foo'), 
     234            ], 
     235            next_section_id='end', 
     236        ) 
     237     
     238    def testExecutesDialogueActions(self): 
     239        """Test runDialogueActions correctly executes DialogueActions""" 
     240        dialogue_processor = self.dialogue_processor 
     241        # Case: DialogueSection 
     242        dialogue_processor.runDialogueActions(self.dialogue_section) 
     243        dialogue_section_action = self.dialogue_section.actions[0] 
     244        self.assertTrue(dialogue_section_action.was_called) 
     245        expected_call_args = ((self.game_state,), {}) 
     246        self.assertTupleEqual(expected_call_args, 
     247                              dialogue_section_action.call_arguments) 
     248        # Case: DialogueResponse 
     249        dialogue_processor.runDialogueActions(self.dialogue_response) 
     250        dialogue_response_action = self.dialogue_response.actions[0] 
     251        self.assertTrue(dialogue_response_action.was_called) 
     252        self.assertTupleEqual(expected_call_args, 
     253                              dialogue_response_action.call_arguments) 
     254 
     255 
     256class TestGetValidResponses(TestDialogueProcessor): 
     257    """Tests of the L{DialogueProcessor.getValidResponses} method.""" 
     258    def setUp(self): 
     259        TestDialogueProcessor.setUp(self) 
     260        self.dialogue_processor.initiateDialogue() 
     261     
     262    def testReturnsValidResponses(self): 
     263        """Test getValidResponses returns list of valid DialogueResponses""" 
     264        dialogue_processor = self.dialogue_processor 
     265        valid_responses = \ 
     266            dialogue_processor.dialogue_section_stack[0].responses 
     267        valid_responses.pop(2) 
     268        # Sanity check, all "valid" responses should have a condition that 
     269        # evaluates to True. 
     270        for response in valid_responses: 
     271            if (response.condition is not None): 
     272                result = eval(response.condition, self.game_state, {}) 
     273                self.assertTrue(result) 
     274        responses = dialogue_processor.continueDialogue() 
     275        self.assertItemsEqual(responses, valid_responses) 
     276 
     277 
     278class TestReply(TestDialogueProcessor): 
     279    """Tests of the L{DialogueProcessor.reply} method.""" 
     280    def setUp(self): 
     281        TestDialogueProcessor.setUp(self) 
     282        self.response = self.dialogue.getRootSection().responses[1] 
     283        self.ending_response = self.dialogue.getRootSection().responses[3] 
     284     
     285    def testRaisesExceptionWhenNotInitiated(self): 
     286        """Test reply raises exception when called before initiateDialogue""" 
     287        dialogue_processor = self.dialogue_processor 
     288        # Sanity check: A dialogue must not have been initiated beforehand. 
     289        self.assertFalse(dialogue_processor.in_dialogue) 
     290        with self.assertRaisesRegexp(RuntimeError, r'initiateDialogue'): 
     291            dialogue_processor.reply(self.response) 
     292     
     293    def testExecutesDialogueActions(self): 
     294        """Test reply correctly executes DialogueActions in DialogueResponse""" 
     295        dialogue_processor = self.dialogue_processor 
     296        dialogue_processor.initiateDialogue() 
     297        dialogue_processor.reply(self.response) 
     298        dialogue_action = self.response.actions[0] 
     299        self.assertTrue(dialogue_action.was_called) 
     300        expected_call_args = ((self.game_state,), {}) 
     301        self.assertTupleEqual(expected_call_args, 
     302                              dialogue_action.call_arguments) 
     303     
     304    def testJumpsToCorrectSection(self): 
     305        """Test reply pushes section specified by response onto stack""" 
     306        dialogue_processor = self.dialogue_processor 
     307        dialogue_processor.initiateDialogue() 
     308        # Sanity check: Test response's next_section_id attribute must be refer 
     309        # to a valid DialogueSection in the test Dialogue. 
     310        self.assertIn(self.response.next_section_id, 
     311                      self.dialogue.sections.keys()) 
     312        dialogue_processor.reply(self.response) 
     313        root_section = self.dialogue.getRootSection() 
     314        next_section = self.dialogue.sections[self.response.next_section_id] 
     315        self.assertStateEqual( 
     316            dialogue_processor, 
     317            in_dialogue=True, 
     318            dialogue=self.dialogue, 
     319            dialogue_section_stack=[root_section, next_section], 
     320        ) 
     321     
     322    def testCorrectlyEndsDialogue(self): 
     323        """Test reply ends dialogue when DialogueResponse specifies 'end'""" 
     324        dialogue_processor = self.dialogue_processor 
     325        dialogue_processor.initiateDialogue() 
     326        # Sanity check: Test response must have a next_section_id of 'end'. 
     327        self.assertEqual(self.ending_response.next_section_id, 'end') 
     328        dialogue_processor.reply(self.ending_response) 
     329        self.assertStateEqual(dialogue_processor, in_dialogue=False, 
     330                              dialogue=self.dialogue, 
     331                              dialogue_section_stack=[]) 
     332 
     333 
    188334if __name__ == "__main__": 
    189335    unittest.main() 
Note: See TracChangeset for help on using the changeset viewer.