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 27from renpy.test.testmouse import click_mouse, move_mouse 28 29# This is an object that is used to configure test settings. 30_test = renpy.object.Object() 31 32# Should we use maximum framerate mode? 33_test.maximum_framerate = True 34 35# How long should we wait before declaring the test stuck? 36_test.timeout = 5.0 37 38# Should we force the test to proceed despite suppress_underlay? 39_test.force = False 40 41# How long should we wait for a transition before we proceed? 42_test.transition_timeout = 5.0 43 44 45class Node(object): 46 """ 47 An AST node for a test script. 48 """ 49 50 def __init__(self, loc): 51 self.filename, self.linenumber = loc 52 53 def start(self): 54 """ 55 Called once when the node starts execution. 56 57 This is expected to return a state, or None to advance to the next 58 node. 59 """ 60 61 def execute(self, state, t): 62 """ 63 Called once each time the screen is drawn. 64 65 `state` 66 The last state that was returned from this node. 67 68 `t` 69 The time since start was called. 70 """ 71 72 return state 73 74 def ready(self): 75 """ 76 Returns True if this node is ready to execute, or False otherwise. 77 """ 78 79 return True 80 81 def report(self): 82 """ 83 Reports the location of this statement. This should only be called 84 in the execute method of leaf nodes of the test tree. 85 """ 86 87 renpy.test.testexecution.node_loc = (self.filename, self.linenumber) 88 89 90class Pattern(Node): 91 92 position = None 93 always = False 94 95 def __init__(self, loc, pattern=None): 96 Node.__init__(self, loc) 97 self.pattern = pattern 98 99 def start(self): 100 return True 101 102 def execute(self, state, t): 103 104 self.report() 105 106 if renpy.display.interface.trans_pause and (t < _test.transition_timeout): 107 return state 108 109 if self.position is not None: 110 position = renpy.python.py_eval(self.position) 111 else: 112 position = (None, None) 113 114 f = renpy.test.testfocus.find_focus(self.pattern) 115 116 if f is None: 117 x, y = None, None 118 else: 119 x, y = renpy.test.testfocus.find_position(f, position) 120 121 if x is None: 122 if self.pattern: 123 return state 124 else: 125 x, y = renpy.exports.get_mouse_pos() 126 127 return self.perform(x, y, state, t) 128 129 def ready(self): 130 131 if self.always: 132 return True 133 134 f = renpy.test.testfocus.find_focus(self.pattern) 135 136 if f is not None: 137 return True 138 else: 139 return False 140 141 142class Click(Pattern): 143 144 # The number of the button to click. 145 button = 1 146 147 def perform(self, x, y, state, t): 148 click_mouse(self.button, x, y) 149 return None 150 151 152class Move(Pattern): 153 154 def perform(self, x, y, state, t): 155 move_mouse(x, y) 156 return None 157 158 159class Scroll(Node): 160 161 def __init__(self, loc, pattern=None): 162 Node.__init__(self, loc) 163 self.pattern = pattern 164 165 def start(self): 166 return True 167 168 def execute(self, state, t): 169 170 self.report() 171 172 f = renpy.test.testfocus.find_focus(self.pattern) 173 174 if f is None: 175 return True 176 177 if not isinstance(f.widget, renpy.display.behavior.Bar): 178 return True 179 180 adj = f.widget.adjustment 181 182 if adj.value == adj.range: 183 new = 0 184 else: 185 new = adj.value + adj.page 186 187 if new > adj.range: 188 new = adj.range 189 190 adj.change(new) 191 192 return None 193 194 def ready(self): 195 196 f = renpy.test.testfocus.find_focus(self.pattern) 197 198 if f is not None: 199 return True 200 else: 201 return False 202 203 204class Drag(Node): 205 206 def __init__(self, loc, points): 207 Node.__init__(self, loc) 208 self.points = points 209 210 self.pattern = None 211 self.button = 1 212 self.steps = 10 213 214 def start(self): 215 return True 216 217 def execute(self, state, t): 218 219 self.report() 220 221 if renpy.display.interface.trans_pause: 222 return state 223 224 if self.pattern: 225 226 f = renpy.test.testfocus.find_focus(self.pattern) 227 if f is None: 228 return state 229 230 else: 231 f = None 232 233 if state is True: 234 235 points = renpy.python.py_eval(self.points) 236 points = [ renpy.test.testfocus.find_position(f, i) for i in points ] 237 238 if len(points) < 2: 239 raise Exception("A drag requires at least two points.") 240 241 interpoints = [ ] 242 243 xa, ya = points[0] 244 245 interpoints.append((xa, ya)) 246 247 for xb, yb in points[1:]: 248 for i in range(1, self.steps + 1): 249 done = 1.0 * i / self.steps 250 251 interpoints.append(( 252 int(xa + done * (xb - xa)), 253 int(ya + done * (yb - ya)), 254 )) 255 256 xa = xb 257 ya = yb 258 259 x, y = interpoints.pop(0) 260 261 renpy.test.testmouse.move_mouse(x, y) 262 renpy.test.testmouse.press_mouse(self.button) 263 264 else: 265 266 interpoints = state 267 268 x, y = interpoints.pop(0) 269 renpy.test.testmouse.move_mouse(x, y) 270 271 if not interpoints: 272 renpy.test.testmouse.release_mouse(self.button) 273 return None 274 275 else: 276 return interpoints 277 278 def ready(self): 279 280 if self.pattern is None: 281 return True 282 283 f = renpy.test.testfocus.find_focus(self.pattern) 284 285 if f is not None: 286 return True 287 else: 288 return False 289 290 291class Type(Pattern): 292 293 interval = .01 294 295 def __init__(self, loc, keys): 296 Pattern.__init__(self, loc) 297 self.keys = keys 298 299 def start(self): 300 return 0 301 302 def perform(self, x, y, state, t): 303 304 if state >= len(self.keys): 305 return None 306 307 move_mouse(x, y) 308 309 keysym = self.keys[state] 310 renpy.test.testkey.down(self, keysym) 311 renpy.test.testkey.up(self, keysym) 312 313 return state + 1 314 315 316class Action(Node): 317 318 def __init__(self, loc, expr): 319 Node.__init__(self, loc) 320 self.expr = expr 321 322 def start(self): 323 renpy.test.testexecution.action = renpy.python.py_eval(self.expr) 324 return True 325 326 def execute(self, state, t): 327 328 self.report() 329 330 if renpy.test.testexecution.action: 331 return True 332 else: 333 return None 334 335 def ready(self): 336 337 self.report() 338 339 action = renpy.python.py_eval(self.expr) 340 return renpy.display.behavior.is_sensitive(action) 341 342 343class Pause(Node): 344 345 def __init__(self, loc, expr): 346 Node.__init__(self, loc) 347 self.expr = expr 348 349 def start(self): 350 return float(renpy.python.py_eval(self.expr)) 351 352 def execute(self, state, t): 353 354 self.report() 355 356 if t < state: 357 return state 358 else: 359 return None 360 361 362class Label(Node): 363 364 def __init__(self, loc, name): 365 Node.__init__(self, loc) 366 self.name = name 367 368 def start(self): 369 return True 370 371 def execute(self, state, t): 372 if self.name in renpy.test.testexecution.labels: 373 return None 374 else: 375 return state 376 377 def ready(self): 378 return self.name in renpy.test.testexecution.labels 379 380 381################################################################################ 382# Non-clause statements. 383 384class Until(Node): 385 """ 386 Executes `left` repeatedly until `right` is ready, then executes `right` 387 once before quitting. 388 """ 389 390 def __init__(self, loc, left, right): 391 Node.__init__(self, loc) 392 self.left = left 393 self.right = right 394 395 def start(self): 396 return (None, None, 0) 397 398 def execute(self, state, t): 399 child, child_state, start = state 400 401 if self.right.ready() and not (child is self.right): 402 child = self.right 403 child_state = None 404 405 elif child is None: 406 child = self.left 407 408 if child_state is None: 409 child_state = child.start() 410 start = t 411 412 if child_state is not None: 413 child_state = child.execute(child_state, t - start) 414 415 if (child_state is None) and (child is self.right): 416 return None 417 418 return child, child_state, start 419 420 def ready(self): 421 return self.left.ready() or self.right.ready() 422 423 424class If(Node): 425 """ 426 If `condition` is ready, runs the block. Otherwise, goes to the next 427 statement. 428 """ 429 430 def __init__(self, loc, condition, block): 431 Node.__init__(self, loc) 432 433 self.condition = condition 434 self.block = block 435 436 def start(self): 437 return (None, None, 0) 438 439 def execute(self, state, t): 440 node, child_state, start = state 441 442 if node is None: 443 if not self.condition.ready(): 444 return None 445 446 node = self.block 447 448 node, child_state, start = renpy.test.testexecution.execute_node(t, node, child_state, start) 449 450 if node is None: 451 return None 452 453 return (node, child_state, start) 454 455 456class Python(Node): 457 458 def __init__(self, loc, code): 459 Node.__init__(self, loc) 460 self.code = code 461 462 def start(self): 463 renpy.test.testexecution.action = self 464 return True 465 466 def execute(self, state, t): 467 468 self.report() 469 470 if renpy.test.testexecution.action: 471 return True 472 else: 473 return None 474 475 def __call__(self): 476 renpy.python.py_exec_bytecode(self.code.bytecode) 477 478 479class Assert(Node): 480 481 def __init__(self, loc, expr): 482 Node.__init__(self, loc) 483 self.expr = expr 484 485 def start(self): 486 renpy.test.testexecution.action = self 487 return True 488 489 def execute(self, state, t): 490 491 self.report() 492 493 if renpy.test.testexecution.action: 494 return True 495 else: 496 return None 497 498 def __call__(self): 499 if not renpy.python.py_eval(self.expr): 500 raise Exception("On line {}:{}, assertion {} failed.".format(self.filename, self.linenumber, self.expr)) 501 502 503class Jump(Node): 504 505 def __init__(self, loc, target): 506 Node.__init__(self, loc) 507 508 self.target = target 509 510 def start(self): 511 node = renpy.test.testexecution.lookup(self.target, self) 512 raise renpy.test.testexecution.TestJump(node) 513 514 515class Call(Node): 516 517 def __init__(self, loc, target): 518 Node.__init__(self, loc) 519 520 self.target = target 521 522 def start(self): 523 print("Call test", self.target) 524 node = renpy.test.testexecution.lookup(self.target, self) 525 return (node, None, 0) 526 527 def execute(self, state, t): 528 node, child_state, start = state 529 530 node, child_state, start = renpy.test.testexecution.execute_node(t, node, child_state, start) 531 532 if node is None: 533 return None 534 535 return (node, child_state, start) 536 537 538################################################################################ 539# Control structures. 540 541class Block(Node): 542 543 def __init__(self, loc, block): 544 Node.__init__(self, loc) 545 self.block = block 546 547 def start(self): 548 return (0, None, None) 549 550 def execute(self, state, t): 551 i, start, s = state 552 553 if i >= len(self.block): 554 return None 555 556 if s is None: 557 s = self.block[i].start() 558 start = t 559 560 if s is not None: 561 s = self.block[i].execute(s, t - start) 562 563 if s is None: 564 i += 1 565 566 return i, start, s 567