1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us> 2# 3# Permission is hereby granted, free of charge, to any person 4# obtaining a copy of this software and associated documentation files 5# (the "Software"), to deal in the Software without restriction, 6# including without limitation the rights to use, copy, modify, merge, 7# publish, distribute, sublicense, and/or sell copies of the Software, 8# and to permit persons to whom the Software is furnished to do so, 9# subject to the following conditions: 10# 11# The above copyright notice and this permission notice shall be 12# included in all copies or substantial portions of the Software. 13# 14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 23from renpy.compat import * 24 25import renpy.display 26import renpy.test 27import pygame_sdl2 28 29# A map from the name of a testcase to the testcase. 30testcases = { } 31 32# The root node. 33node = None 34 35# The location of the currently execution TL node. 36node_loc = None 37 38# The state of the root node. 39state = None 40 41# The previous state and location in the game script. 42old_state = None 43old_loc = None 44 45# The last time the state changed. 46last_state_change = 0 47 48# The time the root node started executing. 49start_time = None 50 51# An action to run before executing another command. 52action = None 53 54# The set of labels that have been reached since the last time execute 55# has been called. 56labels = set() 57 58 59def take_name(name): 60 """ 61 Takes the name of a statement that is about to run. 62 """ 63 64 if node is None: 65 return 66 67 if isinstance(name, basestring): 68 labels.add(name) 69 70 71class TestJump(Exception): 72 """ 73 An exception that is raised in order to jump to `node`. 74 """ 75 76 def __init__(self, node): 77 self.node = node 78 79 80def lookup(name, from_node): 81 """ 82 Tries to look up the name with `target`. If found, returns it, otherwise 83 raises an exception. 84 """ 85 86 if name in testcases: 87 return testcases[name] 88 89 raise Exception("Testcase {} not found at {}:{}.".format(name, from_node.filename, from_node.linenumber)) 90 91 92def execute_node(now, node, state, start): 93 """ 94 Performs one execution cycle of a node. 95 """ 96 97 while True: 98 99 try: 100 if state is None: 101 state = node.start() 102 start = now 103 104 if state is None: 105 break 106 107 state = node.execute(state, now - start) 108 109 break 110 111 except TestJump as e: 112 node = e.node 113 state = None 114 115 if state is None: 116 node = None 117 118 return node, state, start 119 120 121def execute(): 122 """ 123 Called periodically by the test code to generate events, if desired. 124 """ 125 126 global node 127 global state 128 global start_time 129 global action 130 global old_state 131 global old_loc 132 global last_state_change 133 134 _test = renpy.test.testast._test 135 136 if node is None: 137 return 138 139 if renpy.display.interface.suppress_underlay and (not _test.force): 140 return 141 142 if _test.maximum_framerate: 143 renpy.exports.maximum_framerate(10.0) 144 else: 145 renpy.exports.maximum_framerate(None) 146 147 # Make sure there are no test events in the event queue. 148 for e in pygame_sdl2.event.copy_event_queue(): # @UndefinedVariable 149 if getattr(e, "test", False): 150 return 151 152 if action: 153 old_action = action 154 action = None 155 renpy.display.behavior.run(old_action) 156 157 now = renpy.display.core.get_time() 158 159 node, state, start_time = execute_node(now, node, state, start_time) 160 161 labels.clear() 162 163 if node is None: 164 renpy.test.testmouse.reset() 165 return 166 167 loc = renpy.exports.get_filename_line() 168 169 if (old_state != state) or (old_loc != loc): 170 last_state_change = now 171 172 old_state = state 173 old_loc = loc 174 175 if (now - last_state_change) > _test.timeout: 176 raise Exception("Testcase stuck at {}:{}.".format(node_loc[0], node_loc[1])) 177 178 179def test_command(): 180 """ 181 The dialogue command. This updates dialogue.txt, a file giving all the dialogue 182 in the game. 183 """ 184 185 ap = renpy.arguments.ArgumentParser(description="Runs a testcase.") 186 ap.add_argument("testcase", help="The name of a testcase to run.", nargs='?', default="default") 187 188 args = ap.parse_args() 189 190 if args.testcase not in testcases: 191 raise Exception("Testcase {} was not found.".format(args.testcase)) 192 193 global node 194 node = testcases[args.testcase] 195 196 return True 197 198 199renpy.arguments.register_command("test", test_command) 200