#!/usr/bin/env python3 # coding: UTF-8 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # This script checks the valgrind output for errors. # The track-fds does not cause an error if there are too many FDs open, # therefore we parse the output and fail if there are open FDs import sys import subprocess import os.path import re import os logfile = sys.argv[1] valgrind_command = ' '.join('"' + item + '"' for item in sys.argv[2:]) # Execute a command and output its stdout text def execute(command): process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Poll process for new output until finished while True: nextline = process.stdout.readline().decode('utf-8') if nextline == '' and process.poll() is not None: break sys.stdout.write(nextline) sys.stdout.flush() return process.returncode ret_code = execute(valgrind_command) if not os.path.isfile(logfile): print("### PYTHON ERROR: Valgrind logfile does not exist: " + logfile) exit(1) log_content = "" with open(logfile) as content_file: log_content = content_file.read() # Valgrind 3.26 with --quiet writes an empty logfile on success. If # valgrind itself returned 0 (no errors, no leaks), treat the run as # successful regardless of whether the descriptor block was printed. # The descriptor block is only informative for --track-fds=yes. if len(log_content) == 0: if ret_code != 0: print("### PYTHON ERROR: Valgrind found errors but logfile is empty: " + logfile) exit(1) exit(0) # Remove output of possible bug in OSX # --16672-- run: /usr/bin/dsymutil "/Users/travis/build/Pro/open62541/build/bin/tests/check_utils" # --16672-- WARNING: unhandled amd64-darwin syscall: unix:464 # --16672-- You may be able to write your own handler. # --16672-- Read the file README_MISSING_SYSCALL_OR_IOCTL. # --16672-- Nevertheless we consider this a bug. Please report # --16672-- it at http://valgrind.org/support/bug_reports.html. replace_re = re.compile(r"^--(\d+)--\s+run: .*-- it at http://valgrind.org/support/bug_reports.html\.$\n", re.MULTILINE | re.DOTALL) log_content = replace_re.sub('', log_content) # Try to parse the output. Look for the following line: # ==17054== FILE DESCRIPTORS: 5 open at exit. descriptors_re = re.compile(r"^==(\d+)==\s+FILE DESCRIPTORS: (\d+) open[^\n]*\n", re.MULTILINE) m = descriptors_re.search(log_content) if not m: if ret_code == 0: exit(0) print("### PYTHON ERROR: File descriptors header not found: " + logfile) print(log_content) exit(1) log_content = descriptors_re.sub('', log_content, count=1) valgrind_number = m.group(1) open_count = int(m.group(2)) # Remove the open file descriptors which are inherited from parent. they look like this: #==21343== Open file descriptor 3: /home/user/open62541/build/bin/tests/discovery.log #==21343== #==21343== #==21343== Open file descriptor 2: #==21343== #==21343== #==21343== Open file descriptor 1: #==21343== #==21343== #==21343== Open file descriptor 0: /dev/pts/1 #==21343== replace_re = re.compile(r"^==" + str(valgrind_number) + r"==\s+Open .*$\n" + r"^==" + str(valgrind_number) + r"==\s+$\n" + r"(^==" + str(valgrind_number) + r"==\s+$\n)*", re.MULTILINE) log_content = replace_re.sub('', log_content) # The check library opens a tmp file (e.g. /tmp/check_XXXXXX) for # per-test IPC and never closes it. Newer valgrind with --track-fds=yes # reports it as an "Open file descriptor" entry that is NOT marked as # inherited. Strip the whole block (header + frame lines). log_content = re.sub( r"^==\d+==\s+Open file descriptor \d+: /tmp/check_\w+$\n" r"(?:^==\d+==\s+.*\n)*" r"(?:^==\d+==\s*$\n)*", "", log_content, flags=re.MULTILINE) # Valgrind detected a memleak if ret_code != 0 if ret_code != 0: print(log_content) exit(ret_code) # Quick path for quiet-mode output (old Valgrind): nothing left means clean run. if len(log_content) == 0 or log_content.isspace(): exit(0) # Explicitly check for non-inherited open file descriptors (FD leaks). # In quiet mode this is the only content that can remain at this point. # In non-quiet mode (Valgrind 3.26+ without --quiet) the log also contains # informational header/summary lines which are benign when ret_code == 0. fd_leak_re = re.compile(r"^==" + str(valgrind_number) + r"==\s+Open file descriptor \d+:", re.MULTILINE) if fd_leak_re.search(log_content): print("### PYTHON ERROR: Non-inherited file descriptor still open:") print(log_content) exit(1) exit(0)