|
1 "Commons definitions for sikuli based beremiz IDE GUI tests" |
|
2 |
|
3 import os |
|
4 import sys |
|
5 import subprocess |
|
6 from threading import Thread, Event |
|
7 from sikuli import * |
|
8 |
|
9 home = os.environ["HOME"] |
|
10 beremiz_path = os.environ["BEREMIZPATH"] |
|
11 |
|
12 opj = os.path.join |
|
13 |
|
14 |
|
15 def StartBeremizApp(projectpath=None, exemple=None): |
|
16 """ |
|
17 Starts Beremiz IDE, waits for main window to appear, maximize it. |
|
18 |
|
19 Parameters: |
|
20 projectpath (str): path to project to open |
|
21 exemple (str): path relative to exemples directory |
|
22 |
|
23 Returns: |
|
24 Sikuli App class instance |
|
25 """ |
|
26 |
|
27 command = ["%s/beremizenv/bin/python"%home, opj(beremiz_path,"Beremiz.py"), "--log=/dev/stdout"] |
|
28 |
|
29 if exemple is not None: |
|
30 command.append(opj(beremiz_path,"exemples",exemple)) |
|
31 elif projectpath is not None: |
|
32 command.append(projectpath) |
|
33 |
|
34 # App class is broken in Sikuli 2.0.5: can't start process with arguments. |
|
35 # |
|
36 # Workaround : - use subprocess module to spawn IDE process, |
|
37 # - use wmctrl to find IDE window details and maximize it |
|
38 # - pass exact window title to App class constructor |
|
39 |
|
40 proc = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=0) |
|
41 |
|
42 # Window are macthed against process' PID |
|
43 ppid = proc.pid |
|
44 |
|
45 # Timeout 5s |
|
46 c = 50 |
|
47 while c > 0: |
|
48 # equiv to "wmctrl -l -p | grep $pid" |
|
49 try: |
|
50 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())) |
|
51 except subprocess.CalledProcessError: |
|
52 wlist = [] |
|
53 |
|
54 # window with no title only has 4 fields do describe it |
|
55 # beremiz splashcreen has no title |
|
56 # we wity until main window is visible |
|
57 if len(wlist) == 1 and len(wlist[0]) == 5: |
|
58 windowID,_zero,wpid,_XID,wtitle = wlist[0] |
|
59 break |
|
60 |
|
61 wait(0.1) |
|
62 c = c - 1 |
|
63 |
|
64 if c == 0: |
|
65 raise Exception("Couldn't find Beremiz window") |
|
66 |
|
67 # Maximize window x and y |
|
68 subprocess.check_call(["wmctrl", "-i", "-r", windowID, "-b", "add,maximized_vert,maximized_horz"]) |
|
69 |
|
70 # switchApp creates an App object by finding window by title, is not supposed to spawn a process |
|
71 return proc, switchApp(wtitle) |
|
72 |
|
73 class KBDShortcut: |
|
74 """Send shortut to app by calling corresponding methods: |
|
75 Stop |
|
76 Run |
|
77 Transfer |
|
78 Connect |
|
79 Clean |
|
80 Build |
|
81 |
|
82 example: |
|
83 k = KBDShortcut(app) |
|
84 k.Clean() |
|
85 """ |
|
86 |
|
87 fkeys = {"Stop": Key.F4, |
|
88 "Run": Key.F5, |
|
89 "Transfer": Key.F6, |
|
90 "Connect": Key.F7, |
|
91 "Clean": Key.F9, |
|
92 "Build": Key.F11} |
|
93 |
|
94 def __init__(self, app): |
|
95 self.app = app |
|
96 |
|
97 def __getattr__(self, name): |
|
98 fkey = self.fkeys[name] |
|
99 app = self.app |
|
100 |
|
101 def PressShortCut(): |
|
102 app.focus() |
|
103 type(fkey) |
|
104 |
|
105 return PressShortCut |
|
106 |
|
107 |
|
108 class IDEIdleObserver: |
|
109 "Detects when IDE is idle. This is particularly handy when staring an operation and witing for the en of it." |
|
110 |
|
111 def __init__(self, app): |
|
112 """ |
|
113 Parameters: |
|
114 app (class App): Sikuli app given by StartBeremizApp |
|
115 """ |
|
116 self.r = Region(app.window()) |
|
117 |
|
118 self.idechanged = False |
|
119 |
|
120 # 200 was selected because default 50 was still catching cursor blinking in console |
|
121 # FIXME : remove blinking cursor in console |
|
122 self.r.onChange(200,self._OnIDEWindowChange) |
|
123 self.r.observeInBackground() |
|
124 |
|
125 def __del__(self): |
|
126 self.r.stopObserver() |
|
127 |
|
128 def _OnIDEWindowChange(self, event): |
|
129 print event |
|
130 self.idechanged = True |
|
131 |
|
132 def Wait(self, period, timeout): |
|
133 """ |
|
134 Wait for IDE to stop changing |
|
135 Parameters: |
|
136 period (int): how many seconds with no change to consider idle |
|
137 timeout (int): how long to wait for idle, in seconds |
|
138 """ |
|
139 c = timeout/period |
|
140 while c > 0: |
|
141 self.idechanged = False |
|
142 wait(period) |
|
143 if not self.idechanged: |
|
144 break |
|
145 c = c - 1 |
|
146 |
|
147 if c == 0: |
|
148 raise Exception("Window did not idle before timeout") |
|
149 |
|
150 |
|
151 class stdoutIdleObserver: |
|
152 "Detects when IDE's stdout is idle. Can be more reliable than pixel based version (false changes ?)" |
|
153 |
|
154 def __init__(self, proc): |
|
155 """ |
|
156 Parameters: |
|
157 proc (subprocess.Popen): Beremiz process, given by StartBeremizApp |
|
158 """ |
|
159 self.proc = proc |
|
160 self.stdoutchanged = False |
|
161 |
|
162 self.thread = Thread(target = self._waitStdoutProc).start() |
|
163 |
|
164 def _waitStdoutProc(): |
|
165 while True: |
|
166 a = self.proc.stdout.read(1) |
|
167 if len(a) == 0 or a is None: |
|
168 break |
|
169 sys.stdout.write(a) |
|
170 self.idechanged = True |
|
171 |
|
172 def Wait(self, period, timeout): |
|
173 """ |
|
174 Wait for IDE'stdout to stop changing |
|
175 Parameters: |
|
176 period (int): how many seconds with no change to consider idle |
|
177 timeout (int): how long to wait for idle, in seconds |
|
178 """ |
|
179 c = timeout/period |
|
180 while c > 0: |
|
181 self.idechanged = False |
|
182 wait(period) |
|
183 if not self.idechanged: |
|
184 break |
|
185 c = c - 1 |
|
186 |
|
187 if c == 0: |
|
188 raise Exception("Stdout did not idle before timeout") |
|
189 |
|
190 |
|
191 def waitPatternInStdout(proc, pattern, timeout, count=1): |
|
192 |
|
193 success_event = Event() |
|
194 |
|
195 def waitPatternInStdoutProc(): |
|
196 found = 0 |
|
197 while True: |
|
198 a = proc.stdout.readline() |
|
199 if len(a) == 0 or a is None: |
|
200 raise Exception("App finished before producing expected stdout pattern") |
|
201 sys.stdout.write(a) |
|
202 if a.find(pattern) >= 0: |
|
203 found = found + 1 |
|
204 if found >= count: |
|
205 success_event.set() |
|
206 break |
|
207 |
|
208 |
|
209 Thread(target = waitPatternInStdoutProc).start() |
|
210 |
|
211 if not success_event.wait(timeout): |
|
212 # test timed out |
|
213 return False |
|
214 else: |
|
215 return True |
|
216 |
|
217 |
|
218 |