edouard@3424: "Commons definitions for sikuli based beremiz IDE GUI tests" edouard@3424: edouard@3424: import os edouard@3424: import sys edouard@3424: import subprocess edouard@3424: from threading import Thread, Event edouard@3424: from sikuli import * edouard@3424: edouard@3424: home = os.environ["HOME"] edouard@3424: beremiz_path = os.environ["BEREMIZPATH"] edouard@3424: edouard@3424: opj = os.path.join edouard@3424: edouard@3424: edouard@3424: def StartBeremizApp(projectpath=None, exemple=None): edouard@3424: """ edouard@3424: Starts Beremiz IDE, waits for main window to appear, maximize it. edouard@3424: edouard@3424: Parameters: edouard@3424: projectpath (str): path to project to open edouard@3424: exemple (str): path relative to exemples directory edouard@3424: edouard@3424: Returns: edouard@3424: Sikuli App class instance edouard@3424: """ edouard@3424: edouard@3424: command = ["%s/beremizenv/bin/python"%home, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"] edouard@3424: edouard@3424: if exemple is not None: edouard@3424: command.append(opj(beremiz_path,"exemples",exemple)) edouard@3424: elif projectpath is not None: edouard@3424: command.append(projectpath) edouard@3424: edouard@3424: # App class is broken in Sikuli 2.0.5: can't start process with arguments. edouard@3424: # edouard@3424: # Workaround : - use subprocess module to spawn IDE process, edouard@3424: # - use wmctrl to find IDE window details and maximize it edouard@3424: # - pass exact window title to App class constructor edouard@3424: edouard@3424: proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0) edouard@3424: edouard@3424: # Window are macthed against process' PID edouard@3424: ppid = proc.pid edouard@3424: edouard@3424: # Timeout 5s edouard@3424: c = 50 edouard@3424: while c > 0: edouard@3424: # equiv to "wmctrl -l -p | grep $pid" edouard@3424: try: edouard@3424: 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())) edouard@3424: except subprocess.CalledProcessError: edouard@3424: wlist = [] edouard@3424: edouard@3424: # window with no title only has 4 fields do describe it edouard@3424: # beremiz splashcreen has no title edouard@3430: # wait until main window is visible edouard@3424: if len(wlist) == 1 and len(wlist[0]) == 5: edouard@3424: windowID,_zero,wpid,_XID,wtitle = wlist[0] edouard@3424: break edouard@3424: edouard@3424: wait(0.1) edouard@3424: c = c - 1 edouard@3424: edouard@3424: if c == 0: edouard@3424: raise Exception("Couldn't find Beremiz window") edouard@3424: edouard@3424: # Maximize window x and y edouard@3424: subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"]) edouard@3424: edouard@3424: # switchApp creates an App object by finding window by title, is not supposed to spawn a process edouard@3424: return proc, switchApp(wtitle) edouard@3424: edouard@3424: class KBDShortcut: edouard@3424: """Send shortut to app by calling corresponding methods: edouard@3424: Stop edouard@3424: Run edouard@3424: Transfer edouard@3424: Connect edouard@3424: Clean edouard@3424: Build edouard@3424: edouard@3424: example: edouard@3424: k = KBDShortcut(app) edouard@3424: k.Clean() edouard@3424: """ edouard@3424: edouard@3424: fkeys = {"Stop": Key.F4, edouard@3424: "Run": Key.F5, edouard@3424: "Transfer": Key.F6, edouard@3424: "Connect": Key.F7, edouard@3424: "Clean": Key.F9, edouard@3424: "Build": Key.F11} edouard@3424: edouard@3424: def __init__(self, app): edouard@3424: self.app = app edouard@3424: edouard@3424: def __getattr__(self, name): edouard@3424: fkey = self.fkeys[name] edouard@3424: app = self.app edouard@3424: edouard@3424: def PressShortCut(): edouard@3424: app.focus() edouard@3424: type(fkey) edouard@3424: edouard@3424: return PressShortCut edouard@3424: edouard@3424: edouard@3424: class IDEIdleObserver: edouard@3424: "Detects when IDE is idle. This is particularly handy when staring an operation and witing for the en of it." edouard@3424: edouard@3424: def __init__(self, app): edouard@3424: """ edouard@3424: Parameters: edouard@3424: app (class App): Sikuli app given by StartBeremizApp edouard@3424: """ edouard@3424: self.r = Region(app.window()) edouard@3424: edouard@3424: self.idechanged = False edouard@3424: edouard@3424: # 200 was selected because default 50 was still catching cursor blinking in console edouard@3424: # FIXME : remove blinking cursor in console edouard@3424: self.r.onChange(200,self._OnIDEWindowChange) edouard@3424: self.r.observeInBackground() edouard@3424: edouard@3424: def __del__(self): edouard@3424: self.r.stopObserver() edouard@3424: edouard@3424: def _OnIDEWindowChange(self, event): edouard@3424: print event edouard@3424: self.idechanged = True edouard@3424: edouard@3424: def Wait(self, period, timeout): edouard@3424: """ edouard@3424: Wait for IDE to stop changing edouard@3424: Parameters: edouard@3424: period (int): how many seconds with no change to consider idle edouard@3424: timeout (int): how long to wait for idle, in seconds edouard@3424: """ edouard@3424: c = timeout/period edouard@3424: while c > 0: edouard@3424: self.idechanged = False edouard@3424: wait(period) edouard@3424: if not self.idechanged: edouard@3424: break edouard@3424: c = c - 1 edouard@3424: edouard@3424: if c == 0: edouard@3424: raise Exception("Window did not idle before timeout") edouard@3424: edouard@3424: edouard@3424: class stdoutIdleObserver: edouard@3424: "Detects when IDE's stdout is idle. Can be more reliable than pixel based version (false changes ?)" edouard@3424: edouard@3424: def __init__(self, proc): edouard@3424: """ edouard@3424: Parameters: edouard@3424: proc (subprocess.Popen): Beremiz process, given by StartBeremizApp edouard@3424: """ edouard@3424: self.proc = proc edouard@3424: self.stdoutchanged = False edouard@3424: edouard@3424: self.thread = Thread(target = self._waitStdoutProc).start() edouard@3424: edouard@3424: def _waitStdoutProc(): edouard@3424: while True: edouard@3424: a = self.proc.stdout.read(1) edouard@3424: if len(a) == 0 or a is None: edouard@3424: break edouard@3424: sys.stdout.write(a) edouard@3424: self.idechanged = True edouard@3424: edouard@3424: def Wait(self, period, timeout): edouard@3424: """ edouard@3424: Wait for IDE'stdout to stop changing edouard@3424: Parameters: edouard@3424: period (int): how many seconds with no change to consider idle edouard@3424: timeout (int): how long to wait for idle, in seconds edouard@3424: """ edouard@3424: c = timeout/period edouard@3424: while c > 0: edouard@3424: self.idechanged = False edouard@3424: wait(period) edouard@3424: if not self.idechanged: edouard@3424: break edouard@3424: c = c - 1 edouard@3424: edouard@3424: if c == 0: edouard@3424: raise Exception("Stdout did not idle before timeout") edouard@3424: edouard@3424: edouard@3424: def waitPatternInStdout(proc, pattern, timeout, count=1): edouard@3424: edouard@3424: success_event = Event() edouard@3424: edouard@3424: def waitPatternInStdoutProc(): edouard@3424: found = 0 edouard@3424: while True: edouard@3424: a = proc.stdout.readline() edouard@3424: if len(a) == 0 or a is None: edouard@3424: raise Exception("App finished before producing expected stdout pattern") edouard@3424: sys.stdout.write(a) edouard@3424: if a.find(pattern) >= 0: edouard@3424: found = found + 1 edouard@3424: if found >= count: edouard@3424: success_event.set() edouard@3424: break edouard@3424: edouard@3424: edouard@3424: Thread(target = waitPatternInStdoutProc).start() edouard@3424: edouard@3424: if not success_event.wait(timeout): edouard@3424: # test timed out edouard@3424: return False edouard@3424: else: edouard@3424: return True edouard@3424: edouard@3424: edouard@3424: