Changeset 91245 in webkit


Ignore:
Timestamp:
Jul 19, 2011 2:25:46 AM (13 years ago)
Author:
eric@webkit.org
Message:

new-run-webkit-tests should support --leaks
https://bugs.webkit.org/show_bug.cgi?id=63832

Reviewed by Dirk Pranke.

This may not be sufficient to actually transition over the leaks bot,
but this is a huge step in the right direction.

I had to make parse-malloc-history understand being passed more than
one file (to avoid the silly cat | nonsense in old-run-webkit-tests).

I removed some dead code relating to previous iterations of our crash detection.

I created a new class "LeakDetector" to encapsulate all this logic.
Eventually we should consider pulling that class out of mac.py and
sharing with other ports. However given that ORWT has had
--leaks support on Mac for almost 7 years and no other port has added
it, leaves me to believe we're in no rush to move LeakDetector.

I've tested --leaks locally. I suspect there are more bugs to shake out
but it seems to work well enough to start.

I also added support for --guard-malloc, but have not tested it much. It
should be viewed as experimental at this time.

I also fixed various os.path uses to self._filesystem as I was reading
through the various files to understand how best to fix this bug.

  • Scripts/old-run-webkit-tests:

(parseLeaksandPrintUniqueLeaks):

  • Scripts/parse-malloc-history:

(main):

  • Scripts/webkitpy/common/system/crashlogs.py:
  • Scripts/webkitpy/layout_tests/controllers/manager.py:
  • Scripts/webkitpy/layout_tests/controllers/worker.py:
  • Scripts/webkitpy/layout_tests/port/base.py:
  • Scripts/webkitpy/layout_tests/port/chromium_win.py:
  • Scripts/webkitpy/layout_tests/port/gtk.py:
  • Scripts/webkitpy/layout_tests/port/mac.py:
  • Scripts/webkitpy/layout_tests/port/mac_unittest.py:
  • Scripts/webkitpy/layout_tests/port/server_process.py:
  • Scripts/webkitpy/layout_tests/port/webkit.py:
  • Scripts/webkitpy/layout_tests/run_webkit_tests.py:
Location:
trunk/Tools
Files:
16 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r91241 r91245  
     12011-07-18  Eric Seidel  <eric@webkit.org>
     2
     3        new-run-webkit-tests should support --leaks
     4        https://bugs.webkit.org/show_bug.cgi?id=63832
     5
     6        Reviewed by Dirk Pranke.
     7
     8        This may not be sufficient to actually transition over the leaks bot,
     9        but this is a huge step in the right direction.
     10
     11        I had to make parse-malloc-history understand being passed more than
     12        one file (to avoid the silly cat | nonsense in old-run-webkit-tests).
     13
     14        I removed some dead code relating to previous iterations of our crash detection.
     15
     16        I created a new class "LeakDetector" to encapsulate all this logic.
     17        Eventually we should consider pulling that class out of mac.py and
     18        sharing with other ports.  However given that ORWT has had
     19        --leaks support on Mac for almost 7 years and no other port has added
     20        it, leaves me to believe we're in no rush to move LeakDetector.
     21
     22        I've tested --leaks locally.  I suspect there are more bugs to shake out
     23        but it seems to work well enough to start.
     24
     25        I also added support for --guard-malloc, but have not tested it much.  It
     26        should be viewed as experimental at this time.
     27
     28        I also fixed various os.path uses to self._filesystem as I was reading
     29        through the various files to understand how best to fix this bug.
     30
     31        * Scripts/old-run-webkit-tests:
     32        (parseLeaksandPrintUniqueLeaks):
     33        * Scripts/parse-malloc-history:
     34        (main):
     35        * Scripts/webkitpy/common/system/crashlogs.py:
     36        * Scripts/webkitpy/layout_tests/controllers/manager.py:
     37        * Scripts/webkitpy/layout_tests/controllers/worker.py:
     38        * Scripts/webkitpy/layout_tests/port/base.py:
     39        * Scripts/webkitpy/layout_tests/port/chromium_win.py:
     40        * Scripts/webkitpy/layout_tests/port/gtk.py:
     41        * Scripts/webkitpy/layout_tests/port/mac.py:
     42        * Scripts/webkitpy/layout_tests/port/mac_unittest.py:
     43        * Scripts/webkitpy/layout_tests/port/server_process.py:
     44        * Scripts/webkitpy/layout_tests/port/webkit.py:
     45        * Scripts/webkitpy/layout_tests/run_webkit_tests.py:
     46
    1472011-07-19  Adam Barth  <abarth@webkit.org>
    248
  • trunk/Tools/Scripts/old-run-webkit-tests

    r90125 r91245  
    22422242{
    22432243    return unless @leaksFilenames;
    2244      
     2244
    22452245    my $mergedFilenames = join " ", @leaksFilenames;
    22462246    my $parseMallocHistoryTool = sourceDir() . "/Tools/Scripts/parse-malloc-history";
  • trunk/Tools/Scripts/parse-malloc-history

    r58350 r91245  
    5858        "merge-depth:i" => \$mergeDepth
    5959    );
    60     my $fileName = $ARGV[0];
    61     die $usage if (!$getOptionsResult || !$fileName);
     60    die $usage if (!$getOptionsResult || !scalar(@ARGV));
    6261
    63     open FILE, "<$fileName" or die "bad file: $fileName";
    64     my @file = <FILE>;
    65     close FILE;
     62    my @lines = ();
     63    foreach my $fileName (@ARGV) {
     64        open FILE, "<$fileName" or die "bad file: $fileName";
     65        push(@lines, <FILE>);
     66        close FILE;
     67    }
    6668
    6769    my %callstacks = ();
    6870    my $byteCountTotal = 0;
    6971
    70     for (my $i = 0; $i < @file; $i++) {
    71         my $line = $file[$i];
     72    for (my $i = 0; $i < @lines; $i++) {
     73        my $line = $lines[$i];
    7274        my ($callCount, $byteCount);
    7375
     
    8789                while (!($line =~ "Call stack: ")) {
    8890                    $i++;
    89                     $line = $file[$i];
     91                    $line = $lines[$i];
    9092                }
    9193            }
     
    100102            ($byteCount) = ($line =~ /Key: (?:\d+), (\d+) bytes/);
    101103            if ($byteCount) {
    102                 $line = $file[++$i];
     104                $line = $lines[++$i];
    103105                my @tempStack;
    104                 while ($file[$i+1] !~ /^(?:-|\d)/) {
     106                while ($lines[$i+1] !~ /^(?:-|\d)/) {
    105107                    if ($line =~ /\): (.*)$/) {
    106108                        my $call = $1;
     
    108110                        unshift(@tempStack, $call);
    109111                    }
    110                     $line = $file[++$i];
     112                    $line = $lines[++$i];
    111113                }           
    112114                $line = join(" | ", @tempStack);
  • trunk/Tools/Scripts/webkitpy/common/system/crashlogs.py

    r89899 r91245  
    2727# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    2828
    29 import os
    3029import re
    3130import sys
    32 
    33 
    34 def _is_crash_reporter(process_name):
    35     return re.match(r"ReportCrash", process_name)
    3631
    3732
     
    4641    def _log_directory_darwin(self):
    4742        log_directory = self._filesystem.expanduser("~")
    48         log_directory = os.path.join(log_directory, "Library", "Logs")
    49         if self._filesystem.exists(os.path.join(log_directory, "DiagnosticReports")):
    50             log_directory = os.path.join(log_directory, "DiagnosticReports")
     43        log_directory = self._filesystem.join(log_directory, "Library", "Logs")
     44        if self._filesystem.exists(self._filesystem.join(log_directory, "DiagnosticReports")):
     45            log_directory = self._filesystem.join(log_directory, "DiagnosticReports")
    5146        else:
    52             log_directory = os.path.join(log_directory, "CrashReporter")
     47            log_directory = self._filesystem.join(log_directory, "CrashReporter")
    5348        return log_directory
    5449
     
    6055        logs = self._filesystem.files_under(log_directory, file_filter=is_crash_log)
    6156        if not logs:
    62             return
     57            return None
    6358        return self._filesystem.read_text_file(sorted(logs)[-1])
  • trunk/Tools/Scripts/webkitpy/layout_tests/controllers/manager.py

    r91210 r91245  
    494494            skip_chunk = skipped
    495495
    496         result_summary = ResultSummary(self._expectations,
    497             self._test_files | skip_chunk)
    498         self._print_expected_results_of_type(result_summary,
    499             test_expectations.PASS, "passes")
    500         self._print_expected_results_of_type(result_summary,
    501             test_expectations.FAIL, "failures")
    502         self._print_expected_results_of_type(result_summary,
    503             test_expectations.FLAKY, "flaky")
    504         self._print_expected_results_of_type(result_summary,
    505             test_expectations.SKIP, "skipped")
     496        result_summary = ResultSummary(self._expectations, self._test_files | skip_chunk)
     497        self._print_expected_results_of_type(result_summary, test_expectations.PASS, "passes")
     498        self._print_expected_results_of_type(result_summary, test_expectations.FAIL, "failures")
     499        self._print_expected_results_of_type(result_summary, test_expectations.FLAKY, "flaky")
     500        self._print_expected_results_of_type(result_summary, test_expectations.SKIP, "skipped")
    506501
    507502        if self._options.force:
     
    720715
    721716        self._printer.print_update('Sharding tests ...')
    722         locked_shards, unlocked_shards = self._shard_tests(file_list,
    723             int(self._options.child_processes), self._options.experimental_fully_parallel)
     717        locked_shards, unlocked_shards = self._shard_tests(file_list, int(self._options.child_processes), self._options.experimental_fully_parallel)
    724718
    725719        # FIXME: We don't have a good way to coordinate the workers so that
     
    739733        self._log_num_workers(num_workers, len(all_shards), len(locked_shards))
    740734
    741         manager_connection = manager_worker_broker.get(self._port, self._options,
    742                                                        self, worker.Worker)
     735        manager_connection = manager_worker_broker.get(self._port, self._options, self, worker.Worker)
    743736
    744737        if self._options.dry_run:
    745             return (keyboard_interrupted, interrupted, thread_timings,
    746                     self._group_stats, self._all_results)
    747 
    748         self._printer.print_update('Starting %s ...' %
    749                                    grammar.pluralize('worker', num_workers))
     738            return (keyboard_interrupted, interrupted, thread_timings, self._group_stats, self._all_results)
     739
     740        self._printer.print_update('Starting %s ...' % grammar.pluralize('worker', num_workers))
    750741        for worker_number in xrange(num_workers):
    751742            worker_connection = manager_connection.start_worker(worker_number)
     
    773764        try:
    774765            while not self.is_done():
    775                 # Temporarily disabled to see how this code effect performance on the buildbots.
    776                 # if self._port.executive.running_pids(self._port.is_crash_reporter):
    777                 #     self._printer.print_update("Waiting for crash reporter ...")
    778                 #     self._port.executive.wait_newest(self._port.is_crash_reporter)
    779766                manager_connection.run_message_loop(delay_secs=1.0)
    780767
     
    809796
    810797        # FIXME: should this be a class instead of a tuple?
    811         return (interrupted, keyboard_interrupted, thread_timings,
    812                 self._group_stats, self._all_results)
     798        return (interrupted, keyboard_interrupted, thread_timings, self._group_stats, self._all_results)
    813799
    814800    def update(self):
     
    903889        end_time = time.time()
    904890
    905         self._print_timing_statistics(end_time - start_time,
    906                                       thread_timings, test_timings,
    907                                       individual_test_timings,
    908                                       result_summary)
    909 
     891        self._print_timing_statistics(end_time - start_time, thread_timings, test_timings, individual_test_timings, result_summary)
    910892        self._print_result_summary(result_summary)
    911893
     
    913895        sys.stderr.flush()
    914896
    915         self._printer.print_one_line_summary(result_summary.total,
    916                                              result_summary.expected,
    917                                              result_summary.unexpected)
     897        self._printer.print_one_line_summary(result_summary.total, result_summary.expected, result_summary.unexpected)
    918898
    919899        unexpected_results = summarize_results(self._port, self._expectations, result_summary, retry_summary, individual_test_timings, only_unexpected=True, interrupted=interrupted)
     
    926906        # FIXME: remove record_results. It's just used for testing. There's no need
    927907        # for it to be a commandline argument.
    928         if (self._options.record_results and not self._options.dry_run and
    929             not keyboard_interrupted):
    930             # Write the same data to log files and upload generated JSON files
    931             # to appengine server.
     908        if (self._options.record_results and not self._options.dry_run and not keyboard_interrupted):
     909            self._port.print_leaks_summary()
     910            # Write the same data to log files and upload generated JSON files to appengine server.
    932911            summarized_results = summarize_results(self._port, self._expectations, result_summary, retry_summary, individual_test_timings, only_unexpected=False, interrupted=interrupted)
    933912            self._upload_json_files(summarized_results, result_summary, individual_test_timings)
  • trunk/Tools/Scripts/webkitpy/layout_tests/controllers/worker.py

    r90796 r91245  
    121121
    122122    def _run_test(self, test_input):
    123         # Before running the test, we wait for any crash reporters to finish
    124         # running. On Mac, ReportCrash chews up a bunch of resources and
    125         # causes the tests to become unstable, so we don't want to run in
    126         # parallel with ReportCrash.
    127         #
    128         # Temporarily disabled to see how this code effect performance on the buildbots.
    129         # self._port.executive.wait_newest(self._port.is_crash_reporter)
    130 
    131123        test_timeout_sec = self.timeout(test_input)
    132124        start = time.time()
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/base.py

    r91059 r91245  
    289289        return False
    290290
     291    def check_for_leaks(self, process_name, process_pid):
     292        # Subclasses should check for leaks in the running process
     293        # and print any necessary warnings if leaks are found.
     294        # FIXME: We should consider moving much of this logic into
     295        # Executive and make it platform-specific instead of port-specific.
     296        pass
     297
     298    def print_leaks_summary(self):
     299        # Subclasses can override this to print a summary of leaks found
     300        # while running the layout tests.
     301        pass
     302
    291303    def driver_name(self):
    292304        """Returns the name of the actual binary that is performing the test,
     
    853865            # output and stop trying.
    854866            self._pretty_patch_available = False
    855             _log.error("Failed to run PrettyPatch (%s):\n%s" % (command,
    856                        e.message_with_output()))
     867            _log.error("Failed to run PrettyPatch (%s):\n%s" % (command, e.message_with_output()))
    857868            return self._pretty_patch_error_html
    858869
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/chromium_win.py

    r91215 r91245  
    118118        result = chromium.ChromiumPort.check_build(self, needs_http)
    119119        if not result:
    120             _log.error('For complete Windows build requirements, please '
    121                        'see:')
     120            _log.error('For complete Windows build requirements, please see:')
    122121            _log.error('')
    123             _log.error('    http://dev.chromium.org/developers/how-tos/'
    124                        'build-instructions-windows')
     122            _log.error('    http://dev.chromium.org/developers/how-tos/build-instructions-windows')
    125123        return result
    126124
     
    134132    def _build_path(self, *comps):
    135133        if self.get_option('build_directory'):
    136             return self._filesystem.join(self.get_option('build_directory'),
    137                                          *comps)
     134            return self._filesystem.join(self.get_option('build_directory'), *comps)
    138135
    139136        p = self.path_from_chromium_base('webkit', *comps)
     
    149146
    150147    def _lighttpd_path(self, *comps):
    151         return self.path_from_chromium_base('third_party', 'lighttpd', 'win',
    152                                             *comps)
     148        return self.path_from_chromium_base('third_party', 'lighttpd', 'win', *comps)
    153149
    154150    def _path_to_apache(self):
    155         return self.path_from_chromium_base('third_party', 'cygwin', 'usr',
    156                                             'sbin', 'httpd')
     151        return self.path_from_chromium_base('third_party', 'cygwin', 'usr', 'sbin', 'httpd')
    157152
    158153    def _path_to_apache_config_file(self):
     
    183178
    184179    def _path_to_wdiff(self):
    185         return self.path_from_chromium_base('third_party', 'cygwin', 'bin',
    186                                             'wdiff.exe')
     180        return self.path_from_chromium_base('third_party', 'cygwin', 'bin', 'wdiff.exe')
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/gtk.py

    r90826 r91245  
    4848        # We must do this here because the DISPLAY number depends on _worker_number
    4949        environment['DISPLAY'] = ":%d" % (display_id)
    50         self._server_process = server_process.ServerProcess(self._port,
    51             self._port.driver_name(), self.cmd_line(), environment)
     50        self._server_process = server_process.ServerProcess(self._port, self._port.driver_name(), self.cmd_line(), environment)
    5251
    5352    def stop(self):
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/mac.py

    r90826 r91245  
    3333import re
    3434
     35from webkitpy.common.system.executive import ScriptError
    3536from webkitpy.layout_tests.port.webkit import WebKitPort
    3637
    3738
    3839_log = logging.getLogger(__name__)
     40
     41
     42# If other ports/platforms decide to support --leaks, we should see about sharing as much of this code as possible.
     43class LeakDetector(object):
     44    def __init__(self, port):
     45        # We should operate on a "platform" not a port here.
     46        self._port = port
     47        self._executive = port._executive
     48        self._filesystem = port._filesystem
     49
     50    # We exclude the following reported leaks so they do not get in our way when looking for WebKit leaks:
     51    # This allows us ignore known leaks and only be alerted when new leaks occur. Some leaks are in the old
     52    # versions of the system frameworks that are being used by the leaks bots. Even though a leak has been
     53    # fixed, it will be listed here until the bot has been updated with the newer frameworks.
     54    def _types_to_exlude_from_leaks(self):
     55        # Currently we don't have any type excludes from OS leaks, but we will likely again in the future.
     56        return []
     57
     58    def _callstacks_to_exclude_from_leaks(self):
     59        callstacks = [
     60            "Flash_EnforceLocalSecurity",  # leaks in Flash plug-in code, rdar://problem/4449747
     61        ]
     62        if self._port.is_leopard():
     63            callstacks += [
     64                "CFHTTPMessageAppendBytes",  # leak in CFNetwork, rdar://problem/5435912
     65                "sendDidReceiveDataCallback",  # leak in CFNetwork, rdar://problem/5441619
     66                "_CFHTTPReadStreamReadMark",  # leak in CFNetwork, rdar://problem/5441468
     67                "httpProtocolStart",  # leak in CFNetwork, rdar://problem/5468837
     68                "_CFURLConnectionSendCallbacks",  # leak in CFNetwork, rdar://problem/5441600
     69                "DispatchQTMsg",  # leak in QuickTime, PPC only, rdar://problem/5667132
     70                "QTMovieContentView createVisualContext",  # leak in QuickTime, PPC only, rdar://problem/5667132
     71                "_CopyArchitecturesForJVMVersion",  # leak in Java, rdar://problem/5910823
     72            ]
     73        elif self._port.is_snowleopard():
     74            callstacks += [
     75                "readMakerNoteProps",  # <rdar://problem/7156432> leak in ImageIO
     76                "QTKitMovieControllerView completeUISetup",  # <rdar://problem/7155156> leak in QTKit
     77                "getVMInitArgs",  # <rdar://problem/7714444> leak in Java
     78                "Java_java_lang_System_initProperties",  # <rdar://problem/7714465> leak in Java
     79                "glrCompExecuteKernel",  # <rdar://problem/7815391> leak in graphics driver while using OpenGL
     80                "NSNumberFormatter getObjectValue:forString:errorDescription:",  # <rdar://problem/7149350> Leak in NSNumberFormatter
     81            ]
     82        return callstacks
     83
     84    def _leaks_args(self, pid):
     85        leaks_args = []
     86        for callstack in self._callstacks_to_exclude_from_leaks():
     87            leaks_args += ['--exclude-callstack="%s"' % callstack]  # Callstacks can have spaces in them, so we quote the arg to prevent confusing perl's optparse.
     88        for excluded_type in self._types_to_exlude_from_leaks():
     89            leaks_args += ['--exclude-type="%s"' % excluded_type]
     90        leaks_args.append(pid)
     91        return leaks_args
     92
     93    def _parse_leaks_output(self, leaks_output, process_pid):
     94        count, bytes = re.search(r'Process %s: (\d+) leaks? for (\d+) total' % process_pid, leaks_output).groups()
     95        excluded_match = re.search(r'(\d+) leaks? excluded', leaks_output)
     96        excluded = excluded_match.group(0) if excluded_match else 0
     97        return int(count), int(excluded), int(bytes)
     98
     99    def leaks_files_in_directory(self, directory):
     100        return self._filesystem.glob(self._filesystem.join(directory, "leaks-*"))
     101
     102    def leaks_file_name(self, process_name, process_pid):
     103        # We include the number of files this worker has already written in the name to prevent overwritting previous leak results..
     104        return "leaks-%s-%s.txt" % (process_name, process_pid)
     105
     106    def parse_leak_files(self, leak_files):
     107        merge_depth = 5  # ORWT had a --merge-leak-depth argument, but that seems out of scope for the run-webkit-tests tool.
     108        args = [
     109            '--merge-depth',
     110            merge_depth,
     111        ] + leak_files
     112        parse_malloc_history_output = self._port._run_script("parse-malloc-history", args, include_configuration_arguments=False)
     113
     114        unique_leak_count = len(re.findall(r'^(\d*)\scalls', parse_malloc_history_output))
     115        total_bytes = int(re.search(r'^total\:\s(.*)\s\(', parse_malloc_history_output).group(1))
     116        return (total_bytes, unique_leak_count)
     117
     118    def check_for_leaks(self, process_name, process_pid):
     119        _log.debug("Checking for leaks in %s" % process_name)
     120        try:
     121            leaks_output = self._port._run_script("run-leaks", self._leaks_args(process_pid), include_configuration_arguments=False)
     122        except ScriptError, e:
     123            _log.warn("Failed to run leaks tool: %s" % e.message_with_output())
     124            return
     125
     126        count, excluded, bytes = self._parse_leaks_output(leaks_output, process_pid)
     127        adjusted_count = count - excluded
     128        if not adjusted_count:
     129            return
     130
     131        leaks_filename = self.leaks_file_name(process_name, process_pid)
     132        leaks_output_path = self._filesystem.join(self._port.results_directory(), leaks_filename)
     133        self._filesystem.write_text_file(leaks_output_path, leaks_output)
     134
     135        # FIXME: Ideally we would not be logging from the worker process, but rather pass the leak
     136        # information back to the manager and have it log.
     137        if excluded:
     138            _log.info("%s leaks (%s bytes including %s excluded leaks) were found, details in %s" % (adjusted_count, bytes, excluded, leaks_output_path))
     139        else:
     140            _log.info("%s leaks (%s bytes) were found, details in %s" % (count, bytes, leaks_output_path))
    39141
    40142
     
    93195            assert self._version in self.SUPPORTED_VERSIONS, "%s is not in %s" % (self._version, self.SUPPORTED_VERSIONS)
    94196        self._operating_system = 'mac'
     197        self._leak_detector = LeakDetector(self)
    95198
    96199    def baseline_search_path(self):
     
    100203        return map(self._webkit_baseline_path, search_paths)
    101204
     205    def setup_environ_for_server(self):
     206        env = WebKitPort.setup_environ_for_server(self)
     207        if self.get_option('leaks'):
     208            env['MallocStackLogging'] = '1'
     209        if self.get_option('guard_malloc'):
     210            env['DYLD_INSERT_LIBRARIES'] = '/usr/lib/libgmalloc.dylib'
     211        return env
     212
     213    # Belongs on a Platform object.
     214    def is_leopard(self):
     215        return self._version == "leopard"
     216
     217    # Belongs on a Platform object.
     218    def is_snowleopard(self):
     219        return self._version == "snowleopard"
     220
     221    # Belongs on a Platform object.
    102222    def is_crash_reporter(self, process_name):
    103223        return re.search(r'ReportCrash', process_name)
     
    106226        java_tests_path = self._filesystem.join(self.layout_tests_dir(), "java")
    107227        build_java = ["/usr/bin/make", "-C", java_tests_path]
    108         if self._executive.run_command(build_java, return_exit_code=True):
     228        if self._executive.run_command(build_java, return_exit_code=True):  # Paths are absolute, so we don't need to set a cwd.
    109229            _log.error("Failed to build Java support files: %s" % build_java)
    110230            return False
    111231        return True
    112232
     233    def check_for_leaks(self, process_name, process_pid):
     234        if not self.get_option('leaks'):
     235            return
     236        # We could use http://code.google.com/p/psutil/ to get the process_name from the pid.
     237        self._leak_detector.check_for_leaks(process_name, process_pid)
     238
     239    def print_leaks_summary(self):
     240        if not self.get_option('leaks'):
     241            return
     242        # We're in the manager process, so the leak detector will not have a valid list of leak files.
     243        # FIXME: This is a hack, but we don't have a better way to get this information from the workers yet.
     244        leaks_files = self._leak_detector.leaks_files_in_directory(self.results_directory())
     245        if not leaks_files:
     246            return
     247        total_bytes, unique_leaks = self.parse_leak_files(leaks_files)
     248        _log.info("%s total leaks found for a total of %s!" % (self._total_leaks, total_bytes))
     249        _log.info("%s unique leaks found!" % unique_leaks)
     250
    113251    def _check_port_build(self):
    114252        return self._build_java_test_support()
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/mac_unittest.py

    r91210 r91245  
    3131import unittest
    3232
    33 from webkitpy.layout_tests.port.mac import MacPort
     33from webkitpy.layout_tests.port.mac import MacPort, LeakDetector
    3434from webkitpy.layout_tests.port import port_testcase
    3535from webkitpy.common.system.filesystem_mock import MockFileSystem
    3636from webkitpy.common.system.outputcapture import OutputCapture
    3737from webkitpy.tool.mocktool import MockOptions, MockUser, MockExecutive
     38
     39
     40class LeakDetectorTest(unittest.TestCase):
     41    def _mock_port(self):
     42        class MockPort(object):
     43            def __init__(self):
     44                self._filesystem = MockFileSystem()
     45                self._executive = MockExecutive()
     46
     47        return MockPort()
     48
     49    def _make_detector(self):
     50        return LeakDetector(self._mock_port())
     51
     52    def test_leaks_args(self):
     53        detector = self._make_detector()
     54        detector._callstacks_to_exclude_from_leaks = lambda: ['foo bar', 'BAZ']
     55        detector._types_to_exlude_from_leaks = lambda: ['abcdefg', 'hi jklmno']
     56        expected_args = ['--exclude-callstack="foo bar"', '--exclude-callstack="BAZ"', '--exclude-type="abcdefg"', '--exclude-type="hi jklmno"', 1234]
     57        self.assertEquals(detector._leaks_args(1234), expected_args)
     58
     59    example_leaks_output = """Process 5122: 663744 nodes malloced for 78683 KB
     60Process 5122: 337301 leaks for 6525216 total leaked bytes.
     61Leak: 0x38cb600  size=3072  zone: DefaultMallocZone_0x1d94000   instance of 'NSCFData', type ObjC, implemented in Foundation
     62        0xa033f0b8 0x01001384 0x00000b3a 0x00000b3a     ..3.....:...:...
     63        0x00000000 0x038cb620 0x00000000 0x00000000     .... ...........
     64        0x00000000 0x21000000 0x726c6468 0x00000000     .......!hdlr....
     65        0x00000000 0x7269646d 0x6c707061 0x00000000     ....mdirappl....
     66        0x00000000 0x04000000 0x736c69c1 0x00000074     .........ilst...
     67        0x6f74a923 0x0000006f 0x7461641b 0x00000061     #.too....data...
     68        0x00000001 0x76614c00 0x2e323566 0x302e3236     .....Lavf52.62.0
     69        0x37000000 0x6d616ea9 0x2f000000 0x61746164     ...7.nam.../data
     70        ...
     71Leak: 0x2a9c960  size=288  zone: DefaultMallocZone_0x1d94000
     72        0x09a1cc47 0x1bda8560 0x3d472cd1 0xfbe9bccd     G...`....,G=....
     73        0x8bcda008 0x9e972a91 0xa892cf63 0x2448bdb0     .....*..c.....H$
     74        0x4736fc34 0xdbe2d94e 0x25f56688 0x839402a4     4.6GN....f.%....
     75        0xd12496b3 0x59c40c12 0x8cfcab2a 0xd20ef9c4     ..$....Y*.......
     76        0xe7c56b1b 0x5835af45 0xc69115de 0x6923e4bb     .k..E.5X......#i
     77        0x86f15553 0x15d40fa9 0x681288a4 0xc33298a9     SU.........h..2.
     78        0x439bb535 0xc4fc743d 0x7dfaaff8 0x2cc49a4a     5..C=t.....}J..,
     79        0xdd119df8 0x7e086821 0x3d7d129e 0x2e1b1547     ....!h.~..}=G...
     80        ...
     81Leak: 0x25102fe0  size=176  zone: DefaultMallocZone_0x1d94000   string 'NSException Data'
     82"""
     83
     84    def test_parse_leaks_output(self):
     85        self.assertEquals(self._make_detector()._parse_leaks_output(self.example_leaks_output, 5122), (337301, 0, 6525216))
     86
     87    def test_leaks_files_in_directory(self):
     88        detector = self._make_detector()
     89        self.assertEquals(detector.leaks_files_in_directory('/bogus-directory'), [])
     90        detector._filesystem = MockFileSystem({
     91            '/mock-results/leaks-DumpRenderTree-0-1.txt': '',
     92            '/mock-results/leaks-DumpRenderTree-1-1.txt': '',
     93            '/mock-results/leaks-DumpRenderTree-0-2.txt': '',
     94        })
     95        self.assertEquals(len(detector.leaks_files_in_directory('/mock-results')), 3)
     96
     97    def test_parse_leak_files(self):
     98        detector = self._make_detector()
     99
     100        def mock_run_script(name, args, include_configuration_arguments=False):
     101            print "MOCK _run_script: %s %s" % (name, args)
     102            return "total: 12345 ("
     103        detector._port._run_script = mock_run_script
     104
     105        leak_files = ['/mock-results/leaks-DumpRenderTree-1234.txt', '/mock-results/leaks-DumpRenderTree-1235.txt']
     106        expected_stdout = "MOCK _run_script: parse-malloc-history ['--merge-depth', 5, '/mock-results/leaks-DumpRenderTree-1234.txt', '/mock-results/leaks-DumpRenderTree-1235.txt']\n"
     107        results_tuple = OutputCapture().assert_outputs(self, detector.parse_leak_files, [leak_files], expected_stdout=expected_stdout)
     108        self.assertEquals(results_tuple, (12345, 0))
    38109
    39110
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/server_process.py

    r90534 r91245  
    5555    def __init__(self, port_obj, name, cmd, env=None, executive=Executive()):
    5656        self._port = port_obj
    57         self._name = name
     57        self._name = name  # Should be the command name (e.g. DumpRenderTree, ImageDiff)
    5858        self._cmd = cmd
    5959        self._env = env
    6060        self._reset()
    6161        self._executive = executive
     62
     63    def name(self):
     64        return self._name
     65
     66    def pid(self):
     67        return self._proc.pid
    6268
    6369    def _reset(self):
     
    217223
    218224            # Nope - wait for more data.
    219             (read_fds, write_fds, err_fds) = select.select(select_fds, [],
    220                                                            select_fds,
    221                                                            deadline - now)
     225            (read_fds, write_fds, err_fds) = select.select(select_fds, [], select_fds, deadline - now)
    222226            try:
    223227                if out_fd in read_fds:
     
    232236        if not self._proc:
    233237            return
     238
     239        self._port.check_for_leaks(self.name(), self.pid())
    234240
    235241        pid = self._proc.pid
     
    249255                time.sleep(0.1)
    250256            if self._proc.poll() is None:
    251                 _log.warning('stopping %s timed out, killing it' %
    252                              self._name)
     257                _log.warning('stopping %s timed out, killing it' % self._name)
    253258                self._executive.kill_process(self._proc.pid)
    254259                _log.warning('killed')
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/server_process_unittest.py

    r91210 r91245  
    4848        return "/mock-results"
    4949
     50    def check_for_leaks(self, process_name, process_pid):
     51        pass
     52
    5053
    5154class MockFile(object):
     
    8487class TestServerProcess(unittest.TestCase):
    8588    def test_broken_pipe(self):
    86         server_process = FakeServerProcess(port_obj=None, name="test", cmd=["test"])
     89        server_process = FakeServerProcess(port_obj=TrivialMockPort(), name="test", cmd=["test"])
    8790        server_process.write("should break")
    8891        self.assertTrue(server_process.crashed)
  • trunk/Tools/Scripts/webkitpy/layout_tests/port/webkit.py

    r90826 r91245  
    5656        self.set_option_default("pixel_tests", False)
    5757        # WebKit ports expect a 35s timeout, or 350s timeout when running with -g/--guard-malloc.
    58         self.set_option_default("time_out_ms", 35000)
     58        # FIXME: --guard-malloc is only supported on Mac, so this logic should be in mac.py.
     59        default_time_out_seconds = 350 if self.get_option('guard_malloc') else 35
     60        self.set_option_default("time_out_ms", default_time_out_seconds * 1000)
    5961
    6062    def driver_name(self):
     
    116118        if args:
    117119            run_script_command.extend(args)
    118         return self._executive.run_command(run_script_command, cwd=self._config.webkit_base_dir())  # It's unclear if setting cwd is necessary for all callers.
     120        return self._executive.run_command(run_script_command, cwd=self._config.webkit_base_dir())
    119121
    120122    def _build_driver(self):
     
    451453    def poll(self):
    452454        return self._server_process.poll()
    453 
    454     def restart(self):
    455         self._server_process.stop()
    456         self._server_process.start()
    457         return
    458455
    459456    def detected_crash(self):
  • trunk/Tools/Scripts/webkitpy/layout_tests/run_webkit_tests.py

    r91136 r91245  
    242242        optparse.make_option("--complex-text", action="store_true", default=False,
    243243            help="Use the complex text code path for all text (Mac OS X and Windows only)"),
     244        optparse.make_option("-l", "--leaks", action="store_true", default=False,
     245            help="Enable leaks checking (Mac OS X only)"),
     246        optparse.make_option("-g", "--guard-malloc", action="store_true", default=False,
     247            help="Enable malloc guard (Mac OS X only)"),
    244248        optparse.make_option("--threaded", action="store_true", default=False,
    245249            help="Run a concurrent JavaScript thread with each test"),
     
    248252    ]
    249253
    250     # Missing Mac-specific old-run-webkit-tests options:
    251     # FIXME: Need: -g, --guard for guard malloc support on Mac.
    252     # FIXME: Need: -l --leaks    Enable leaks checking.
    253 
    254254    old_run_webkit_tests_compat = [
    255255        # FIXME: Remove this option once the bots don't refer to it.
    256256        # results.html is smart enough to figure this out itself.
    257257        _compat_shim_option("--use-remote-links-to-tests"),
    258         # FIXME: Implement leak detection.
    259         _compat_shim_option("--leaks"),
    260258    ]
    261259
  • trunk/Tools/Scripts/webkitpy/layout_tests/views/printing.py

    r90534 r91245  
    344344        """Prints one unexpected test result line."""
    345345        desc = TestExpectations.EXPECTATION_DESCRIPTIONS[result.type][0]
    346         self.write("  %s -> unexpected %s" %
    347                    (result.test_name, desc), "unexpected")
     346        self.write("  %s -> unexpected %s" % (result.test_name, desc), "unexpected")
    348347
    349348    def print_progress(self, result_summary, retrying, test_list):
Note: See TracChangeset for help on using the changeset viewer.