# HG changeset patch # User Edouard Tisserant # Date 1648536601 -7200 # Node ID de8cc85b688abc30ab86da9045325b524ac8bf38 # Parent 83545348403e824bb5e09bc82a28af44b8862d86 Tests: refactored sikuli based test diff -r 83545348403e -r de8cc85b688a tests/ide_tests/edit_project.sikuli/edit_project.py --- a/tests/ide_tests/edit_project.sikuli/edit_project.py Tue Mar 29 08:30:03 2022 +0200 +++ b/tests/ide_tests/edit_project.sikuli/edit_project.py Tue Mar 29 08:50:01 2022 +0200 @@ -12,65 +12,54 @@ from sikuliberemiz import * # Start the app -proc,app = StartBeremizApp(exemple="python") +app = BeremizApp(exemple="python") -# To detect when actions did finish because IDE content isn't changing -idle = IDEIdleObserver(app) +app.doubleClick("1646062660770.png") -doubleClick("1646062660770.png") +app.WaitIdleUI() -idle.Wait(1,15) +app.click("example") -click("example") +app.WaitIdleUI() -idle.Wait(1,15) +app.type(Key.DOWN * 10, Key.CTRL) -type(Key.DOWN * 10, Key.CTRL) +app.WaitIdleUI() -idle.Wait(1,15) +app.doubleClick("1646066996620.png") -doubleClick("1646066996620.png") +app.WaitIdleUI() -idle.Wait(1,15) +app.type(Key.TAB*3) # select text content -type(Key.TAB*3) # select text content +app.type("'sys.stdout.write(\"EDIT TEST OK\\n\")'") -type("'sys.stdout.write(\"EDIT TEST OK\\n\")'") +app.type(Key.ENTER) -type(Key.ENTER) +app.WaitIdleUI() -idle.Wait(1,15) +app.k.Save() -k = KBDShortcut(app) +app.k.Clean() -k.Save() +app.waitForChangeAndIdleStdout() -del idle +app.k.Build() -stdoutIdle = stdoutIdleObserver(proc) +app.waitForChangeAndIdleStdout() -k.Clean() +app.k.Connect() -stdoutIdle.WaitForChangeAndIdle(2,15) +app.waitForChangeAndIdleStdout() -k.Build() +app.k.Transfer() -stdoutIdle.WaitForChangeAndIdle(2,15) +app.waitForChangeAndIdleStdout() -k.Connect() +app.k.Run() -stdoutIdle.WaitForChangeAndIdle(2,15) - -k.Transfer() - -stdoutIdle.WaitForChangeAndIdle(2,15) - -del stdoutIdle - -k.Run() - -# wait 10 seconds for 10 Grumpfs -found = waitPatternInStdout(proc, "EDIT TEST OK", 10) +# wait 10 seconds for 10 patterns +found = app.waitPatternInStdout("EDIT TEST OK", 10) app.close() diff -r 83545348403e -r de8cc85b688a tests/ide_tests/new_project.sikuli/new_project.py --- a/tests/ide_tests/new_project.sikuli/new_project.py Tue Mar 29 08:30:03 2022 +0200 +++ b/tests/ide_tests/new_project.sikuli/new_project.py Tue Mar 29 08:50:01 2022 +0200 @@ -12,133 +12,122 @@ from sikuliberemiz import * # Start the app without any project given -proc,app = StartBeremizApp() +app = BeremizApp() new_project_path = os.path.join(os.path.abspath(os.path.curdir), "new_test_project") # New project path must exist (usually created in directory selection dialog) os.mkdir(new_project_path) -# To detect when actions did finish because IDE content isn't changing -idle = IDEIdleObserver(app) - -# To send keyboard shortuts -k = KBDShortcut(app) - -idle.Wait(1,15) +app.WaitIdleUI() # Create new project (opens new project directory selection dialog) -k.New() +app.k.New() -idle.Wait(1,15) +app.WaitIdleUI() # Move to "Home" section of file selecor, otherwise address is # "file ignored" at first run -type("f", Key.CTRL) -type(Key.ESC) -type(Key.TAB) +app.type("f", Key.CTRL) +app.type(Key.ESC) +app.type(Key.TAB) # Enter directory by name -k.Address() +app.k.Address() # Fill address bar -type(new_project_path + Key.ENTER) +app.type(new_project_path + Key.ENTER) -idle.Wait(1,15) +app.WaitIdleUI() # When prompted for creating first program select type ST -type(Key.TAB*4) # go to lang dropdown -type(Key.DOWN*2) # change selected language -type(Key.ENTER) # validate +app.type(Key.TAB*4) # go to lang dropdown +app.type(Key.DOWN*2) # change selected language +app.type(Key.ENTER) # validate -idle.Wait(1,15) +app.WaitIdleUI() # Name created program -type("Test program") +app.type("Test program") -idle.Wait(1,15) +app.WaitIdleUI() # Focus on Variable grid -type(Key.TAB*4) +app.type(Key.TAB*4) # Add 2 variables -type(Key.ADD*2) +app.type(Key.ADD*2) # Focus on ST text -idle.Wait(1,15) +app.WaitIdleUI() -type(Key.TAB*8) +app.type(Key.TAB*8) -type("""\ +app.type("""\ LocalVar0 := LocalVar1; {printf("Test OK\\n");fflush(stdout);} """) -k.Save() +app.k.Save() # Close ST POU -type("w", Key.CTRL) +app.type("w", Key.CTRL) -idle.Wait(1,15) +app.WaitIdleUI() # Focus project tree and select root item -type(Key.TAB) +app.type(Key.TAB) -type(Key.LEFT) +app.type(Key.LEFT) -type(Key.UP) +app.type(Key.UP) # Edit root item -type(Key.ENTER) +app.type(Key.ENTER) -idle.Wait(1,15) +app.WaitIdleUI() # Switch to config tab -type(Key.RIGHT*2) +app.type(Key.RIGHT*2) # Focus on URI -type(Key.TAB) +app.type(Key.TAB) # Set URI -type("LOCAL://") +app.type("LOCAL://") # FIXME: Select other field to ensure URI is validated -type(Key.TAB) +app.type(Key.TAB) -k.Save() +app.k.Save() # Close project config editor -type("w", Key.CTRL) +app.type("w", Key.CTRL) -idle.Wait(1,15) +app.WaitIdleUI() # Focus seems undefined at that time (FIXME) # Force focussing on "something" so that next shortcut is taken -type(Key.TAB) +app.type(Key.TAB) -del idle +app.waitIdleStdout() -stdoutIdle = stdoutIdleObserver(proc) -stdoutIdle.Wait(2,15) +app.k.Build() -k.Build() +app.waitIdleStdout(5,30) -stdoutIdle.Wait(5,15) +app.k.Connect() -k.Connect() +app.waitIdleStdout() -stdoutIdle.Wait(2,15) +app.k.Transfer() -k.Transfer() +app.waitIdleStdout() -stdoutIdle.Wait(2,15) - -del stdoutIdle - -k.Run() +app.k.Run() # wait 10 seconds -found = waitPatternInStdout(proc, "Test OK", 10) +found = app.waitPatternInStdout("Test OK", 10) app.close() diff -r 83545348403e -r de8cc85b688a tests/ide_tests/run_python_exemple.sikuli/run_python_exemple.py --- a/tests/ide_tests/run_python_exemple.sikuli/run_python_exemple.py Tue Mar 29 08:30:03 2022 +0200 +++ b/tests/ide_tests/run_python_exemple.sikuli/run_python_exemple.py Tue Mar 29 08:50:01 2022 +0200 @@ -12,41 +12,28 @@ from sikuliberemiz import * # Start the app -proc,app = StartBeremizApp(exemple="python") +app = BeremizApp(exemple="python") -# To detect when actions did finish because IDE content isn't changing -# idle = IDEIdleObserver(app) -# screencap based idle detection was making many false positive. Test is more stable with stdout based idle detection +app.k.Clean() -stdoutIdle = stdoutIdleObserver(proc) +app.waitForChangeAndIdleStdout() -# To send keyboard shortuts -k = KBDShortcut(app) +app.k.Build() -k.Clean() +app.waitForChangeAndIdleStdout() -stdoutIdle.Wait(2,15) +app.k.Connect() -k.Build() +app.waitForChangeAndIdleStdout() -stdoutIdle.Wait(2,15) +app.k.Transfer() -k.Connect() +app.waitForChangeAndIdleStdout() -stdoutIdle.Wait(2,15) - -k.Transfer() - -stdoutIdle.Wait(2,15) - -#del idle - -del stdoutIdle - -k.Run() +app.k.Run() # wait 10 seconds for 10 Grumpfs -found = waitPatternInStdout(proc, "Grumpf", 10, 10) +found = app.waitPatternInStdout("Grumpf", 10, 10) app.close() diff -r 83545348403e -r de8cc85b688a tests/ide_tests/sikuliberemiz.py --- 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) +