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