Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1237)

Side by Side Diff: Tools/Scripts/webkitpy/layout_tests/port/driver.py

Issue 148153009: DOM-object leak detection at run_webkit_tests.py (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Bug fix: unit test Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 # Copyright (C) 2011 Google Inc. All rights reserved. 1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
53 self.should_run_pixel_test = should_run_pixel_test 53 self.should_run_pixel_test = should_run_pixel_test
54 self.args = args 54 self.args = args
55 55
56 56
57 class DriverOutput(object): 57 class DriverOutput(object):
58 """Groups information about a output from driver for easy passing 58 """Groups information about a output from driver for easy passing
59 and post-processing of data.""" 59 and post-processing of data."""
60 60
61 def __init__(self, text, image, image_hash, audio, crash=False, 61 def __init__(self, text, image, image_hash, audio, crash=False,
62 test_time=0, measurements=None, timeout=False, error='', crashed_pro cess_name='??', 62 test_time=0, measurements=None, timeout=False, error='', crashed_pro cess_name='??',
63 crashed_pid=None, crash_log=None, pid=None): 63 crashed_pid=None, crash_log=None, leak=False, leak_log=None, pid=Non e):
64 # FIXME: Args could be renamed to better clarify what they do. 64 # FIXME: Args could be renamed to better clarify what they do.
65 self.text = text 65 self.text = text
66 self.image = image # May be empty-string if the test crashes. 66 self.image = image # May be empty-string if the test crashes.
67 self.image_hash = image_hash 67 self.image_hash = image_hash
68 self.image_diff = None # image_diff gets filled in after construction. 68 self.image_diff = None # image_diff gets filled in after construction.
69 self.audio = audio # Binary format is port-dependent. 69 self.audio = audio # Binary format is port-dependent.
70 self.crash = crash 70 self.crash = crash
71 self.crashed_process_name = crashed_process_name 71 self.crashed_process_name = crashed_process_name
72 self.crashed_pid = crashed_pid 72 self.crashed_pid = crashed_pid
73 self.crash_log = crash_log 73 self.crash_log = crash_log
74 self.leak = leak
75 self.leak_log = leak_log
74 self.test_time = test_time 76 self.test_time = test_time
75 self.measurements = measurements 77 self.measurements = measurements
76 self.timeout = timeout 78 self.timeout = timeout
77 self.error = error # stderr output 79 self.error = error # stderr output
78 self.pid = pid 80 self.pid = pid
79 81
80 def has_stderr(self): 82 def has_stderr(self):
81 return bool(self.error) 83 return bool(self.error)
82 84
83 85
(...skipping 11 matching lines...) Expand all
95 ready for subsequent input. 97 ready for subsequent input.
96 98
97 port - reference back to the port object. 99 port - reference back to the port object.
98 worker_number - identifier for a particular worker/driver instance 100 worker_number - identifier for a particular worker/driver instance
99 """ 101 """
100 self._port = port 102 self._port = port
101 self._worker_number = worker_number 103 self._worker_number = worker_number
102 self._no_timeout = no_timeout 104 self._no_timeout = no_timeout
103 105
104 self._driver_tempdir = None 106 self._driver_tempdir = None
105 # WebKitTestRunner can report back subprocess crashes by printing 107 # content_shell can report back subprocess crashes by printing
106 # "#CRASHED - PROCESSNAME". Since those can happen at any time 108 # "#CRASHED - PROCESSNAME". Since those can happen at any time
107 # and ServerProcess won't be aware of them (since the actual tool 109 # and ServerProcess won't be aware of them (since the actual tool
108 # didn't crash, just a subprocess) we record the crashed subprocess name here. 110 # didn't crash, just a subprocess) we record the crashed subprocess name here.
109 self._crashed_process_name = None 111 self._crashed_process_name = None
110 self._crashed_pid = None 112 self._crashed_pid = None
111 113
112 # WebKitTestRunner can report back subprocesses that became unresponsive 114 # content_shell can report back subprocesses that became unresponsive
113 # This could mean they crashed. 115 # This could mean they crashed.
114 self._subprocess_was_unresponsive = False 116 self._subprocess_was_unresponsive = False
115 117
118 # content_shell can report back subprocess DOM-object leaks by printing
119 # "#LEAK". This leak detection is enabled only when the flag
120 # --enable-leak-detection is passed to content_shell.
121 self._leaked = False
122
116 # stderr reading is scoped on a per-test (not per-block) basis, so we st ore the accumulated 123 # stderr reading is scoped on a per-test (not per-block) basis, so we st ore the accumulated
117 # stderr output, as well as if we've seen #EOF on this driver instance. 124 # stderr output, as well as if we've seen #EOF on this driver instance.
118 # FIXME: We should probably remove _read_first_block and _read_optional_ image_block and 125 # FIXME: We should probably remove _read_first_block and _read_optional_ image_block and
119 # instead scope these locally in run_test. 126 # instead scope these locally in run_test.
120 self.error_from_test = str() 127 self.error_from_test = str()
121 self.err_seen_eof = False 128 self.err_seen_eof = False
122 self._server_process = None 129 self._server_process = None
123 self._current_cmd_line = None 130 self._current_cmd_line = None
124 131
125 self._measurements = {} 132 self._measurements = {}
(...skipping 25 matching lines...) Expand all
151 command = self._command_from_driver_input(driver_input) 158 command = self._command_from_driver_input(driver_input)
152 deadline = test_begin_time + int(driver_input.timeout) / 1000.0 159 deadline = test_begin_time + int(driver_input.timeout) / 1000.0
153 160
154 self._server_process.write(command) 161 self._server_process.write(command)
155 text, audio = self._read_first_block(deadline) # First block is either text or audio 162 text, audio = self._read_first_block(deadline) # First block is either text or audio
156 image, actual_image_hash = self._read_optional_image_block(deadline) # The second (optional) block is image data. 163 image, actual_image_hash = self._read_optional_image_block(deadline) # The second (optional) block is image data.
157 164
158 crashed = self.has_crashed() 165 crashed = self.has_crashed()
159 timed_out = self._server_process.timed_out 166 timed_out = self._server_process.timed_out
160 pid = self._server_process.pid() 167 pid = self._server_process.pid()
168 leaked = self._leaked
161 169
162 if stop_when_done or crashed or timed_out: 170 if stop_when_done or crashed or timed_out or leaked:
163 # We call stop() even if we crashed or timed out in order to get any remaining stdout/stderr output. 171 # We call stop() even if we crashed or timed out in order to get any remaining stdout/stderr output.
164 # In the timeout case, we kill the hung process as well. 172 # In the timeout case, we kill the hung process as well.
165 out, err = self._server_process.stop(self._port.driver_stop_timeout( ) if stop_when_done else 0.0) 173 out, err = self._server_process.stop(self._port.driver_stop_timeout( ) if stop_when_done else 0.0)
166 if out: 174 if out:
167 text += out 175 text += out
168 if err: 176 if err:
169 self.error_from_test += err 177 self.error_from_test += err
170 self._server_process = None 178 self._server_process = None
171 179
172 crash_log = None 180 crash_log = None
181 leak_log = None
173 if crashed: 182 if crashed:
174 self.error_from_test, crash_log = self._get_crash_log(text, self.err or_from_test, newer_than=start_time) 183 self.error_from_test, crash_log = self._get_crash_log(text, self.err or_from_test, newer_than=start_time)
175 184
176 # If we don't find a crash log use a placeholder error message inste ad. 185 # If we don't find a crash log use a placeholder error message inste ad.
177 if not crash_log: 186 if not crash_log:
178 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn own pid" 187 pid_str = str(self._crashed_pid) if self._crashed_pid else "unkn own pid"
179 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p rocess_name, pid_str) 188 crash_log = 'No crash log found for %s:%s.\n' % (self._crashed_p rocess_name, pid_str)
180 # If we were unresponsive append a message informing there may n ot have been a crash. 189 # If we were unresponsive append a message informing there may n ot have been a crash.
181 if self._subprocess_was_unresponsive: 190 if self._subprocess_was_unresponsive:
182 crash_log += 'Process failed to become responsive before tim ing out.\n' 191 crash_log += 'Process failed to become responsive before tim ing out.\n'
183 192
184 # Print stdout and stderr to the placeholder crash log; we want as much context as possible. 193 # Print stdout and stderr to the placeholder crash log; we want as much context as possible.
185 if self.error_from_test: 194 if self.error_from_test:
186 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er ror_from_test) 195 crash_log += '\nstdout:\n%s\nstderr:\n%s\n' % (text, self.er ror_from_test)
196 elif leaked:
197 self.error_from_test, leak_log = self._get_leak_log(text, self.error _from_test, newer_than=start_time)
187 198
188 return DriverOutput(text, image, actual_image_hash, audio, 199 return DriverOutput(text, image, actual_image_hash, audio,
189 crash=crashed, test_time=time.time() - test_begin_time, measurements =self._measurements, 200 crash=crashed, test_time=time.time() - test_begin_time, measurements =self._measurements,
190 timeout=timed_out, error=self.error_from_test, 201 timeout=timed_out, error=self.error_from_test,
191 crashed_process_name=self._crashed_process_name, 202 crashed_process_name=self._crashed_process_name,
192 crashed_pid=self._crashed_pid, crash_log=crash_log, pid=pid) 203 crashed_pid=self._crashed_pid, crash_log=crash_log,
204 leak=leaked, leak_log=leak_log,
205 pid=pid)
193 206
194 def _get_crash_log(self, stdout, stderr, newer_than): 207 def _get_crash_log(self, stdout, stderr, newer_than):
195 return self._port._get_crash_log(self._crashed_process_name, self._crash ed_pid, stdout, stderr, newer_than) 208 return self._port._get_crash_log(self._crashed_process_name, self._crash ed_pid, stdout, stderr, newer_than)
196 209
210 def _get_leak_log(self, stdout, stderr, newer_than):
211 return self._port._get_leak_log(self._crashed_process_name, self._crashe d_pid, stdout, stderr, newer_than)
212
197 # FIXME: Seems this could just be inlined into callers. 213 # FIXME: Seems this could just be inlined into callers.
198 @classmethod 214 @classmethod
199 def _command_wrapper(cls, wrapper_option): 215 def _command_wrapper(cls, wrapper_option):
200 # Hook for injecting valgrind or other runtime instrumentation, 216 # Hook for injecting valgrind or other runtime instrumentation,
201 # used by e.g. tools/valgrind/valgrind_tests.py. 217 # used by e.g. tools/valgrind/valgrind_tests.py.
202 return shlex.split(wrapper_option) if wrapper_option else [] 218 return shlex.split(wrapper_option) if wrapper_option else []
203 219
204 HTTP_DIR = "http/tests/" 220 HTTP_DIR = "http/tests/"
205 HTTP_LOCAL_DIR = "http/tests/local/" 221 HTTP_LOCAL_DIR = "http/tests/local/"
206 222
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
261 return environment 277 return environment
262 278
263 def _start(self, pixel_tests, per_test_args, wait_for_ready=True): 279 def _start(self, pixel_tests, per_test_args, wait_for_ready=True):
264 self.stop() 280 self.stop()
265 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel f._port.driver_name()) 281 self._driver_tempdir = self._port._filesystem.mkdtemp(prefix='%s-' % sel f._port.driver_name())
266 server_name = self._port.driver_name() 282 server_name = self._port.driver_name()
267 environment = self._port.setup_environ_for_server(server_name) 283 environment = self._port.setup_environ_for_server(server_name)
268 environment = self._setup_environ_for_driver(environment) 284 environment = self._setup_environ_for_driver(environment)
269 self._crashed_process_name = None 285 self._crashed_process_name = None
270 self._crashed_pid = None 286 self._crashed_pid = None
287 self._leaked = False
271 cmd_line = self.cmd_line(pixel_tests, per_test_args) 288 cmd_line = self.cmd_line(pixel_tests, per_test_args)
272 self._server_process = self._port._server_process_constructor(self._port , server_name, cmd_line, environment, logging=self._port.get_option("driver_logg ing")) 289 self._server_process = self._port._server_process_constructor(self._port , server_name, cmd_line, environment, logging=self._port.get_option("driver_logg ing"))
273 self._server_process.start() 290 self._server_process.start()
274 self._current_cmd_line = cmd_line 291 self._current_cmd_line = cmd_line
275 292
276 if wait_for_ready: 293 if wait_for_ready:
277 deadline = time.time() + DRIVER_START_TIMEOUT_SECS 294 deadline = time.time() + DRIVER_START_TIMEOUT_SECS
278 if not self._wait_for_server_process_output(self._server_process, de adline, '#READY'): 295 if not self._wait_for_server_process_output(self._server_process, de adline, '#READY'):
279 _log.error("content_shell took too long to startup.") 296 _log.error("content_shell took too long to startup.")
280 297
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
313 330
314 self._current_cmd_line = None 331 self._current_cmd_line = None
315 332
316 def cmd_line(self, pixel_tests, per_test_args): 333 def cmd_line(self, pixel_tests, per_test_args):
317 cmd = self._command_wrapper(self._port.get_option('wrapper')) 334 cmd = self._command_wrapper(self._port.get_option('wrapper'))
318 cmd.append(self._port._path_to_driver()) 335 cmd.append(self._port._path_to_driver())
319 if self._no_timeout: 336 if self._no_timeout:
320 cmd.append('--no-timeout') 337 cmd.append('--no-timeout')
321 cmd.extend(self._port.get_option('additional_drt_flag', [])) 338 cmd.extend(self._port.get_option('additional_drt_flag', []))
322 cmd.extend(self._port.additional_drt_flag()) 339 cmd.extend(self._port.additional_drt_flag())
340 if self._port.get_option('enable_leak_detection'):
341 cmd.append('--enable-leak-detection')
323 cmd.extend(per_test_args) 342 cmd.extend(per_test_args)
324 cmd.append('-') 343 cmd.append('-')
325 return cmd 344 return cmd
326 345
327 def _check_for_driver_crash(self, error_line): 346 def _check_for_driver_crash(self, error_line):
328 if error_line == "#CRASHED\n": 347 if error_line == "#CRASHED\n":
329 # This is used on Windows to report that the process has crashed 348 # This is used on Windows to report that the process has crashed
330 # See http://trac.webkit.org/changeset/65537. 349 # See http://trac.webkit.org/changeset/65537.
331 self._crashed_process_name = self._server_process.name() 350 self._crashed_process_name = self._server_process.name()
332 self._crashed_pid = self._server_process.pid() 351 self._crashed_pid = self._server_process.pid()
333 elif (error_line.startswith("#CRASHED - ") 352 elif (error_line.startswith("#CRASHED - ")
334 or error_line.startswith("#PROCESS UNRESPONSIVE - ")): 353 or error_line.startswith("#PROCESS UNRESPONSIVE - ")):
335 # WebKitTestRunner uses this to report that the WebProcess subproces s crashed. 354 # WebKitTestRunner uses this to report that the WebProcess subproces s crashed.
336 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_ line) 355 match = re.match('#(?:CRASHED|PROCESS UNRESPONSIVE) - (\S+)', error_ line)
337 self._crashed_process_name = match.group(1) if match else 'WebProces s' 356 self._crashed_process_name = match.group(1) if match else 'WebProces s'
338 match = re.search('pid (\d+)', error_line) 357 match = re.search('pid (\d+)', error_line)
339 pid = int(match.group(1)) if match else None 358 pid = int(match.group(1)) if match else None
340 self._crashed_pid = pid 359 self._crashed_pid = pid
341 # FIXME: delete this after we're sure this code is working :) 360 # FIXME: delete this after we're sure this code is working :)
342 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr ocess_name, str(pid), error_line)) 361 _log.debug('%s crash, pid = %s, error_line = %s' % (self._crashed_pr ocess_name, str(pid), error_line))
343 if error_line.startswith("#PROCESS UNRESPONSIVE - "): 362 if error_line.startswith("#PROCESS UNRESPONSIVE - "):
344 self._subprocess_was_unresponsive = True 363 self._subprocess_was_unresponsive = True
345 self._port.sample_process(self._crashed_process_name, self._cras hed_pid) 364 self._port.sample_process(self._crashed_process_name, self._cras hed_pid)
346 # We want to show this since it's not a regular crash and probab ly we don't have a crash log. 365 # We want to show this since it's not a regular crash and probab ly we don't have a crash log.
347 self.error_from_test += error_line 366 self.error_from_test += error_line
348 return True 367 return True
349 return self.has_crashed() 368 return self.has_crashed()
350 369
370 def _check_for_leak(self, error_line):
371 if error_line.startswith("#LEAK - "):
372 self._leaked = True
373 return self._leaked
374
351 def _command_from_driver_input(self, driver_input): 375 def _command_from_driver_input(self, driver_input):
352 # FIXME: performance tests pass in full URLs instead of test names. 376 # FIXME: performance tests pass in full URLs instead of test names.
353 if driver_input.test_name.startswith('http://') or driver_input.test_nam e.startswith('https://') or driver_input.test_name == ('about:blank'): 377 if driver_input.test_name.startswith('http://') or driver_input.test_nam e.startswith('https://') or driver_input.test_name == ('about:blank'):
354 command = driver_input.test_name 378 command = driver_input.test_name
355 elif self.is_http_test(driver_input.test_name): 379 elif self.is_http_test(driver_input.test_name):
356 command = self.test_to_uri(driver_input.test_name) 380 command = self.test_to_uri(driver_input.test_name)
357 else: 381 else:
358 command = self._port.abspath_for_test(driver_input.test_name) 382 command = self._port.abspath_for_test(driver_input.test_name)
359 if sys.platform == 'cygwin': 383 if sys.platform == 'cygwin':
360 command = path.cygpath(command) 384 command = path.cygpath(command)
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after
454 if content_length_before_header_check != block._content_length: 478 if content_length_before_header_check != block._content_length:
455 if block._content_length > 0: 479 if block._content_length > 0:
456 block.content = self._server_process.read_stdout(deadlin e, block._content_length) 480 block.content = self._server_process.read_stdout(deadlin e, block._content_length)
457 else: 481 else:
458 _log.error("Received content of type %s with Content-Len gth of 0! This indicates a bug in %s.", 482 _log.error("Received content of type %s with Content-Len gth of 0! This indicates a bug in %s.",
459 block.content_type, self._server_process.name ()) 483 block.content_type, self._server_process.name ())
460 484
461 if err_line: 485 if err_line:
462 if self._check_for_driver_crash(err_line): 486 if self._check_for_driver_crash(err_line):
463 break 487 break
488 self._check_for_leak(err_line)
464 self.error_from_test += err_line 489 self.error_from_test += err_line
465 490
466 block.decode_content() 491 block.decode_content()
467 return block 492 return block
468 493
469 494
470 class ContentBlock(object): 495 class ContentBlock(object):
471 def __init__(self): 496 def __init__(self):
472 self.content_type = None 497 self.content_type = None
473 self.encoding = None 498 self.encoding = None
474 self.content_hash = None 499 self.content_hash = None
475 self._content_length = None 500 self._content_length = None
476 # Content is treated as binary data even though the text output is usual ly UTF-8. 501 # Content is treated as binary data even though the text output is usual ly UTF-8.
477 self.content = str() # FIXME: Should be bytearray() once we require Pyt hon 2.6. 502 self.content = str() # FIXME: Should be bytearray() once we require Pyt hon 2.6.
478 self.decoded_content = None 503 self.decoded_content = None
479 self.malloc = None 504 self.malloc = None
480 self.js_heap = None 505 self.js_heap = None
481 506
482 def decode_content(self): 507 def decode_content(self):
483 if self.encoding == 'base64' and self.content is not None: 508 if self.encoding == 'base64' and self.content is not None:
484 self.decoded_content = base64.b64decode(self.content) 509 self.decoded_content = base64.b64decode(self.content)
485 else: 510 else:
486 self.decoded_content = self.content 511 self.decoded_content = self.content
OLDNEW
« no previous file with comments | « Tools/Scripts/webkitpy/layout_tests/port/base.py ('k') | Tools/Scripts/webkitpy/layout_tests/port/driver_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698