Merge lp:~gary/html5-browser/incremental-timeouts into lp:html5-browser

Proposed by Gary Poster on 2011-10-15
Status: Merged
Approved by: Curtis Hovey on 2011-10-17
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
Reviewer Review Type Date Requested Status
Curtis Hovey code 2011-10-15 Approve on 2011-10-17
Review via email: mp+79470@code.launchpad.net

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/__init__.py html5browser/tests/test_browser.py`` for good measure)

Thanks, Curtis

Gary

To post a comment you must log in.
28. By Gary Poster on 2011-10-17

make it possible to use html5browser.main as a setuptools console script entry point.

Curtis Hovey (sinzui) wrote :

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.

review: Approve (code)
29. By Gary Poster on 2011-10-17

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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
6 STATUS_PREFIX = '::::'
7 TIMEOUT = 5000
8+ INCREMENTAL_PREFIX = '>>>>'
9+ INITIAL_TIMEOUT = None
10+ INCREMENTAL_TIMEOUT = None
11
12 def __init__(self, show_window=False, hide_console_messages=True,
13 force_internal=False):
14@@ -87,21 +90,30 @@
15 self.listeners = {}
16 self._connect('console-message', self._on_console_message, False)
17
18- def load_page(self, uri, timeout=TIMEOUT):
19+ def load_page(self, uri,
20+ timeout=TIMEOUT,
21+ initial_timeout=INITIAL_TIMEOUT,
22+ incremental_timeout=INCREMENTAL_TIMEOUT):
23 """Load a page and return the content."""
24 if REQUIRES_EXTERNAL and not self.force_internal:
25- self.run_external_browser(uri, timeout)
26+ self.run_external_browser(
27+ uri, timeout, initial_timeout, incremental_timeout)
28 else:
29- self._setup_listening_operation(timeout)
30+ self._setup_listening_operation(
31+ timeout, initial_timeout, incremental_timeout)
32 if uri.startswith('/'):
33 uri = 'file://' + uri
34 self.load_uri(uri)
35 Gtk.main()
36 return self.command
37
38- def run_script(self, script, timeout=TIMEOUT):
39+ def run_script(self, script,
40+ timeout=TIMEOUT,
41+ initial_timeout=INITIAL_TIMEOUT,
42+ incremental_timeout=INCREMENTAL_TIMEOUT):
43 """Run a script and return the result."""
44- self._setup_listening_operation(timeout)
45+ self._setup_listening_operation(
46+ timeout, initial_timeout, incremental_timeout)
47 self.script = script
48 self._connect('notify::load-status', self._on_script_load_finished)
49 self.load_string(
50@@ -110,10 +122,16 @@
51 Gtk.main()
52 return self.command
53
54- def run_external_browser(self, uri, timeout):
55+ def run_external_browser(self, uri, timeout,
56+ initial_timeout=None, incremental_timeout=None):
57 """Load the page and run the script in an external process."""
58 self.command = Command()
59- command_line = ['python', HERE, '-t', str(timeout), uri]
60+ command_line = ['python', HERE, '-t', str(timeout)]
61+ if initial_timeout is not None:
62+ command_line.extend(['-i', str(initial_timeout)])
63+ if incremental_timeout is not None:
64+ command_line.extend(['-s', str(incremental_timeout)])
65+ command_line.append(uri)
66 browser = subprocess.Popen(
67 command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
68 content, ignore = browser.communicate()
69@@ -131,13 +149,23 @@
70 '\\', '\\\\').replace('"', '\\"').replace("'", "\\'").replace(
71 '\n', '\\n')
72
73- def _setup_listening_operation(self, timeout):
74+ def _setup_listening_operation(self, timeout, initial_timeout,
75+ incremental_timeout):
76 """Setup a one-time listening operation for command's completion."""
77 self._create_window()
78 self.command = Command()
79+ self._last_status = None
80+ self._incremental_timeout = incremental_timeout
81 self._connect(
82 'status-bar-text-changed', self._on_status_bar_text_changed)
83- GObject.timeout_add(timeout, self._on_timeout)
84+ self._timeout_source = GObject.timeout_add(timeout, self._on_timeout)
85+ if initial_timeout is None:
86+ initial_timeout = incremental_timeout
87+ if initial_timeout is not None:
88+ self._incremental_timeout_source = GObject.timeout_add(
89+ initial_timeout, self._on_timeout)
90+ else:
91+ self._incremental_timeout_source = None
92
93 def _create_window(self):
94 """Create a window needed to render pages."""
95@@ -155,10 +183,22 @@
96 def _on_quit(self, widget=None):
97 Gtk.main_quit()
98
99+ def _clear_status(self):
100+ self.execute_script('window.status = "";')
101+
102 def _on_status_bar_text_changed(self, view, text):
103- if text.startswith(self.STATUS_PREFIX):
104+ if text.startswith(self.INCREMENTAL_PREFIX):
105+ self._clear_incremental_timeout()
106+ self._clear_status()
107+ self._last_status = text[4:]
108+ if self._incremental_timeout:
109+ self._incremental_timeout_source = GObject.timeout_add(
110+ self._incremental_timeout, self._on_timeout)
111+ elif text.startswith(self.STATUS_PREFIX):
112+ self._clear_timeout()
113+ self._clear_incremental_timeout()
114 self._disconnect('status-bar-text-changed')
115- self.execute_script('window.status = "";')
116+ self._clear_status()
117 self.command.status = Command.STATUS_COMPLETE
118 self.command.return_code = Command.CODE_SUCCESS
119 self.command.content = text[4:]
120@@ -173,11 +213,24 @@
121 self.execute_script(self.script)
122 self.script = None
123
124+ def _clear_incremental_timeout(self):
125+ if self._incremental_timeout_source is not None:
126+ GObject.source_remove(self._incremental_timeout_source)
127+ self._incremental_timeout_source = None
128+
129+ def _clear_timeout(self):
130+ if self._timeout_source is not None:
131+ GObject.source_remove(self._timeout_source)
132+ self._timeout_source = None
133+
134 def _on_timeout(self):
135+ self._clear_timeout()
136+ self._clear_incremental_timeout()
137 if self.command.status is not Command.STATUS_COMPLETE:
138 self._disconnect()
139 self.command.status = Command.STATUS_COMPLETE
140 self.command.return_code = Command.CODE_FAIL
141+ self.command.content = self._last_status
142 self._on_quit()
143 return False
144
145@@ -200,18 +253,24 @@
146 from optparse import OptionParser
147
148
149-def main(argv):
150+def main(argv=None):
151 """Load a page an return the result set by a page script."""
152+ if argv is None:
153+ argv = sys.argv
154 (options, uri) = parser_options(args=argv[1:])
155 client = Browser(force_internal=True)
156- page = client.load_page(uri, timeout=options.timeout)
157+ page = client.load_page(uri, timeout=options.timeout,
158+ initial_timeout=options.initial_timeout,
159+ incremental_timeout=options.incremental_timeout)
160+ has_page_content = page.content is not None and page.content.strip() != ''
161+ if has_page_content:
162+ print page.content
163 if page.return_code == page.CODE_FAIL:
164 sys.exit(1)
165- elif page.content is None or page.content.strip() == '':
166- # Did not get a report back.
167+ elif not has_page_content:
168 sys.exit(2)
169- print page.content
170- sys.exit(0)
171+ else:
172+ sys.exit(0)
173
174
175 def parser_options(args):
176@@ -224,6 +283,10 @@
177 parser = OptionParser(usage=usage, epilog=epilog)
178 parser.add_option(
179 "-t", "--timeout", type="int", dest="timeout")
180+ parser.add_option(
181+ "-i", "--initial-timeout", type="int", dest="initial_timeout")
182+ parser.add_option(
183+ "-s", "--test-timeout", type="int", dest="incremental_timeout")
184 parser.set_defaults(timeout=Browser.TIMEOUT)
185 (options, uris) = parser.parse_args(args)
186 if len(uris) != 1:
187@@ -232,4 +295,4 @@
188
189
190 if __name__ == '__main__':
191- main(sys.argv)
192+ main()
193
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 </head><body></body></html>
199 """
200
201+incremental_timeout_page = """\
202+ <html><head>
203+ <script type="text/javascript">
204+ window.status = '>>>>shazam';
205+ </script>
206+ </head><body></body></html>
207+ """
208+
209+
210 load_page_set_window_status_ignores_non_commands = """\
211 <html><head>
212 <script type="text/javascript">
213@@ -35,6 +44,16 @@
214 <html><head></head><body></body></html>
215 """
216
217+initial_long_wait_page = """\
218+ <html><head>
219+ <script type="text/javascript">
220+ setTimeout(function() {
221+ window.status = '>>>>initial';
222+ setTimeout(function() {window.status = '::::ended'}, 200);
223+ }, 1000);
224+ </script>
225+ </head><body></body></html>"""
226+
227
228 class BrowserTestCase(unittest.TestCase):
229 """Verify Browser methods."""
230@@ -93,6 +112,71 @@
231 self.assertEqual(Command.CODE_SUCCESS, command.return_code)
232 self.assertEqual('pting', command.content)
233
234+ def test_load_page_initial_timeout(self):
235+ # If a initial_timeout is set, it can cause a timeout.
236+ self.file.write(timeout_page)
237+ self.file.flush()
238+ browser = Browser()
239+ command = browser.load_page(
240+ self.file.name, initial_timeout=1000, timeout=30000)
241+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
242+ self.assertEqual(Command.CODE_FAIL, command.return_code)
243+
244+ def test_load_page_incremental_timeout(self):
245+ # If an incremental_timeout is set, it can cause a timeout.
246+ self.file.write(timeout_page)
247+ self.file.flush()
248+ browser = Browser()
249+ command = browser.load_page(
250+ self.file.name, incremental_timeout=1000, timeout=30000)
251+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
252+ self.assertEqual(Command.CODE_FAIL, command.return_code)
253+
254+ def test_load_page_initial_timeout_has_precedence_first(self):
255+ # If both an initial_timeout and an incremental_timeout are set,
256+ # initial_timeout takes precedence for the first wait.
257+ self.file.write(initial_long_wait_page)
258+ self.file.flush()
259+ browser = Browser()
260+ command = browser.load_page(
261+ self.file.name, initial_timeout=3000,
262+ incremental_timeout=500, timeout=30000)
263+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
264+ self.assertEqual(Command.CODE_SUCCESS, command.return_code)
265+ self.assertEqual('ended', command.content)
266+
267+ def test_load_page_incremental_timeout_has_precedence_second(self):
268+ # If both an initial_timeout and an incremental_timeout are set,
269+ # incremental_timeout takes precedence for the second wait.
270+ self.file.write(initial_long_wait_page)
271+ self.file.flush()
272+ browser = Browser()
273+ command = browser.load_page(
274+ self.file.name, initial_timeout=3000,
275+ incremental_timeout=100, timeout=30000)
276+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
277+ self.assertEqual(Command.CODE_FAIL, command.return_code)
278+ self.assertEqual('initial', command.content)
279+
280+ def test_load_page_timeout_always_wins(self):
281+ # If timeout, initial_timeout, and incremental_timeout are set,
282+ # the main timeout will still be honored.
283+ self.file.write(initial_long_wait_page)
284+ self.file.flush()
285+ browser = Browser()
286+ command = browser.load_page(
287+ self.file.name, initial_timeout=3000,
288+ incremental_timeout=3000, timeout=100)
289+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
290+ self.assertEqual(Command.CODE_FAIL, command.return_code)
291+ self.assertEqual('', command.content)
292+
293+ def test_load_page_default_timeout_values(self):
294+ # Verify our expected class defaults.
295+ self.assertEqual(5000, Browser.TIMEOUT)
296+ self.assertEqual(None, Browser.INITIAL_TIMEOUT)
297+ self.assertEqual(None, Browser.INCREMENTAL_TIMEOUT)
298+
299 def test_load_page_timeout(self):
300 # A page that does not set window.status in 5 seconds will timeout.
301 self.file.write(timeout_page)
302@@ -101,7 +185,16 @@
303 command = browser.load_page(self.file.name, timeout=1000)
304 self.assertEqual(Command.STATUS_COMPLETE, command.status)
305 self.assertEqual(Command.CODE_FAIL, command.return_code)
306- self.assertEqual(5000, Browser.TIMEOUT)
307+
308+ def test_load_page_set_window_status_incremental_timeout(self):
309+ # Any incremental information is returned on a timeout.
310+ self.file.write(incremental_timeout_page)
311+ self.file.flush()
312+ browser = Browser()
313+ command = browser.load_page(self.file.name, timeout=1000)
314+ self.assertEqual(Command.STATUS_COMPLETE, command.status)
315+ self.assertEqual(Command.CODE_FAIL, command.return_code)
316+ self.assertEqual('shazam', command.content)
317
318 def test_run_script_timeout(self):
319 # A script that does not set window.status in 5 seconds will timeout.
320
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 setup(
326 name="html5browser",
327 description="A HTML5 browser that can be driven by python code.",
328- version="0.0.8",
329+ version="0.0.9",
330 maintainer="Curtis C. Hovey",
331 maintainer_email="sinzui.is@verizon.net",
332 url="https://launchpad.net/html5-browser",

Subscribers

People subscribed via source and target branches

to all changes: