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

Proposed by Gary Poster
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
Reviewer Review Type Date Requested Status
Curtis Hovey code Approve
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

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

Revision history for this message
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

clear the incremental timeout first, before clearing status, for safety.

Revision history for this message
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
=== modified file 'html5browser/__init__.py'
--- html5browser/__init__.py 2011-06-24 16:34:41 +0000
+++ html5browser/__init__.py 2011-10-17 14:19:58 +0000
@@ -74,6 +74,9 @@
7474
75 STATUS_PREFIX = '::::'75 STATUS_PREFIX = '::::'
76 TIMEOUT = 500076 TIMEOUT = 5000
77 INCREMENTAL_PREFIX = '>>>>'
78 INITIAL_TIMEOUT = None
79 INCREMENTAL_TIMEOUT = None
7780
78 def __init__(self, show_window=False, hide_console_messages=True,81 def __init__(self, show_window=False, hide_console_messages=True,
79 force_internal=False):82 force_internal=False):
@@ -87,21 +90,30 @@
87 self.listeners = {}90 self.listeners = {}
88 self._connect('console-message', self._on_console_message, False)91 self._connect('console-message', self._on_console_message, False)
8992
90 def load_page(self, uri, timeout=TIMEOUT):93 def load_page(self, uri,
94 timeout=TIMEOUT,
95 initial_timeout=INITIAL_TIMEOUT,
96 incremental_timeout=INCREMENTAL_TIMEOUT):
91 """Load a page and return the content."""97 """Load a page and return the content."""
92 if REQUIRES_EXTERNAL and not self.force_internal:98 if REQUIRES_EXTERNAL and not self.force_internal:
93 self.run_external_browser(uri, timeout)99 self.run_external_browser(
100 uri, timeout, initial_timeout, incremental_timeout)
94 else:101 else:
95 self._setup_listening_operation(timeout)102 self._setup_listening_operation(
103 timeout, initial_timeout, incremental_timeout)
96 if uri.startswith('/'):104 if uri.startswith('/'):
97 uri = 'file://' + uri105 uri = 'file://' + uri
98 self.load_uri(uri)106 self.load_uri(uri)
99 Gtk.main()107 Gtk.main()
100 return self.command108 return self.command
101109
102 def run_script(self, script, timeout=TIMEOUT):110 def run_script(self, script,
111 timeout=TIMEOUT,
112 initial_timeout=INITIAL_TIMEOUT,
113 incremental_timeout=INCREMENTAL_TIMEOUT):
103 """Run a script and return the result."""114 """Run a script and return the result."""
104 self._setup_listening_operation(timeout)115 self._setup_listening_operation(
116 timeout, initial_timeout, incremental_timeout)
105 self.script = script117 self.script = script
106 self._connect('notify::load-status', self._on_script_load_finished)118 self._connect('notify::load-status', self._on_script_load_finished)
107 self.load_string(119 self.load_string(
@@ -110,10 +122,16 @@
110 Gtk.main()122 Gtk.main()
111 return self.command123 return self.command
112124
113 def run_external_browser(self, uri, timeout):125 def run_external_browser(self, uri, timeout,
126 initial_timeout=None, incremental_timeout=None):
114 """Load the page and run the script in an external process."""127 """Load the page and run the script in an external process."""
115 self.command = Command()128 self.command = Command()
116 command_line = ['python', HERE, '-t', str(timeout), uri]129 command_line = ['python', HERE, '-t', str(timeout)]
130 if initial_timeout is not None:
131 command_line.extend(['-i', str(initial_timeout)])
132 if incremental_timeout is not None:
133 command_line.extend(['-s', str(incremental_timeout)])
134 command_line.append(uri)
117 browser = subprocess.Popen(135 browser = subprocess.Popen(
118 command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE)136 command_line, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
119 content, ignore = browser.communicate()137 content, ignore = browser.communicate()
@@ -131,13 +149,23 @@
131 '\\', '\\\\').replace('"', '\\"').replace("'", "\\'").replace(149 '\\', '\\\\').replace('"', '\\"').replace("'", "\\'").replace(
132 '\n', '\\n')150 '\n', '\\n')
133151
134 def _setup_listening_operation(self, timeout):152 def _setup_listening_operation(self, timeout, initial_timeout,
153 incremental_timeout):
135 """Setup a one-time listening operation for command's completion."""154 """Setup a one-time listening operation for command's completion."""
136 self._create_window()155 self._create_window()
137 self.command = Command()156 self.command = Command()
157 self._last_status = None
158 self._incremental_timeout = incremental_timeout
138 self._connect(159 self._connect(
139 'status-bar-text-changed', self._on_status_bar_text_changed)160 'status-bar-text-changed', self._on_status_bar_text_changed)
140 GObject.timeout_add(timeout, self._on_timeout)161 self._timeout_source = GObject.timeout_add(timeout, self._on_timeout)
162 if initial_timeout is None:
163 initial_timeout = incremental_timeout
164 if initial_timeout is not None:
165 self._incremental_timeout_source = GObject.timeout_add(
166 initial_timeout, self._on_timeout)
167 else:
168 self._incremental_timeout_source = None
141169
142 def _create_window(self):170 def _create_window(self):
143 """Create a window needed to render pages."""171 """Create a window needed to render pages."""
@@ -155,10 +183,22 @@
155 def _on_quit(self, widget=None):183 def _on_quit(self, widget=None):
156 Gtk.main_quit()184 Gtk.main_quit()
157185
186 def _clear_status(self):
187 self.execute_script('window.status = "";')
188
158 def _on_status_bar_text_changed(self, view, text):189 def _on_status_bar_text_changed(self, view, text):
159 if text.startswith(self.STATUS_PREFIX):190 if text.startswith(self.INCREMENTAL_PREFIX):
191 self._clear_incremental_timeout()
192 self._clear_status()
193 self._last_status = text[4:]
194 if self._incremental_timeout:
195 self._incremental_timeout_source = GObject.timeout_add(
196 self._incremental_timeout, self._on_timeout)
197 elif text.startswith(self.STATUS_PREFIX):
198 self._clear_timeout()
199 self._clear_incremental_timeout()
160 self._disconnect('status-bar-text-changed')200 self._disconnect('status-bar-text-changed')
161 self.execute_script('window.status = "";')201 self._clear_status()
162 self.command.status = Command.STATUS_COMPLETE202 self.command.status = Command.STATUS_COMPLETE
163 self.command.return_code = Command.CODE_SUCCESS203 self.command.return_code = Command.CODE_SUCCESS
164 self.command.content = text[4:]204 self.command.content = text[4:]
@@ -173,11 +213,24 @@
173 self.execute_script(self.script)213 self.execute_script(self.script)
174 self.script = None214 self.script = None
175215
216 def _clear_incremental_timeout(self):
217 if self._incremental_timeout_source is not None:
218 GObject.source_remove(self._incremental_timeout_source)
219 self._incremental_timeout_source = None
220
221 def _clear_timeout(self):
222 if self._timeout_source is not None:
223 GObject.source_remove(self._timeout_source)
224 self._timeout_source = None
225
176 def _on_timeout(self):226 def _on_timeout(self):
227 self._clear_timeout()
228 self._clear_incremental_timeout()
177 if self.command.status is not Command.STATUS_COMPLETE:229 if self.command.status is not Command.STATUS_COMPLETE:
178 self._disconnect()230 self._disconnect()
179 self.command.status = Command.STATUS_COMPLETE231 self.command.status = Command.STATUS_COMPLETE
180 self.command.return_code = Command.CODE_FAIL232 self.command.return_code = Command.CODE_FAIL
233 self.command.content = self._last_status
181 self._on_quit()234 self._on_quit()
182 return False235 return False
183236
@@ -200,18 +253,24 @@
200from optparse import OptionParser253from optparse import OptionParser
201254
202255
203def main(argv):256def main(argv=None):
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."""
258 if argv is None:
259 argv = sys.argv
205 (options, uri) = parser_options(args=argv[1:])260 (options, uri) = parser_options(args=argv[1:])
206 client = Browser(force_internal=True)261 client = Browser(force_internal=True)
207 page = client.load_page(uri, timeout=options.timeout)262 page = client.load_page(uri, timeout=options.timeout,
263 initial_timeout=options.initial_timeout,
264 incremental_timeout=options.incremental_timeout)
265 has_page_content = page.content is not None and page.content.strip() != ''
266 if has_page_content:
267 print page.content
208 if page.return_code == page.CODE_FAIL:268 if page.return_code == page.CODE_FAIL:
209 sys.exit(1)269 sys.exit(1)
210 elif page.content is None or page.content.strip() == '':270 elif not has_page_content:
211 # Did not get a report back.
212 sys.exit(2)271 sys.exit(2)
213 print page.content272 else:
214 sys.exit(0)273 sys.exit(0)
215274
216275
217def parser_options(args):276def parser_options(args):
@@ -224,6 +283,10 @@
224 parser = OptionParser(usage=usage, epilog=epilog)283 parser = OptionParser(usage=usage, epilog=epilog)
225 parser.add_option(284 parser.add_option(
226 "-t", "--timeout", type="int", dest="timeout")285 "-t", "--timeout", type="int", dest="timeout")
286 parser.add_option(
287 "-i", "--initial-timeout", type="int", dest="initial_timeout")
288 parser.add_option(
289 "-s", "--test-timeout", type="int", dest="incremental_timeout")
227 parser.set_defaults(timeout=Browser.TIMEOUT)290 parser.set_defaults(timeout=Browser.TIMEOUT)
228 (options, uris) = parser.parse_args(args)291 (options, uris) = parser.parse_args(args)
229 if len(uris) != 1:292 if len(uris) != 1:
@@ -232,4 +295,4 @@
232295
233296
234if __name__ == '__main__':297if __name__ == '__main__':
235 main(sys.argv)298 main()
236299
=== modified file 'html5browser/tests/test_browser.py'
--- html5browser/tests/test_browser.py 2011-06-23 23:29:42 +0000
+++ html5browser/tests/test_browser.py 2011-10-17 14:19:58 +0000
@@ -19,6 +19,15 @@
19 </head><body></body></html>19 </head><body></body></html>
20 """20 """
2121
22incremental_timeout_page = """\
23 <html><head>
24 <script type="text/javascript">
25 window.status = '>>>>shazam';
26 </script>
27 </head><body></body></html>
28 """
29
30
22load_page_set_window_status_ignores_non_commands = """\31load_page_set_window_status_ignores_non_commands = """\
23 <html><head>32 <html><head>
24 <script type="text/javascript">33 <script type="text/javascript">
@@ -35,6 +44,16 @@
35 <html><head></head><body></body></html>44 <html><head></head><body></body></html>
36 """45 """
3746
47initial_long_wait_page = """\
48 <html><head>
49 <script type="text/javascript">
50 setTimeout(function() {
51 window.status = '>>>>initial';
52 setTimeout(function() {window.status = '::::ended'}, 200);
53 }, 1000);
54 </script>
55 </head><body></body></html>"""
56
3857
39class BrowserTestCase(unittest.TestCase):58class BrowserTestCase(unittest.TestCase):
40 """Verify Browser methods."""59 """Verify Browser methods."""
@@ -93,6 +112,71 @@
93 self.assertEqual(Command.CODE_SUCCESS, command.return_code)112 self.assertEqual(Command.CODE_SUCCESS, command.return_code)
94 self.assertEqual('pting', command.content)113 self.assertEqual('pting', command.content)
95114
115 def test_load_page_initial_timeout(self):
116 # If a initial_timeout is set, it can cause a timeout.
117 self.file.write(timeout_page)
118 self.file.flush()
119 browser = Browser()
120 command = browser.load_page(
121 self.file.name, initial_timeout=1000, timeout=30000)
122 self.assertEqual(Command.STATUS_COMPLETE, command.status)
123 self.assertEqual(Command.CODE_FAIL, command.return_code)
124
125 def test_load_page_incremental_timeout(self):
126 # If an incremental_timeout is set, it can cause a timeout.
127 self.file.write(timeout_page)
128 self.file.flush()
129 browser = Browser()
130 command = browser.load_page(
131 self.file.name, incremental_timeout=1000, timeout=30000)
132 self.assertEqual(Command.STATUS_COMPLETE, command.status)
133 self.assertEqual(Command.CODE_FAIL, command.return_code)
134
135 def test_load_page_initial_timeout_has_precedence_first(self):
136 # If both an initial_timeout and an incremental_timeout are set,
137 # initial_timeout takes precedence for the first wait.
138 self.file.write(initial_long_wait_page)
139 self.file.flush()
140 browser = Browser()
141 command = browser.load_page(
142 self.file.name, initial_timeout=3000,
143 incremental_timeout=500, timeout=30000)
144 self.assertEqual(Command.STATUS_COMPLETE, command.status)
145 self.assertEqual(Command.CODE_SUCCESS, command.return_code)
146 self.assertEqual('ended', command.content)
147
148 def test_load_page_incremental_timeout_has_precedence_second(self):
149 # If both an initial_timeout and an incremental_timeout are set,
150 # incremental_timeout takes precedence for the second wait.
151 self.file.write(initial_long_wait_page)
152 self.file.flush()
153 browser = Browser()
154 command = browser.load_page(
155 self.file.name, initial_timeout=3000,
156 incremental_timeout=100, timeout=30000)
157 self.assertEqual(Command.STATUS_COMPLETE, command.status)
158 self.assertEqual(Command.CODE_FAIL, command.return_code)
159 self.assertEqual('initial', command.content)
160
161 def test_load_page_timeout_always_wins(self):
162 # If timeout, initial_timeout, and incremental_timeout are set,
163 # the main timeout will still be honored.
164 self.file.write(initial_long_wait_page)
165 self.file.flush()
166 browser = Browser()
167 command = browser.load_page(
168 self.file.name, initial_timeout=3000,
169 incremental_timeout=3000, timeout=100)
170 self.assertEqual(Command.STATUS_COMPLETE, command.status)
171 self.assertEqual(Command.CODE_FAIL, command.return_code)
172 self.assertEqual('', command.content)
173
174 def test_load_page_default_timeout_values(self):
175 # Verify our expected class defaults.
176 self.assertEqual(5000, Browser.TIMEOUT)
177 self.assertEqual(None, Browser.INITIAL_TIMEOUT)
178 self.assertEqual(None, Browser.INCREMENTAL_TIMEOUT)
179
96 def test_load_page_timeout(self):180 def test_load_page_timeout(self):
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.
98 self.file.write(timeout_page)182 self.file.write(timeout_page)
@@ -101,7 +185,16 @@
101 command = browser.load_page(self.file.name, timeout=1000)185 command = browser.load_page(self.file.name, timeout=1000)
102 self.assertEqual(Command.STATUS_COMPLETE, command.status)186 self.assertEqual(Command.STATUS_COMPLETE, command.status)
103 self.assertEqual(Command.CODE_FAIL, command.return_code)187 self.assertEqual(Command.CODE_FAIL, command.return_code)
104 self.assertEqual(5000, Browser.TIMEOUT)188
189 def test_load_page_set_window_status_incremental_timeout(self):
190 # Any incremental information is returned on a timeout.
191 self.file.write(incremental_timeout_page)
192 self.file.flush()
193 browser = Browser()
194 command = browser.load_page(self.file.name, timeout=1000)
195 self.assertEqual(Command.STATUS_COMPLETE, command.status)
196 self.assertEqual(Command.CODE_FAIL, command.return_code)
197 self.assertEqual('shazam', command.content)
105198
106 def test_run_script_timeout(self):199 def test_run_script_timeout(self):
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.
108201
=== modified file 'setup.py'
--- setup.py 2011-06-24 16:34:41 +0000
+++ setup.py 2011-10-17 14:19:58 +0000
@@ -20,7 +20,7 @@
20setup(20setup(
21 name="html5browser",21 name="html5browser",
22 description="A HTML5 browser that can be driven by python code.",22 description="A HTML5 browser that can be driven by python code.",
23 version="0.0.8",23 version="0.0.9",
24 maintainer="Curtis C. Hovey",24 maintainer="Curtis C. Hovey",
25 maintainer_email="sinzui.is@verizon.net",25 maintainer_email="sinzui.is@verizon.net",
26 url="https://launchpad.net/html5-browser",26 url="https://launchpad.net/html5-browser",

Subscribers

People subscribed via source and target branches

to all changes: