Merge lp:~gary/html5-browser/incremental-timeouts into lp:html5-browser
- incremental-timeouts
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Curtis Hovey | ||||
Approved revision: | 28 | ||||
Merged at revision: | 27 | ||||
Proposed branch: | lp:~gary/html5-browser/incremental-timeouts | ||||
Merge into: | lp:html5-browser | ||||
Diff against target: |
332 lines (+176/-20) 3 files modified
html5browser/__init__.py (+81/-18) html5browser/tests/test_browser.py (+94/-1) setup.py (+1/-1) |
||||
To merge this branch: | bzr merge lp:~gary/html5-browser/incremental-timeouts | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey | code | Approve | |
Review via email: mp+79470@code.launchpad.net |
Commit message
Description of the change
This adds incremental timeouts, per the discussion we had. My intent is that, for us, the Javascript will report the incremental results when the test suite starts, and after every test pass or fail (the YUI test runner does not provide events when a test suite starts). I have not tried this actually with LP yet, but it looks like this is what we need on the html5-browser side.
pocketlint appears to be happy (I tried ``pocketlint .`` and then ``pocketlint html5browser/
Thanks, Curtis
Gary
- 28. By Gary Poster
-
make it possible to use html5browser.main as a setuptools console script entry point.
- 29. By Gary Poster
-
clear the incremental timeout first, before clearing status, for safety.
Gary Poster (gary) wrote : | # |
Cool, thanks.
Yeah, that's reasonable paranoia. I have made the change and pushed.
Thanks again!
Preview Diff
1 | === modified file 'html5browser/__init__.py' | |||
2 | --- html5browser/__init__.py 2011-06-24 16:34:41 +0000 | |||
3 | +++ html5browser/__init__.py 2011-10-17 14:19:58 +0000 | |||
4 | @@ -74,6 +74,9 @@ | |||
5 | 74 | 74 | ||
6 | 75 | STATUS_PREFIX = '::::' | 75 | STATUS_PREFIX = '::::' |
7 | 76 | TIMEOUT = 5000 | 76 | TIMEOUT = 5000 |
8 | 77 | INCREMENTAL_PREFIX = '>>>>' | ||
9 | 78 | INITIAL_TIMEOUT = None | ||
10 | 79 | INCREMENTAL_TIMEOUT = None | ||
11 | 77 | 80 | ||
12 | 78 | def __init__(self, show_window=False, hide_console_messages=True, | 81 | def __init__(self, show_window=False, hide_console_messages=True, |
13 | 79 | force_internal=False): | 82 | force_internal=False): |
14 | @@ -87,21 +90,30 @@ | |||
15 | 87 | self.listeners = {} | 90 | self.listeners = {} |
16 | 88 | self._connect('console-message', self._on_console_message, False) | 91 | self._connect('console-message', self._on_console_message, False) |
17 | 89 | 92 | ||
19 | 90 | def load_page(self, uri, timeout=TIMEOUT): | 93 | def load_page(self, uri, |
20 | 94 | timeout=TIMEOUT, | ||
21 | 95 | initial_timeout=INITIAL_TIMEOUT, | ||
22 | 96 | incremental_timeout=INCREMENTAL_TIMEOUT): | ||
23 | 91 | """Load a page and return the content.""" | 97 | """Load a page and return the content.""" |
24 | 92 | if REQUIRES_EXTERNAL and not self.force_internal: | 98 | if REQUIRES_EXTERNAL and not self.force_internal: |
26 | 93 | self.run_external_browser(uri, timeout) | 99 | self.run_external_browser( |
27 | 100 | uri, timeout, initial_timeout, incremental_timeout) | ||
28 | 94 | else: | 101 | else: |
30 | 95 | self._setup_listening_operation(timeout) | 102 | self._setup_listening_operation( |
31 | 103 | timeout, initial_timeout, incremental_timeout) | ||
32 | 96 | if uri.startswith('/'): | 104 | if uri.startswith('/'): |
33 | 97 | uri = 'file://' + uri | 105 | uri = 'file://' + uri |
34 | 98 | self.load_uri(uri) | 106 | self.load_uri(uri) |
35 | 99 | Gtk.main() | 107 | Gtk.main() |
36 | 100 | return self.command | 108 | return self.command |
37 | 101 | 109 | ||
39 | 102 | def run_script(self, script, timeout=TIMEOUT): | 110 | def run_script(self, script, |
40 | 111 | timeout=TIMEOUT, | ||
41 | 112 | initial_timeout=INITIAL_TIMEOUT, | ||
42 | 113 | incremental_timeout=INCREMENTAL_TIMEOUT): | ||
43 | 103 | """Run a script and return the result.""" | 114 | """Run a script and return the result.""" |
45 | 104 | self._setup_listening_operation(timeout) | 115 | self._setup_listening_operation( |
46 | 116 | timeout, initial_timeout, incremental_timeout) | ||
47 | 105 | self.script = script | 117 | self.script = script |
48 | 106 | self._connect('notify::load-status', self._on_script_load_finished) | 118 | self._connect('notify::load-status', self._on_script_load_finished) |
49 | 107 | self.load_string( | 119 | self.load_string( |
50 | @@ -110,10 +122,16 @@ | |||
51 | 110 | Gtk.main() | 122 | Gtk.main() |
52 | 111 | return self.command | 123 | return self.command |
53 | 112 | 124 | ||
55 | 113 | def run_external_browser(self, uri, timeout): | 125 | def run_external_browser(self, uri, timeout, |
56 | 126 | initial_timeout=None, incremental_timeout=None): | ||
57 | 114 | """Load the page and run the script in an external process.""" | 127 | """Load the page and run the script in an external process.""" |
58 | 115 | self.command = Command() | 128 | self.command = Command() |
60 | 116 | command_line = ['python', HERE, '-t', str(timeout), uri] | 129 | command_line = ['python', HERE, '-t', str(timeout)] |
61 | 130 | if initial_timeout is not None: | ||
62 | 131 | command_line.extend(['-i', str(initial_timeout)]) | ||
63 | 132 | if incremental_timeout is not None: | ||
64 | 133 | command_line.extend(['-s', str(incremental_timeout)]) | ||
65 | 134 | command_line.append(uri) | ||
66 | 117 | browser = subprocess.Popen( | 135 | browser = subprocess.Popen( |
67 | 118 | command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | 136 | command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
68 | 119 | content, ignore = browser.communicate() | 137 | content, ignore = browser.communicate() |
69 | @@ -131,13 +149,23 @@ | |||
70 | 131 | '\\', '\\\\').replace('"', '\\"').replace("'", "\\'").replace( | 149 | '\\', '\\\\').replace('"', '\\"').replace("'", "\\'").replace( |
71 | 132 | '\n', '\\n') | 150 | '\n', '\\n') |
72 | 133 | 151 | ||
74 | 134 | def _setup_listening_operation(self, timeout): | 152 | def _setup_listening_operation(self, timeout, initial_timeout, |
75 | 153 | incremental_timeout): | ||
76 | 135 | """Setup a one-time listening operation for command's completion.""" | 154 | """Setup a one-time listening operation for command's completion.""" |
77 | 136 | self._create_window() | 155 | self._create_window() |
78 | 137 | self.command = Command() | 156 | self.command = Command() |
79 | 157 | self._last_status = None | ||
80 | 158 | self._incremental_timeout = incremental_timeout | ||
81 | 138 | self._connect( | 159 | self._connect( |
82 | 139 | 'status-bar-text-changed', self._on_status_bar_text_changed) | 160 | 'status-bar-text-changed', self._on_status_bar_text_changed) |
84 | 140 | GObject.timeout_add(timeout, self._on_timeout) | 161 | self._timeout_source = GObject.timeout_add(timeout, self._on_timeout) |
85 | 162 | if initial_timeout is None: | ||
86 | 163 | initial_timeout = incremental_timeout | ||
87 | 164 | if initial_timeout is not None: | ||
88 | 165 | self._incremental_timeout_source = GObject.timeout_add( | ||
89 | 166 | initial_timeout, self._on_timeout) | ||
90 | 167 | else: | ||
91 | 168 | self._incremental_timeout_source = None | ||
92 | 141 | 169 | ||
93 | 142 | def _create_window(self): | 170 | def _create_window(self): |
94 | 143 | """Create a window needed to render pages.""" | 171 | """Create a window needed to render pages.""" |
95 | @@ -155,10 +183,22 @@ | |||
96 | 155 | def _on_quit(self, widget=None): | 183 | def _on_quit(self, widget=None): |
97 | 156 | Gtk.main_quit() | 184 | Gtk.main_quit() |
98 | 157 | 185 | ||
99 | 186 | def _clear_status(self): | ||
100 | 187 | self.execute_script('window.status = "";') | ||
101 | 188 | |||
102 | 158 | def _on_status_bar_text_changed(self, view, text): | 189 | def _on_status_bar_text_changed(self, view, text): |
104 | 159 | if text.startswith(self.STATUS_PREFIX): | 190 | if text.startswith(self.INCREMENTAL_PREFIX): |
105 | 191 | self._clear_incremental_timeout() | ||
106 | 192 | self._clear_status() | ||
107 | 193 | self._last_status = text[4:] | ||
108 | 194 | if self._incremental_timeout: | ||
109 | 195 | self._incremental_timeout_source = GObject.timeout_add( | ||
110 | 196 | self._incremental_timeout, self._on_timeout) | ||
111 | 197 | elif text.startswith(self.STATUS_PREFIX): | ||
112 | 198 | self._clear_timeout() | ||
113 | 199 | self._clear_incremental_timeout() | ||
114 | 160 | self._disconnect('status-bar-text-changed') | 200 | self._disconnect('status-bar-text-changed') |
116 | 161 | self.execute_script('window.status = "";') | 201 | self._clear_status() |
117 | 162 | self.command.status = Command.STATUS_COMPLETE | 202 | self.command.status = Command.STATUS_COMPLETE |
118 | 163 | self.command.return_code = Command.CODE_SUCCESS | 203 | self.command.return_code = Command.CODE_SUCCESS |
119 | 164 | self.command.content = text[4:] | 204 | self.command.content = text[4:] |
120 | @@ -173,11 +213,24 @@ | |||
121 | 173 | self.execute_script(self.script) | 213 | self.execute_script(self.script) |
122 | 174 | self.script = None | 214 | self.script = None |
123 | 175 | 215 | ||
124 | 216 | def _clear_incremental_timeout(self): | ||
125 | 217 | if self._incremental_timeout_source is not None: | ||
126 | 218 | GObject.source_remove(self._incremental_timeout_source) | ||
127 | 219 | self._incremental_timeout_source = None | ||
128 | 220 | |||
129 | 221 | def _clear_timeout(self): | ||
130 | 222 | if self._timeout_source is not None: | ||
131 | 223 | GObject.source_remove(self._timeout_source) | ||
132 | 224 | self._timeout_source = None | ||
133 | 225 | |||
134 | 176 | def _on_timeout(self): | 226 | def _on_timeout(self): |
135 | 227 | self._clear_timeout() | ||
136 | 228 | self._clear_incremental_timeout() | ||
137 | 177 | if self.command.status is not Command.STATUS_COMPLETE: | 229 | if self.command.status is not Command.STATUS_COMPLETE: |
138 | 178 | self._disconnect() | 230 | self._disconnect() |
139 | 179 | self.command.status = Command.STATUS_COMPLETE | 231 | self.command.status = Command.STATUS_COMPLETE |
140 | 180 | self.command.return_code = Command.CODE_FAIL | 232 | self.command.return_code = Command.CODE_FAIL |
141 | 233 | self.command.content = self._last_status | ||
142 | 181 | self._on_quit() | 234 | self._on_quit() |
143 | 182 | return False | 235 | return False |
144 | 183 | 236 | ||
145 | @@ -200,18 +253,24 @@ | |||
146 | 200 | from optparse import OptionParser | 253 | from optparse import OptionParser |
147 | 201 | 254 | ||
148 | 202 | 255 | ||
150 | 203 | def main(argv): | 256 | def main(argv=None): |
151 | 204 | """Load a page an return the result set by a page script.""" | 257 | """Load a page an return the result set by a page script.""" |
152 | 258 | if argv is None: | ||
153 | 259 | argv = sys.argv | ||
154 | 205 | (options, uri) = parser_options(args=argv[1:]) | 260 | (options, uri) = parser_options(args=argv[1:]) |
155 | 206 | client = Browser(force_internal=True) | 261 | client = Browser(force_internal=True) |
157 | 207 | page = client.load_page(uri, timeout=options.timeout) | 262 | page = client.load_page(uri, timeout=options.timeout, |
158 | 263 | initial_timeout=options.initial_timeout, | ||
159 | 264 | incremental_timeout=options.incremental_timeout) | ||
160 | 265 | has_page_content = page.content is not None and page.content.strip() != '' | ||
161 | 266 | if has_page_content: | ||
162 | 267 | print page.content | ||
163 | 208 | if page.return_code == page.CODE_FAIL: | 268 | if page.return_code == page.CODE_FAIL: |
164 | 209 | sys.exit(1) | 269 | sys.exit(1) |
167 | 210 | elif page.content is None or page.content.strip() == '': | 270 | elif not has_page_content: |
166 | 211 | # Did not get a report back. | ||
168 | 212 | sys.exit(2) | 271 | sys.exit(2) |
171 | 213 | print page.content | 272 | else: |
172 | 214 | sys.exit(0) | 273 | sys.exit(0) |
173 | 215 | 274 | ||
174 | 216 | 275 | ||
175 | 217 | def parser_options(args): | 276 | def parser_options(args): |
176 | @@ -224,6 +283,10 @@ | |||
177 | 224 | parser = OptionParser(usage=usage, epilog=epilog) | 283 | parser = OptionParser(usage=usage, epilog=epilog) |
178 | 225 | parser.add_option( | 284 | parser.add_option( |
179 | 226 | "-t", "--timeout", type="int", dest="timeout") | 285 | "-t", "--timeout", type="int", dest="timeout") |
180 | 286 | parser.add_option( | ||
181 | 287 | "-i", "--initial-timeout", type="int", dest="initial_timeout") | ||
182 | 288 | parser.add_option( | ||
183 | 289 | "-s", "--test-timeout", type="int", dest="incremental_timeout") | ||
184 | 227 | parser.set_defaults(timeout=Browser.TIMEOUT) | 290 | parser.set_defaults(timeout=Browser.TIMEOUT) |
185 | 228 | (options, uris) = parser.parse_args(args) | 291 | (options, uris) = parser.parse_args(args) |
186 | 229 | if len(uris) != 1: | 292 | if len(uris) != 1: |
187 | @@ -232,4 +295,4 @@ | |||
188 | 232 | 295 | ||
189 | 233 | 296 | ||
190 | 234 | if __name__ == '__main__': | 297 | if __name__ == '__main__': |
192 | 235 | main(sys.argv) | 298 | main() |
193 | 236 | 299 | ||
194 | === modified file 'html5browser/tests/test_browser.py' | |||
195 | --- html5browser/tests/test_browser.py 2011-06-23 23:29:42 +0000 | |||
196 | +++ html5browser/tests/test_browser.py 2011-10-17 14:19:58 +0000 | |||
197 | @@ -19,6 +19,15 @@ | |||
198 | 19 | </head><body></body></html> | 19 | </head><body></body></html> |
199 | 20 | """ | 20 | """ |
200 | 21 | 21 | ||
201 | 22 | incremental_timeout_page = """\ | ||
202 | 23 | <html><head> | ||
203 | 24 | <script type="text/javascript"> | ||
204 | 25 | window.status = '>>>>shazam'; | ||
205 | 26 | </script> | ||
206 | 27 | </head><body></body></html> | ||
207 | 28 | """ | ||
208 | 29 | |||
209 | 30 | |||
210 | 22 | load_page_set_window_status_ignores_non_commands = """\ | 31 | load_page_set_window_status_ignores_non_commands = """\ |
211 | 23 | <html><head> | 32 | <html><head> |
212 | 24 | <script type="text/javascript"> | 33 | <script type="text/javascript"> |
213 | @@ -35,6 +44,16 @@ | |||
214 | 35 | <html><head></head><body></body></html> | 44 | <html><head></head><body></body></html> |
215 | 36 | """ | 45 | """ |
216 | 37 | 46 | ||
217 | 47 | initial_long_wait_page = """\ | ||
218 | 48 | <html><head> | ||
219 | 49 | <script type="text/javascript"> | ||
220 | 50 | setTimeout(function() { | ||
221 | 51 | window.status = '>>>>initial'; | ||
222 | 52 | setTimeout(function() {window.status = '::::ended'}, 200); | ||
223 | 53 | }, 1000); | ||
224 | 54 | </script> | ||
225 | 55 | </head><body></body></html>""" | ||
226 | 56 | |||
227 | 38 | 57 | ||
228 | 39 | class BrowserTestCase(unittest.TestCase): | 58 | class BrowserTestCase(unittest.TestCase): |
229 | 40 | """Verify Browser methods.""" | 59 | """Verify Browser methods.""" |
230 | @@ -93,6 +112,71 @@ | |||
231 | 93 | self.assertEqual(Command.CODE_SUCCESS, command.return_code) | 112 | self.assertEqual(Command.CODE_SUCCESS, command.return_code) |
232 | 94 | self.assertEqual('pting', command.content) | 113 | self.assertEqual('pting', command.content) |
233 | 95 | 114 | ||
234 | 115 | def test_load_page_initial_timeout(self): | ||
235 | 116 | # If a initial_timeout is set, it can cause a timeout. | ||
236 | 117 | self.file.write(timeout_page) | ||
237 | 118 | self.file.flush() | ||
238 | 119 | browser = Browser() | ||
239 | 120 | command = browser.load_page( | ||
240 | 121 | self.file.name, initial_timeout=1000, timeout=30000) | ||
241 | 122 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
242 | 123 | self.assertEqual(Command.CODE_FAIL, command.return_code) | ||
243 | 124 | |||
244 | 125 | def test_load_page_incremental_timeout(self): | ||
245 | 126 | # If an incremental_timeout is set, it can cause a timeout. | ||
246 | 127 | self.file.write(timeout_page) | ||
247 | 128 | self.file.flush() | ||
248 | 129 | browser = Browser() | ||
249 | 130 | command = browser.load_page( | ||
250 | 131 | self.file.name, incremental_timeout=1000, timeout=30000) | ||
251 | 132 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
252 | 133 | self.assertEqual(Command.CODE_FAIL, command.return_code) | ||
253 | 134 | |||
254 | 135 | def test_load_page_initial_timeout_has_precedence_first(self): | ||
255 | 136 | # If both an initial_timeout and an incremental_timeout are set, | ||
256 | 137 | # initial_timeout takes precedence for the first wait. | ||
257 | 138 | self.file.write(initial_long_wait_page) | ||
258 | 139 | self.file.flush() | ||
259 | 140 | browser = Browser() | ||
260 | 141 | command = browser.load_page( | ||
261 | 142 | self.file.name, initial_timeout=3000, | ||
262 | 143 | incremental_timeout=500, timeout=30000) | ||
263 | 144 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
264 | 145 | self.assertEqual(Command.CODE_SUCCESS, command.return_code) | ||
265 | 146 | self.assertEqual('ended', command.content) | ||
266 | 147 | |||
267 | 148 | def test_load_page_incremental_timeout_has_precedence_second(self): | ||
268 | 149 | # If both an initial_timeout and an incremental_timeout are set, | ||
269 | 150 | # incremental_timeout takes precedence for the second wait. | ||
270 | 151 | self.file.write(initial_long_wait_page) | ||
271 | 152 | self.file.flush() | ||
272 | 153 | browser = Browser() | ||
273 | 154 | command = browser.load_page( | ||
274 | 155 | self.file.name, initial_timeout=3000, | ||
275 | 156 | incremental_timeout=100, timeout=30000) | ||
276 | 157 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
277 | 158 | self.assertEqual(Command.CODE_FAIL, command.return_code) | ||
278 | 159 | self.assertEqual('initial', command.content) | ||
279 | 160 | |||
280 | 161 | def test_load_page_timeout_always_wins(self): | ||
281 | 162 | # If timeout, initial_timeout, and incremental_timeout are set, | ||
282 | 163 | # the main timeout will still be honored. | ||
283 | 164 | self.file.write(initial_long_wait_page) | ||
284 | 165 | self.file.flush() | ||
285 | 166 | browser = Browser() | ||
286 | 167 | command = browser.load_page( | ||
287 | 168 | self.file.name, initial_timeout=3000, | ||
288 | 169 | incremental_timeout=3000, timeout=100) | ||
289 | 170 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
290 | 171 | self.assertEqual(Command.CODE_FAIL, command.return_code) | ||
291 | 172 | self.assertEqual('', command.content) | ||
292 | 173 | |||
293 | 174 | def test_load_page_default_timeout_values(self): | ||
294 | 175 | # Verify our expected class defaults. | ||
295 | 176 | self.assertEqual(5000, Browser.TIMEOUT) | ||
296 | 177 | self.assertEqual(None, Browser.INITIAL_TIMEOUT) | ||
297 | 178 | self.assertEqual(None, Browser.INCREMENTAL_TIMEOUT) | ||
298 | 179 | |||
299 | 96 | def test_load_page_timeout(self): | 180 | def test_load_page_timeout(self): |
300 | 97 | # A page that does not set window.status in 5 seconds will timeout. | 181 | # A page that does not set window.status in 5 seconds will timeout. |
301 | 98 | self.file.write(timeout_page) | 182 | self.file.write(timeout_page) |
302 | @@ -101,7 +185,16 @@ | |||
303 | 101 | command = browser.load_page(self.file.name, timeout=1000) | 185 | command = browser.load_page(self.file.name, timeout=1000) |
304 | 102 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | 186 | self.assertEqual(Command.STATUS_COMPLETE, command.status) |
305 | 103 | self.assertEqual(Command.CODE_FAIL, command.return_code) | 187 | self.assertEqual(Command.CODE_FAIL, command.return_code) |
307 | 104 | self.assertEqual(5000, Browser.TIMEOUT) | 188 | |
308 | 189 | def test_load_page_set_window_status_incremental_timeout(self): | ||
309 | 190 | # Any incremental information is returned on a timeout. | ||
310 | 191 | self.file.write(incremental_timeout_page) | ||
311 | 192 | self.file.flush() | ||
312 | 193 | browser = Browser() | ||
313 | 194 | command = browser.load_page(self.file.name, timeout=1000) | ||
314 | 195 | self.assertEqual(Command.STATUS_COMPLETE, command.status) | ||
315 | 196 | self.assertEqual(Command.CODE_FAIL, command.return_code) | ||
316 | 197 | self.assertEqual('shazam', command.content) | ||
317 | 105 | 198 | ||
318 | 106 | def test_run_script_timeout(self): | 199 | def test_run_script_timeout(self): |
319 | 107 | # A script that does not set window.status in 5 seconds will timeout. | 200 | # A script that does not set window.status in 5 seconds will timeout. |
320 | 108 | 201 | ||
321 | === modified file 'setup.py' | |||
322 | --- setup.py 2011-06-24 16:34:41 +0000 | |||
323 | +++ setup.py 2011-10-17 14:19:58 +0000 | |||
324 | @@ -20,7 +20,7 @@ | |||
325 | 20 | setup( | 20 | setup( |
326 | 21 | name="html5browser", | 21 | name="html5browser", |
327 | 22 | description="A HTML5 browser that can be driven by python code.", | 22 | description="A HTML5 browser that can be driven by python code.", |
329 | 23 | version="0.0.8", | 23 | version="0.0.9", |
330 | 24 | maintainer="Curtis C. Hovey", | 24 | maintainer="Curtis C. Hovey", |
331 | 25 | maintainer_email="sinzui.is@verizon.net", | 25 | maintainer_email="sinzui.is@verizon.net", |
332 | 26 | url="https://launchpad.net/html5-browser", | 26 | url="https://launchpad.net/html5-browser", |
Wow! This is great. Thank you very much for solving this problem. Can you move the statement to clear the incremental timeout (line 107) to the top of the block (line 105) because I am paranoid that a timeout can happen when the code has determined all is okay.
I'll merge this and make a release when I get your reply.