def run_and_throw_if_fail(self, args, quiet=False, decode_output=True, **kwargs): # Cache the child's output locally so it can be used for error reports. child_out_file = unicode_compatibility.StringIO() tee_stdout = sys.stdout try: if quiet: dev_null = open(os.devnull, "w") # FIXME: Does this need an encoding? tee_stdout = dev_null child_stdout = Tee(child_out_file, tee_stdout) exit_code = self._run_command_with_teed_output(args, child_stdout, **kwargs) finally: if quiet: dev_null.close() child_output = child_out_file.getvalue() child_out_file.close() if decode_output: child_output = unicode_compatibility.decode_if_necessary(child_output, self._child_process_encoding()) else: child_output = unicode_compatibility.encode_if_necessary(child_output, self._child_process_encoding()) if exit_code: raise ScriptError(script_args=args, exit_code=exit_code, output=child_output) return child_output
def generate_crash_log(self, stdout, stderr): pid_representation = str(self.pid or '<unknown>') log_directory = os.environ.get("WEBKIT_CORE_DUMPS_DIRECTORY") errors = [] crash_log = '' expected_crash_dump_filename = "core-pid_%s.dump" % pid_representation proc_name = "%s" % (self.name) def match_filename(filesystem, directory, filename): if self.pid: return filename == expected_crash_dump_filename return filename.find(self.name) > -1 # Poor man which, ignore any failure. for coredumpctl in [['coredumpctl'], ['flatpak-spawn', '--host', 'coredumpctl'], []]: try: if not self._executive.run_command(coredumpctl, return_exit_code=True): break except: continue if log_directory: dumps = self._filesystem.files_under( log_directory, file_filter=match_filename) if dumps: # Get the most recent coredump matching the pid and/or process name. coredump_path = list(reversed(sorted(dumps)))[0] if not self.newer_than or self._filesystem.mtime(coredump_path) > self.newer_than: crash_log, errors = self._get_gdb_output(coredump_path) elif coredumpctl: crash_log, errors = self._get_trace_from_systemd(coredumpctl, pid_representation) stderr_lines = errors + decode_if_necessary(str(stderr or '<empty>'), errors='ignore').splitlines() errors_str = '\n'.join(('STDERR: ' + stderr_line) for stderr_line in stderr_lines) cppfilt_proc = self._executive.popen( ['c++filt'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) errors_str = cppfilt_proc.communicate(errors_str)[0] if not crash_log: if not log_directory: log_directory = "/path/to/coredumps" core_pattern = self._filesystem.join(log_directory, "core-pid_%p.dump") crash_log = """\ Coredump %(expected_crash_dump_filename)s not found. To enable crash logs: - run this command as super-user: echo "%(core_pattern)s" > /proc/sys/kernel/core_pattern - enable core dumps: ulimit -c unlimited - set the WEBKIT_CORE_DUMPS_DIRECTORY environment variable: export WEBKIT_CORE_DUMPS_DIRECTORY=%(log_directory)s """ % locals() return (stderr, """\ crash log for %(proc_name)s (pid %(pid_representation)s): %(crash_log)s %(errors_str)s""" % locals())
def run_command(self, args, cwd=None, env=None, input=None, error_handler=None, ignore_errors=False, return_exit_code=False, return_stderr=True, decode_output=True): """Popen wrapper for convenience and to work around python bugs.""" assert(isinstance(args, list) or isinstance(args, tuple)) start_time = time.time() stdin, string_to_communicate = self._compute_stdin(input) stderr = self.STDOUT if return_stderr else None process = self.popen(args, stdin=stdin, stdout=self.PIPE, stderr=stderr, cwd=cwd, env=env, close_fds=self._should_close_fds()) with process: if not string_to_communicate: output = process.communicate()[0] else: output = process.communicate(unicode_compatibility.encode_if_necessary(string_to_communicate, 'utf-8'))[0] # run_command automatically decodes to unicode() and converts CRLF to LF unless explicitly told not to. if decode_output: output = unicode_compatibility.decode_if_necessary(output, self._child_process_encoding()).replace('\r\n', '\n') # wait() is not threadsafe and can throw OSError due to: # http://bugs.python.org/issue1731717 exit_code = process.wait() _log.debug('"%s" took %.2fs' % (self.command_for_printing(args), time.time() - start_time)) if return_exit_code: return exit_code if exit_code: script_error = ScriptError(script_args=args, exit_code=exit_code, output=output, cwd=cwd) if ignore_errors: assert error_handler is None, "don't specify error_handler if ignore_errors is True" error_handler = Executive.ignore_error (error_handler or self.default_error_handler)(script_error) return output
def test_find_all_log_darwin(self): if not SystemHost().platform.is_mac(): return crash_logs = self.create_crash_logs_darwin() all_logs = crash_logs.find_all_logs() self.assertEqual(len(all_logs), 8) for test, crash_log in all_logs.items(): self.assertTrue(crash_log in [unicode_compatibility.decode_if_necessary(value) for value in self.files.values()]) if test.split('-')[0] != 'Sandbox': self.assertTrue(test == "Unknown" or int(test.split("-")[1]) in range(28527, 28531))
def _get_trace_from_flatpak(self): if self.newer_than: coredump_since = "--gdb-stack-trace=@%f" % self.newer_than else: coredump_since = "--gdb-stack-trace" webkit_flatpak_path = self._webkit_finder.path_to_script('webkit-flatpak') cmd = ['flatpak-spawn', '--host', webkit_flatpak_path, '--%s' % self._port_name, "--%s" % self._configuration.lower(), coredump_since] proc = self._executive.popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) crash_log, stderr = proc.communicate() errors = decode_if_necessary(str(stderr or '<empty>'), errors='ignore').splitlines() return crash_log, errors
def test_run_command_with_unicode(self): """Validate that it is safe to pass unicode() objects to Executive.run* methods, and they will return unicode() objects by default unless decode_output=False""" unicode_tor_input = u"WebKit \u2661 Tor Arne Vestb\u00F8!" if sys.platform.startswith('win'): encoding = 'mbcs' else: encoding = 'utf-8' encoded_tor = unicode_compatibility.encode_if_necessary( unicode_tor_input, encoding) # On Windows, we expect the unicode->mbcs->unicode roundtrip to be # lossy. On other platforms, we expect a lossless roundtrip. if sys.platform.startswith('win'): unicode_tor_output = unicode_compatibility.decode_if_necessary( encoded_tor, encoding) else: unicode_tor_output = unicode_tor_input executive = Executive() output = executive.run_command(command_line('cat'), input=unicode_tor_input) self.assertEqual(output, unicode_tor_output) output = executive.run_command(command_line('echo', unicode_tor_input)) self.assertEqual(output, unicode_tor_output) output = executive.run_command(command_line('echo', unicode_tor_input), decode_output=False) self.assertEqual(output, encoded_tor) # Make sure that str() input also works. output = executive.run_command(command_line('cat'), input=encoded_tor, decode_output=False) self.assertEqual(output, encoded_tor) # FIXME: We should only have one run* method to test output = executive.run_and_throw_if_fail(command_line( 'echo', unicode_tor_input), quiet=True) self.assertEqual(output, unicode_tor_output) output = executive.run_and_throw_if_fail(command_line( 'echo', unicode_tor_input), quiet=True, decode_output=False) self.assertEqual(output, encoded_tor)
def _find_newest_log_win(self, process_name, pid, include_errors, newer_than): def is_crash_log(fs, dirpath, basename): if self._crash_logs_to_skip and fs.join( dirpath, basename) in self._crash_logs_to_skip: return False return basename.startswith("CrashLog") logs = self._host.filesystem.files_under(self._crash_log_directory, file_filter=is_crash_log) errors = u'' for path in reversed(sorted(logs)): try: if not newer_than or self._host.filesystem.mtime( path) > newer_than: log_file = unicode_compatibility.decode_if_necessary( self._host.filesystem.read_binary_file(path), 'ascii', 'ignore') match = self.GLOBAL_PID_REGEX.search(log_file) if match: if int(match.group('pid')) == pid: return errors + log_file match = self.EXIT_PROCESS_PID_REGEX.search(log_file) if match is None: continue # Note: This output comes from a program that shows PID in hex: if int(match.group('pid'), 16) == pid: return errors + log_file except IOError as e: if include_errors: errors += u"ERROR: Failed to read '%s': %s\n" % (path, str(e)) except OSError as e: if include_errors: errors += u"ERROR: Failed to read '%s': %s\n" % (path, str(e)) except UnicodeDecodeError as e: if include_errors: errors += u"ERROR: Failed to decode '%s' as ascii: %s\n" % ( path, str(e)) if include_errors and errors: return errors return None
def command_for_printing(self, args): """Returns a print-ready string representing command args. The string should be copy/paste ready for execution in a shell.""" args = self._stringify_args(args) return unicode_compatibility.decode_if_necessary(unicode_compatibility.encode_if_necessary(' '.join(args), 'unicode_escape'))
def write_text_file(self, path, contents, errors='strict'): """Write the contents to the file at the given location. The file is written encoded as UTF-8 with no BOM.""" with codecs.open(path, 'w', 'utf-8', errors=errors) as f: f.write(decode_if_necessary(contents, errors=errors))