tests/ide_tests/sikuliberemiz.py
branchwxPython4
changeset 3446 de8cc85b688a
parent 3437 ce366d67a5b7
child 3447 65c5f66e9298
--- a/tests/ide_tests/sikuliberemiz.py	Tue Mar 29 08:30:03 2022 +0200
+++ b/tests/ide_tests/sikuliberemiz.py	Tue Mar 29 08:50:01 2022 +0200
@@ -3,112 +3,47 @@
 import os
 import sys
 import subprocess
-from threading import Thread, Event
+from threading import Thread, Event, Lock
 from time import time as timesec
 
-typeof=type
-
-from sikuli import *
+import sikuli
 
 beremiz_path = os.environ["BEREMIZPATH"]
 python_bin = os.environ.get("BEREMIZPYTHONPATH", "/usr/bin/python")
 
 opj = os.path.join
 
-def StartBeremizApp(projectpath=None, exemple=None):
-    """
-    Starts Beremiz IDE, waits for main window to appear, maximize it.
-
-        Parameters: 
-            projectpath (str): path to project to open
-            exemple (str): path relative to exemples directory
-
-        Returns:
-            Sikuli App class instance
-    """
-
-    command = [python_bin, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"]
-
-    if exemple is not None:
-        command.append(opj(beremiz_path,"exemples",exemple))
-    elif projectpath is not None:
-        command.append(projectpath)
-
-    # App class is broken in Sikuli 2.0.5: can't start process with arguments.
-    # 
-    # Workaround : - use subprocess module to spawn IDE process,
-    #              - use wmctrl to find IDE window details and maximize it
-    #              - pass exact window title to App class constructor
-
-    proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0)
-
-    # Window are macthed against process' PID
-    ppid = proc.pid
-
-    # Timeout 5s
-    c = 50
-    while c > 0:
-        # equiv to "wmctrl -l -p | grep $pid"
-        try:
-            wlist = filter(lambda l:(len(l)>2 and l[2]==str(ppid)), map(lambda s:s.split(None,4), subprocess.check_output(["wmctrl", "-l", "-p"]).splitlines()))
-        except subprocess.CalledProcessError:
-            wlist = []
-
-        # window with no title only has 4 fields do describe it
-        # beremiz splashcreen has no title
-        # wait until main window is visible
-        if len(wlist) == 1 and len(wlist[0]) == 5:
-            windowID,_zero,wpid,_XID,wtitle = wlist[0] 
-            break
-
-        wait(0.1)
-        c = c - 1
-
-    if c == 0:
-        raise Exception("Couldn't find Beremiz window")
-
-    # Maximize window x and y
-    subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"])
-
-    # switchApp creates an App object by finding window by title, is not supposed to spawn a process
-    return proc, switchApp(wtitle)
 
 class KBDShortcut:
-    """Send shortut to app by calling corresponding methods:
-          Stop   
-          Run    
-          Transfer
-          Connect
-          Clean  
-          Build  
+    """Send shortut to app by calling corresponding methods.
 
     example:
-        k = KBDShortcut(app)
+        k = KBDShortcut()
         k.Clean()
     """
 
-    fkeys = {"Stop":     Key.F4,
-             "Run":      Key.F5,
-             "Transfer": Key.F6,
-             "Connect":  Key.F7,
-             "Clean":    Key.F9,
-             "Build":    Key.F11,
-             "Save":     ("s",Key.CTRL),
-             "New":      ("n",Key.CTRL),
-             "Address":  ("l",Key.CTRL)}  # to reach address bar in GTK's file selector
+    fkeys = {"Stop":     sikuli.Key.F4,
+             "Run":      sikuli.Key.F5,
+             "Transfer": sikuli.Key.F6,
+             "Connect":  sikuli.Key.F7,
+             "Clean":    sikuli.Key.F9,
+             "Build":    sikuli.Key.F11,
+             "Save":     ("s",sikuli.Key.CTRL),
+             "New":      ("n",sikuli.Key.CTRL),
+             "Address":  ("l",sikuli.Key.CTRL)}  # to reach address bar in GTK's file selector
 
     def __init__(self, app):
-        self.app = app
+        self.app = app.sikuliapp
     
     def __getattr__(self, name):
         fkey = self.fkeys[name]
-        if typeof(fkey) != tuple:
+        if type(fkey) != tuple:
             fkey = (fkey,)
         app = self.app
 
         def PressShortCut():
             app.focus()
-            type(*fkey)
+            sikuli.type(*fkey)
 
         return PressShortCut
 
@@ -116,12 +51,12 @@
 class IDEIdleObserver:
     "Detects when IDE is idle. This is particularly handy when staring an operation and witing for the en of it."
 
-    def __init__(self, app):
-        """
-        Parameters: 
-            app (class App): Sikuli app given by StartBeremizApp
-        """
-        self.r = Region(app.window())
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
+        self.r = sikuli.Region(self.sikuliapp.window())
 
         self.idechanged = False
         
@@ -136,7 +71,7 @@
     def _OnIDEWindowChange(self, event):
         self.idechanged = True
 
-    def Wait(self, period, timeout):
+    def WaitIdleUI(self, period=1, timeout=15):
         """
         Wait for IDE to stop changing
         Parameters: 
@@ -146,7 +81,7 @@
         c = max(timeout/period,1)
         while c > 0:
             self.idechanged = False
-            wait(period)
+            sikuli.wait(period)
             if not self.idechanged:
                 break
             c = c - 1
@@ -158,19 +93,18 @@
 class stdoutIdleObserver:
     "Detects when IDE's stdout is idle. Can be more reliable than pixel based version (false changes ?)"
 
-    def __init__(self, proc):
-        """
-        Parameters: 
-            proc (subprocess.Popen): Beremiz process, given by StartBeremizApp
-        """
-        self.proc = proc
+    def __init__(self):
+        """
+        Parameters: 
+            app (class BeremizApp)
+        """
         self.stdoutchanged = False
 
-        self.changes = 0
-        self.last_change_count = 0
-
         self.event = Event()
 
+        self.pattern = None
+        self.success_event = Event()
+
         self.thread = Thread(target = self._waitStdoutProc).start()
 
     def _waitStdoutProc(self):
@@ -178,72 +112,140 @@
             a = self.proc.stdout.readline()
             if len(a) == 0 or a is None: 
                 break
-            # sys.stdout.write(a)
-            self.changes = self.changes + 1
+            sys.stdout.write(a)
             self.event.set()
-
-    def WaitForChangeAndIdle(self, period, timeout):
+            if self.pattern is not None and a.find(self.pattern) >= 0:
+                sys.stdout.write("found pattern in '" + a +"'")
+                self.success_event.set()
+
+    def waitForChangeAndIdleStdout(self, period=2, timeout=15):
         """
         Wait for IDE'stdout to start changing
         Parameters: 
             timeout (int): how long to wait for change, in seconds
         """
         start_time = timesec()
-        if self.changes == self.last_change_count:
-            if self.event.wait(timeout):
-                self.event.clear()
-                self.last_change_count = self.changes
-            else:
-                raise Exception("Stdout didn't become active before timeout")
-
-        self.Wait(period, timeout - (timesec() - start_time))
-
-    def Wait(self, period, timeout):
+
+        if self.event.wait(timeout):
+            self.event.clear()
+        else:
+            raise Exception("Stdout didn't become active before timeout")
+
+        self.waitIdleStdout(period, timeout - (timesec() - start_time))
+
+    def waitIdleStdout(self, period=2, timeout=15):
         """
         Wait for IDE'stdout to stop changing
         Parameters: 
             period (int): how many seconds with no change to consider idle
             timeout (int): how long to wait for idle, in seconds
         """
-        c = max(timeout/period, 1)
-        while c > 0:
-            changes = self.changes
-            wait(period)
-            if self.changes == changes:
-                self.last_change_count = self.changes
-                break
-            c = c - 1
-
-        if c == 0:
-            raise Exception("Stdout did not idle before timeout")
-
-
-def waitPatternInStdout(proc, pattern, timeout, count=1):
-    
-    success_event = Event()
-
-    def waitPatternInStdoutProc():
+        end_time = timesec() + timeout
+        self.event.clear()
+        while timesec() < end_time:
+            if self.event.wait(period):
+                # no timeout -> got event -> not idle -> loop again
+                self.event.clear()
+            else:
+                # timeout -> no event -> idle -> exit
+                return True
+
+        raise Exception("Stdout did not idle before timeout")
+
+    def waitPatternInStdout(self, pattern, timeout, count=1):
         found = 0
+        self.pattern = pattern
+        end_time = timesec() + timeout
+        self.event.clear()
         while True:
-            a = proc.stdout.readline()
-            if len(a) == 0 or a is None: 
-                raise Exception("App finished before producing expected stdout pattern")
-            # sys.stdout.write(a)
-            if a.find(pattern) >= 0:
-                sys.stdout.write("found pattern in '" + a +"'")
+            remain = end_time - timesec()
+            if remain <= 0 :
+                res = False
+                break
+
+            res = self.success_event.wait(remain)
+            if res:
+                self.success_event.clear()
                 found = found + 1
                 if found >= count:
-                    success_event.set()
                     break
-
-
-    Thread(target = waitPatternInStdoutProc).start()
-
-    if not success_event.wait(timeout):
-        # test timed out
-        return False
-    else:
-        return True
-
-
-
+        self.pattern = None
+        return res
+
+class BeremizApp(IDEIdleObserver, stdoutIdleObserver):
+    def __init__(self, projectpath=None, exemple=None):
+        """
+        Starts Beremiz IDE, waits for main window to appear, maximize it.
+
+            Parameters: 
+                projectpath (str): path to project to open
+                exemple (str): path relative to exemples directory
+
+            Returns:
+                Sikuli App class instance
+        """
+
+        command = [python_bin, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"]
+
+        if exemple is not None:
+            command.append(opj(beremiz_path,"exemples",exemple))
+        elif projectpath is not None:
+            command.append(projectpath)
+
+        # App class is broken in Sikuli 2.0.5: can't start process with arguments.
+        # 
+        # Workaround : - use subprocess module to spawn IDE process,
+        #              - use wmctrl to find IDE window details and maximize it
+        #              - pass exact window title to App class constructor
+
+        self.proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0)
+
+        # Window are macthed against process' PID
+        ppid = self.proc.pid
+
+        # Timeout 5s
+        c = 50
+        while c > 0:
+            # equiv to "wmctrl -l -p | grep $pid"
+            try:
+                wlist = filter(lambda l:(len(l)>2 and l[2]==str(ppid)), map(lambda s:s.split(None,4), subprocess.check_output(["wmctrl", "-l", "-p"]).splitlines()))
+            except subprocess.CalledProcessError:
+                wlist = []
+
+            # window with no title only has 4 fields do describe it
+            # beremiz splashcreen has no title
+            # wait until main window is visible
+            if len(wlist) == 1 and len(wlist[0]) == 5:
+                windowID,_zero,wpid,_XID,wtitle = wlist[0] 
+                break
+
+            sikuli.wait(0.1)
+            c = c - 1
+
+        if c == 0:
+            raise Exception("Couldn't find Beremiz window")
+
+        # Maximize window x and y
+        subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"])
+
+        # switchApp creates an App object by finding window by title, is not supposed to spawn a process
+        self.sikuliapp = sikuli.switchApp(wtitle)
+        self.k = KBDShortcut(self)
+
+        IDEIdleObserver.__init__(self)
+        stdoutIdleObserver.__init__(self)
+
+        # stubs for common sikuli calls to allow adding hooks later
+        for n in ["click","doubleClick","type"]:
+            setattr(self, n, getattr(sikuli, n))
+
+    def close(self):
+        self.sikuliapp.close()
+        self.sikuliapp = None
+
+    def __del__(self):
+        if self.sikuliapp is not None:
+            self.sikuliapp.close()
+        IDEIdleObserver.__del__(self)
+        stdoutIdleObserver.__del__(self)
+