tests/ide_tests/sikuliberemiz.py
author Edouard Tisserant <edouard.tisserant@gmail.com>
Fri, 18 Feb 2022 14:56:54 +0100
branchwxPython4
changeset 3430 c2171d87b587
parent 3424 7db96e011fe7
child 3431 5bcef31e276d
permissions -rw-r--r--
Documentation/comment fix.
"Commons definitions for sikuli based beremiz IDE GUI tests"

import os
import sys
import subprocess
from threading import Thread, Event
from sikuli import *

home = os.environ["HOME"]
beremiz_path = os.environ["BEREMIZPATH"]

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 = ["%s/beremizenv/bin/python"%home, 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  

    example:
        k = KBDShortcut(app)
        k.Clean()
    """

    fkeys = {"Stop":     Key.F4,
             "Run":      Key.F5,
             "Transfer": Key.F6,
             "Connect":  Key.F7,
             "Clean":    Key.F9,
             "Build":    Key.F11}

    def __init__(self, app):
        self.app = app
    
    def __getattr__(self, name):
        fkey = self.fkeys[name]
        app = self.app

        def PressShortCut():
            app.focus()
            type(fkey)

        return PressShortCut


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())

        self.idechanged = False
        
        # 200 was selected because default 50 was still catching cursor blinking in console
        # FIXME : remove blinking cursor in console
        self.r.onChange(200,self._OnIDEWindowChange)
        self.r.observeInBackground()

    def __del__(self):
        self.r.stopObserver()

    def _OnIDEWindowChange(self, event):
        print event
        self.idechanged = True

    def Wait(self, period, timeout):
        """
        Wait for IDE 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 = timeout/period
        while c > 0:
            self.idechanged = False
            wait(period)
            if not self.idechanged:
                break
            c = c - 1

        if c == 0:
            raise Exception("Window did not idle before timeout")

 
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
        self.stdoutchanged = False

        self.thread = Thread(target = self._waitStdoutProc).start()

    def _waitStdoutProc():
        while True:
            a = self.proc.stdout.read(1)
            if len(a) == 0 or a is None: 
                break
            sys.stdout.write(a)
            self.idechanged = True

    def Wait(self, period, timeout):
        """
        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 = timeout/period
        while c > 0:
            self.idechanged = False
            wait(period)
            if not self.idechanged:
                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():
        found = 0
        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:
                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