michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: from __future__ import print_function, unicode_literals michael@0: michael@0: import re michael@0: michael@0: class OutputHandler(object): michael@0: ''' michael@0: A class for handling Valgrind output. michael@0: michael@0: Valgrind errors look like this: michael@0: michael@0: ==60741== 40 (24 direct, 16 indirect) bytes in 1 blocks are definitely lost in loss record 2,746 of 5,235 michael@0: ==60741== at 0x4C26B43: calloc (vg_replace_malloc.c:593) michael@0: ==60741== by 0x63AEF65: PR_Calloc (prmem.c:443) michael@0: ==60741== by 0x69F236E: PORT_ZAlloc_Util (secport.c:117) michael@0: ==60741== by 0x69F1336: SECITEM_AllocItem_Util (secitem.c:28) michael@0: ==60741== by 0xA04280B: ffi_call_unix64 (in /builds/slave/m-in-l64-valgrind-000000000000/objdir/toolkit/library/libxul.so) michael@0: ==60741== by 0xA042443: ffi_call (ffi64.c:485) michael@0: michael@0: For each such error, this class extracts most or all of the first (error michael@0: kind) line, plus the function name in each of the first few stack entries. michael@0: With this data it constructs and prints a TEST-UNEXPECTED-FAIL message that michael@0: TBPL will highlight. michael@0: michael@0: It buffers these lines from which text is extracted so that the michael@0: TEST-UNEXPECTED-FAIL message can be printed before the full error. michael@0: michael@0: Parsing the Valgrind output isn't ideal, and it may break in the future if michael@0: Valgrind changes the format of the messages, or introduces new error kinds. michael@0: To protect against this, we also count how many lines containing michael@0: "" are seen. Thanks to the use of michael@0: --gen-suppressions=yes, exactly one of these lines is present per error. If michael@0: the count of these lines doesn't match the error count found during michael@0: parsing, then the parsing has missed one or more errors and we can fail michael@0: appropriately. michael@0: ''' michael@0: michael@0: def __init__(self): michael@0: # The regexps in this list match all of Valgrind's errors. Note that michael@0: # Valgrind is English-only, so we don't have to worry about michael@0: # localization. michael@0: self.re_error = \ michael@0: r'==\d+== (' + \ michael@0: r'(Use of uninitialised value of size \d+)|' + \ michael@0: r'(Conditional jump or move depends on uninitialised value\(s\))|' + \ michael@0: r'(Syscall param .* contains uninitialised byte\(s\))|' + \ michael@0: r'(Syscall param .* points to (unaddressable|uninitialised) byte\(s\))|' + \ michael@0: r'((Unaddressable|Uninitialised) byte\(s\) found during client check request)|' + \ michael@0: r'(Invalid free\(\) / delete / delete\[\] / realloc\(\))|' + \ michael@0: r'(Mismatched free\(\) / delete / delete \[\])|' + \ michael@0: r'(Invalid (read|write) of size \d+)|' + \ michael@0: r'(Jump to the invalid address stated on the next line)|' + \ michael@0: r'(Source and destination overlap in .*)|' + \ michael@0: r'(.* bytes in .* blocks are .* lost)' + \ michael@0: r')' michael@0: # Match identifer chars, plus ':' for namespaces, and '\?' in order to michael@0: # match "???" which Valgrind sometimes produces. michael@0: self.re_stack_entry = r'^==\d+==.*0x[A-Z0-9]+: ([A-Za-z0-9_:\?]+)' michael@0: self.re_suppression = r' *' michael@0: self.error_count = 0 michael@0: self.suppression_count = 0 michael@0: self.number_of_stack_entries_to_get = 0 michael@0: self.curr_failure_msg = None michael@0: self.buffered_lines = None michael@0: michael@0: def __call__(self, line): michael@0: if self.number_of_stack_entries_to_get == 0: michael@0: # Look for the start of a Valgrind error. michael@0: m = re.search(self.re_error, line) michael@0: if m: michael@0: self.error_count += 1 michael@0: self.number_of_stack_entries_to_get = 4 michael@0: self.curr_failure_msg = 'TEST-UNEXPECTED-FAIL | valgrind-test | ' + m.group(1) + " at " michael@0: self.buffered_lines = [line] michael@0: else: michael@0: print(line) michael@0: michael@0: else: michael@0: # We've recently found a Valgrind error, and are now extracting michael@0: # details from the first few stack entries. michael@0: self.buffered_lines.append(line) michael@0: m = re.match(self.re_stack_entry, line) michael@0: if m: michael@0: self.curr_failure_msg += m.group(1) michael@0: else: michael@0: self.curr_failure_msg += '?!?' michael@0: michael@0: self.number_of_stack_entries_to_get -= 1 michael@0: if self.number_of_stack_entries_to_get != 0: michael@0: self.curr_failure_msg += ' / ' michael@0: else: michael@0: # We've finished getting the first few stack entries. Print the michael@0: # failure message and the buffered lines, and then reset state. michael@0: print('\n' + self.curr_failure_msg + '\n') michael@0: for b in self.buffered_lines: michael@0: print(b) michael@0: self.curr_failure_msg = None michael@0: self.buffered_lines = None michael@0: michael@0: if re.match(self.re_suppression, line): michael@0: self.suppression_count += 1 michael@0: