From eb27ca1e2ee6eaad1e44f098b36b122f49a9a3ee Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 25 Jan 2016 12:43:49 -0800 Subject: [PATCH 01/38] [coverage] Initial commit of profdata merging worker --- utils/build-script | 9 ++- utils/build-script-impl | 2 +- utils/profdata_merge_worker.py | 136 +++++++++++++++++++++++++++++++++ utils/use_profdir.py | 11 +++ 4 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 utils/profdata_merge_worker.py diff --git a/utils/build-script b/utils/build-script index c5616cd53687d..6265f6b7b408e 100755 --- a/utils/build-script +++ b/utils/build-script @@ -512,11 +512,12 @@ also build for Apple watchos, but disallow tests that require an watchOS device" action="store_true") parser.add_argument("--swift-analyze-code-coverage", - help="enable code coverage analysis in Swift", + help="""enable code coverage analysis in Swift (set to Merged to merge +and remove intermediary profdata files)""", action="store_const", - const=True, + const="Default", dest="swift_analyze_code_coverage", - default=False) + default="Default") parser.add_argument("--build-subdir", help=""" @@ -728,6 +729,8 @@ the number of parallel build jobs to use""", swift_build_dir_label += "Assert" if args.swift_analyze_code_coverage: swift_build_dir_label += "Coverage" + if args.swift_analyze_code_coverage == "Merged": + swift_build_dir_label += "Merged" swift_stdlib_build_dir_label = args.swift_stdlib_build_variant if args.swift_stdlib_assertions: diff --git a/utils/build-script-impl b/utils/build-script-impl index 7cb9be31332f3..5c656f5dd4af5 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -71,7 +71,7 @@ KNOWN_SETTINGS=( llvm-enable-assertions "1" "enable assertions in LLVM and Clang" swift-build-type "Debug" "the CMake build variant for Swift" swift-enable-assertions "1" "enable assertions in Swift" - swift-analyze-code-coverage "0" "enable code coverage analysis in Swift" + swift-analyze-code-coverage "Default" "enable code coverage analysis in Swift (set to Merged to merge profdata files and remove intermediaries)" swift-stdlib-build-type "Debug" "the CMake build variant for Swift" swift-stdlib-enable-assertions "1" "enable assertions in Swift" lldb-build-type "Debug" "the CMake build variant for LLDB" diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py new file mode 100644 index 0000000000000..6f27736735c9e --- /dev/null +++ b/utils/profdata_merge_worker.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +from contextlib import closing +import os +import sys +import atexit +import socket +import select +import time +import SocketServer +from multiprocessing import Lock, Process, Queue, JoinableQueue +import thread +import subprocess + +PID_FILE_PATH = "/tmp/profdata_merge_worker.pid" + +FINAL_PROFDATA_PATH = "/tmp/final.profdata" +SERVER_ADDRESS = ('localhost', 12400) + +TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" + +FILE_QUEUE = JoinableQueue() +FILES_MERGED = set() + +printlock = Lock() +def printsync(msg): + pass +# with printlock: +# print >>sys.stderr, msg + +class ProfdataTCPHandler(SocketServer.StreamRequestHandler): + def handle(self): + data = self.rfile.read() + printsync(data) + if data.startswith(TESTS_FINISHED_SENTINEL): + printsync("received sentinel; killing server...") + self.finish() + self.connection.close() + def kill_server(server): + server.shutdown() + # must be killed on another thread, or else deadlock + thread.start_new_thread(kill_server, (self.server,)) + else: + for f in data.splitlines(): + f = f.strip() + if f in FILES_MERGED: + return + FILES_MERGED.add(f) + FILE_QUEUE.put(f) + +class ProfdataMergerProcess(Process): + def __init__(self): + Process.__init__(self) + self.filename_buffer = [] + self.profdata_path = "/tmp/%s.profdata" % self.name + self.profdata_tmp_path = self.profdata_path + ".tmp" + + def report(self, msg): + printsync("\n===== %s =====\n%s\n" % (self.name, msg)) + + def merge_file_buffer(self): + if not self.filename_buffer: + self.report("no files to merge...") + return + if os.path.exists(self.profdata_path): + os.rename(self.profdata_path, self.profdata_tmp_path) + self.filename_buffer.append(self.profdata_tmp_path) + llvm_cmd = ("xcrun llvm-profdata merge %s -o %s" % + (" ".join(self.filename_buffer), self.profdata_path)) + self.report(llvm_cmd) + subprocess.call(llvm_cmd, shell=True) + for f in self.filename_buffer: + if os.path.exists(f): + os.remove(f) + self.filename_buffer = [] + + def run(self): + while True: + filename = FILE_QUEUE.get() + self.report("received filename: %s" % filename) + if filename is None: + self.report("received sentinel; merging...") + self.merge_file_buffer() + FILE_QUEUE.task_done() + break + self.filename_buffer.append(filename) + self.report("Adding %s to filename_buffer." % filename) + if len(self.filename_buffer) >= 10: + self.merge_file_buffer() + FILE_QUEUE.task_done() + +def main(): + fpid = os.fork() + if fpid != 0: + sys.exit(0) + if os.path.exists(PID_FILE_PATH): + pid = "" + with open(PID_FILE_PATH) as pidfile: + pid = pidfile.read() + printsync('existing process found with pid %s' % pid) + return + + pid = os.getpid() + with open(PID_FILE_PATH, "w") as pidfile: + pidfile.write(str(pid)) + + def remove_pidfile(): + os.remove(PID_FILE_PATH) + atexit.register(remove_pidfile) + + processes = [ProfdataMergerProcess() for _ in range(10)] + for p in processes: + p.start() + + server = SocketServer.TCPServer(SERVER_ADDRESS, ProfdataTCPHandler) + server.serve_forever() + + for p in processes: + # force each merge worker to gracefully exit + FILE_QUEUE.put(None) + + for p in processes: + printsync("waiting for %s to finish..." % p.name) + p.join() + + # now that all workers have completed, merge all their files + merge_final = ProfdataMergerProcess() + merge_final.profdata_path = "/tmp/swift.profdata" + for p in processes: + if os.path.exists(p.profdata_path): + printsync("merging " + p.profdata_path + "...") + merge_final.filename_buffer.append(p.profdata_path) + merge_final.merge_file_buffer() + +if __name__ == "__main__": + exit(main()) diff --git a/utils/use_profdir.py b/utils/use_profdir.py index 59d05fa9c3bf0..1f85b1ef4347b 100755 --- a/utils/use_profdir.py +++ b/utils/use_profdir.py @@ -13,11 +13,17 @@ # This script is used to help prevent profile data clobbering during code # coverage profiling. +from __future__ import print_function + +from contextlib import closing, contextmanager +import socket import sys import subprocess import os import string import random +import glob +from profdata_merge_worker import SERVER_ADDRESS def random_string(N): """Return a random ascii_uppercase + digits string of length `N`""" @@ -54,6 +60,11 @@ def main(): os.chdir(profdir) try: return_code = subprocess.call(cmd) + files = [os.path.join(profdir, fn) for fn in glob.glob('*.profraw')] + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.connect(SERVER_ADDRESS) + for f in files: + sock.send(f + "\n") finally: os.chdir(previous_cwd) From 2cf852f9aa63eaa911cd20aabd079b0950013ba7 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 25 Jan 2016 17:09:58 -0800 Subject: [PATCH 02/38] [coverage] Cleaned up profdata merge worker and added debug flag --- utils/profdata_merge_worker.py | 53 +++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 17 deletions(-) mode change 100644 => 100755 utils/profdata_merge_worker.py diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py old mode 100644 new mode 100755 index 6f27736735c9e..c233d26813431 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -12,9 +12,9 @@ import thread import subprocess -PID_FILE_PATH = "/tmp/profdata_merge_worker.pid" +PID_FILE_PATH = os.path.join("/tmp", "profdata_merge_worker.pid") -FINAL_PROFDATA_PATH = "/tmp/final.profdata" +FINAL_PROFDATA_PATH = os.path.join("/tmp", "swift.profdata") SERVER_ADDRESS = ('localhost', 12400) TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" @@ -22,16 +22,23 @@ FILE_QUEUE = JoinableQueue() FILES_MERGED = set() +DEBUG = len(sys.argv) > 1 and sys.argv[1] == "--debug" + printlock = Lock() def printsync(msg): - pass -# with printlock: -# print >>sys.stderr, msg + if DEBUG: + with printlock: + print >>sys.stderr, msg class ProfdataTCPHandler(SocketServer.StreamRequestHandler): def handle(self): + """Receive a newline-separated list of filenames from a TCP connection + and add them to the shared merge queue, where the workers will + execute llvm-profdata merge commands.""" data = self.rfile.read() printsync(data) + + # Stop once we receive the sentinel if data.startswith(TESTS_FINISHED_SENTINEL): printsync("received sentinel; killing server...") self.finish() @@ -41,6 +48,7 @@ def kill_server(server): # must be killed on another thread, or else deadlock thread.start_new_thread(kill_server, (self.server,)) else: + # Add all the files to the queue for f in data.splitlines(): f = f.strip() if f in FILES_MERGED: @@ -52,13 +60,18 @@ class ProfdataMergerProcess(Process): def __init__(self): Process.__init__(self) self.filename_buffer = [] - self.profdata_path = "/tmp/%s.profdata" % self.name + self.profdata_path = os.path.join("/tmp", "%s.profdata" % self.name) self.profdata_tmp_path = self.profdata_path + ".tmp" def report(self, msg): + """Convenience method for reporting status from the workers.""" printsync("\n===== %s =====\n%s\n" % (self.name, msg)) def merge_file_buffer(self): + """Merge all files in this worker's buffer and clear them. + This method makes a copy of the working merge progress, then + calls llvm-cov merge with up to 10 filenames, plus the current + in-progress merge.""" if not self.filename_buffer: self.report("no files to merge...") return @@ -75,6 +88,9 @@ def merge_file_buffer(self): self.filename_buffer = [] def run(self): + """Blocks and waits for the file queue, so it can fill its buffer + and execute merges. If it finds None in the queue, then it knows to + stop waiting for the queue, merge its current buffer, and kill itself""" while True: filename = FILE_QUEUE.get() self.report("received filename: %s" % filename) @@ -89,25 +105,28 @@ def run(self): self.merge_file_buffer() FILE_QUEUE.task_done() +@atexit.register +def remove_pidfile(): + """Always remove the pid file when we exit.""" + if os.path.exists(PID_FILE_PATH): + os.remove(PID_FILE_PATH) + def main(): - fpid = os.fork() - if fpid != 0: - sys.exit(0) + pid = os.getpid() + if not DEBUG: + pid = os.fork() + if pid != 0: + sys.exit(0) # kill the parent process we forked from. + if os.path.exists(PID_FILE_PATH): - pid = "" with open(PID_FILE_PATH) as pidfile: pid = pidfile.read() - printsync('existing process found with pid %s' % pid) + printsync('existing process found with pid %s' % pid) return - pid = os.getpid() with open(PID_FILE_PATH, "w") as pidfile: pidfile.write(str(pid)) - def remove_pidfile(): - os.remove(PID_FILE_PATH) - atexit.register(remove_pidfile) - processes = [ProfdataMergerProcess() for _ in range(10)] for p in processes: p.start() @@ -125,7 +144,7 @@ def remove_pidfile(): # now that all workers have completed, merge all their files merge_final = ProfdataMergerProcess() - merge_final.profdata_path = "/tmp/swift.profdata" + merge_final.profdata_path = FINAL_PROFDATA_PATH for p in processes: if os.path.exists(p.profdata_path): printsync("merging " + p.profdata_path + "...") From c6c0b3d5d01e48c79bc3f55f302c9ad9f3d044b8 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 25 Jan 2016 17:11:26 -0800 Subject: [PATCH 03/38] [coverage] Removed explicit swift-%p.profraw arguments from CMake commands, because we're using LLVM's environment variables --- cmake/modules/AddSwift.cmake | 4 +-- tools/SourceKit/CMakeLists.txt | 2 +- .../tools/complete-test/CMakeLists.txt | 2 +- .../tools/sourcekitd-repl/CMakeLists.txt | 2 +- .../tools/sourcekitd-test/CMakeLists.txt | 2 +- unittests/CMakeLists.txt | 2 +- utils/use_profdir.py | 34 +++++++++++-------- 7 files changed, 27 insertions(+), 21 deletions(-) diff --git a/cmake/modules/AddSwift.cmake b/cmake/modules/AddSwift.cmake index d331c43e1fcc9..960592610ecb6 100644 --- a/cmake/modules/AddSwift.cmake +++ b/cmake/modules/AddSwift.cmake @@ -64,7 +64,7 @@ function(_add_variant_c_compile_link_flags "-m${SWIFT_SDK_${sdk}_VERSION_MIN_NAME}-version-min=${SWIFT_SDK_${sdk}_DEPLOYMENT_VERSION}") if(analyze_code_coverage) - list(APPEND result "-fprofile-instr-generate=swift-%p.profraw" + list(APPEND result "-fprofile-instr-generate" "-fcoverage-mapping") endif() endif() @@ -111,7 +111,7 @@ function(_add_variant_c_compile_flags endif() if(analyze_code_coverage) - list(APPEND result "-fprofile-instr-generate=swift-%p.profraw" + list(APPEND result "-fprofile-instr-generate" "-fcoverage-mapping") endif() diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index aa1de0f2ed8e4..9d3f2803998c8 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -241,7 +241,7 @@ macro(add_sourcekit_executable name) if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${name}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endif() diff --git a/tools/SourceKit/tools/complete-test/CMakeLists.txt b/tools/SourceKit/tools/complete-test/CMakeLists.txt index 1ee0c2e695bec..a266ad864d64a 100644 --- a/tools/SourceKit/tools/complete-test/CMakeLists.txt +++ b/tools/SourceKit/tools/complete-test/CMakeLists.txt @@ -17,7 +17,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET complete-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt index 20542027dbc19..a718f33127c7a 100644 --- a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt @@ -11,7 +11,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-repl APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index ba470ff72a917..831f6b78ce0c1 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -25,7 +25,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 073d7a6241362..5fd60ee32f8a6 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -29,7 +29,7 @@ function(add_swift_unittest test_dirname) if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate=swift-%p.profraw -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endfunction() diff --git a/utils/use_profdir.py b/utils/use_profdir.py index 1f85b1ef4347b..2b136c6e4b8d1 100755 --- a/utils/use_profdir.py +++ b/utils/use_profdir.py @@ -15,15 +15,15 @@ from __future__ import print_function -from contextlib import closing, contextmanager import socket import sys import subprocess import os import string import random -import glob +from glob import glob from profdata_merge_worker import SERVER_ADDRESS +import argparse def random_string(N): """Return a random ascii_uppercase + digits string of length `N`""" @@ -34,6 +34,12 @@ def main(): # Grab passed in bash command cmd = sys.argv[1:] + # Check if we're going to merge the profdata files together + merged = True # False + if cmd[0] == "--merged": + merged = True + cmd = cmd[1:] + # Search arguments for test identifiers script_files = [f for f in cmd if f.endswith('.script')] gtests = [f.split('=')[1] for f in cmd if f.startswith('--gtest_filter')] @@ -55,18 +61,18 @@ def main(): if e.errno != ERROR_FILE_EXISTS: raise - # cd into the new directory and execute the passed in command - previous_cwd = os.getcwd() - os.chdir(profdir) - try: - return_code = subprocess.call(cmd) - files = [os.path.join(profdir, fn) for fn in glob.glob('*.profraw')] - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: - sock.connect(SERVER_ADDRESS) - for f in files: - sock.send(f + "\n") - finally: - os.chdir(previous_cwd) + os.environ["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") + return_code = subprocess.call(cmd) + + # Send the filenames in this directory to the merge worker, if we're + # merging the files + if merged: + profile_glob = os.path.join(profdir, '*.profraw') + files = [os.path.join(profdir, fn) for fn in glob(profile_glob)] + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(SERVER_ADDRESS) + sock.send("\n".join(files)) + sock.close() return return_code From 32fda29eac56ec930d7c3b99b3d216ed3a6e23c5 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 25 Jan 2016 19:44:40 -0800 Subject: [PATCH 04/38] [coverage] Made subclass of list test format so we can hook into each test run and properly create profile directories. This removes the need for use_profdir.py and removes the valgrind hack from lit.site.cfg.in --- test/lit.cfg | 30 ++++++++++++++++- test/lit.site.cfg.in | 10 +++--- utils/use_profdir.py | 80 -------------------------------------------- 3 files changed, 35 insertions(+), 85 deletions(-) delete mode 100755 utils/use_profdir.py diff --git a/test/lit.cfg b/test/lit.cfg index b0b6a1a81b25c..cdba3d0dcba90 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -24,7 +24,10 @@ import re import subprocess import sys import tempfile +import socket +import glob +import lit import lit.formats import lit.util @@ -129,6 +132,31 @@ if config.test_exec_root is None: ### +class SwiftTest(lit.formats.ShTest): + def __init__(self, merged=False, execute_external=True): + lit.formats.ShTest.__init__(self, execute_external) + self.merged = merged + + def execute(self, test, litConfig): + tmp_dir, tmp_base = lit.TestRunner.getTempPaths(test) + + profdir = tmp_base + ".script.profdir" + if not os.path.exists(profdir): + os.makedirs(profdir) + + test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") + + result = super(SwiftTest, self).execute(test, litConfig) + + if self.merged: + files = glob.glob(os.path.join(profdir, "*.profraw")) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', 12400)) + sock.send("\n".join(files)) + sock.close() + + return result + # name: The name of this test suite. config.name = 'Swift' @@ -138,7 +166,7 @@ if platform.system() == 'Darwin': config.environment['TOOLCHAINS'] = 'default' # testFormat: The test format to use to interpret tests. -config.test_format = lit.formats.ShTest(execute_external=True) +config.test_format = SwiftTest(merged=True) # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.swift', '.ll', '.sil', '.gyb', '.m'] diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index cd569075595fe..c7178e791accc 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -42,10 +42,12 @@ if "@SWIFT_OPTIMIZED@" == "TRUE": if "@SWIFT_HAVE_WORKING_STD_REGEX@" == "FALSE": config.available_features.add('broken_std_regex') -if "@SWIFT_ANALYZE_CODE_COVERAGE@" == "TRUE": - lit_config.useValgrind = True - lit_config.valgrindArgs = [os.path.join(config.swift_src_root, - "utils/use_profdir.py")] +# if "@SWIFT_ANALYZE_CODE_COVERAGE@" != "NONE": +# lit_config.useValgrind = True +# lit_config.valgrindArgs = [os.path.join(config.swift_src_root, +# "utils/use_profdir.py")] +# if "@SWIFT_ANALYZE_CODE_COVERAGE@" == "MERGED": +# lit_config.valgrindArgs.append("--merged") # Let the main config do the real work. if config.test_exec_root is None: diff --git a/utils/use_profdir.py b/utils/use_profdir.py deleted file mode 100755 index 2b136c6e4b8d1..0000000000000 --- a/utils/use_profdir.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python - -# utils/use_profdir.py -# -# This source file is part of the Swift.org open source project -# -# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors -# Licensed under Apache License v2.0 with Runtime Library Exception -# -# See http://swift.org/LICENSE.txt for license information -# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors - -# This script is used to help prevent profile data clobbering during code -# coverage profiling. - -from __future__ import print_function - -import socket -import sys -import subprocess -import os -import string -import random -from glob import glob -from profdata_merge_worker import SERVER_ADDRESS -import argparse - -def random_string(N): - """Return a random ascii_uppercase + digits string of length `N`""" - return ''.join(random.choice(string.ascii_uppercase + string.digits) - for _ in range(N)) - -def main(): - # Grab passed in bash command - cmd = sys.argv[1:] - - # Check if we're going to merge the profdata files together - merged = True # False - if cmd[0] == "--merged": - merged = True - cmd = cmd[1:] - - # Search arguments for test identifiers - script_files = [f for f in cmd if f.endswith('.script')] - gtests = [f.split('=')[1] for f in cmd if f.startswith('--gtest_filter')] - - # Generate directory name using first test identifier, defaulting to - # random characters if no test identifier can be found - if script_files: - profdir = script_files[0] + '.profdir' - elif gtests: - profdir = os.path.join(os.path.dirname(cmd[0]), gtests[0] + '.profdir') - else: - profdir = random_string(12) + '.profdir' - - # Create the directory using the generated name - try: - os.makedirs(profdir) - except OSError as e: - ERROR_FILE_EXISTS = 17 - if e.errno != ERROR_FILE_EXISTS: - raise - - os.environ["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") - return_code = subprocess.call(cmd) - - # Send the filenames in this directory to the merge worker, if we're - # merging the files - if merged: - profile_glob = os.path.join(profdir, '*.profraw') - files = [os.path.join(profdir, fn) for fn in glob(profile_glob)] - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(SERVER_ADDRESS) - sock.send("\n".join(files)) - sock.close() - - return return_code - -if __name__ == '__main__': - exit(main()) From a41ec1a6f7f6021de9a6f397f21c2dcb2684ce7c Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 25 Jan 2016 19:45:28 -0800 Subject: [PATCH 05/38] [coverage] Cleaned up argument parsing for swift coverage, and hooked into build-script to propagate the different values --- utils/build-script | 11 ++++------- utils/build-script-impl | 4 ++-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/utils/build-script b/utils/build-script index 6265f6b7b408e..997095ddfd97b 100755 --- a/utils/build-script +++ b/utils/build-script @@ -512,12 +512,11 @@ also build for Apple watchos, but disallow tests that require an watchOS device" action="store_true") parser.add_argument("--swift-analyze-code-coverage", - help="""enable code coverage analysis in Swift (set to Merged to merge + help="""enable code coverage analysis in Swift (set to `merged` to merge and remove intermediary profdata files)""", - action="store_const", - const="Default", - dest="swift_analyze_code_coverage", - default="Default") + const="default", + nargs='?', + dest="swift_analyze_code_coverage") parser.add_argument("--build-subdir", help=""" @@ -729,8 +728,6 @@ the number of parallel build jobs to use""", swift_build_dir_label += "Assert" if args.swift_analyze_code_coverage: swift_build_dir_label += "Coverage" - if args.swift_analyze_code_coverage == "Merged": - swift_build_dir_label += "Merged" swift_stdlib_build_dir_label = args.swift_stdlib_build_variant if args.swift_stdlib_assertions: diff --git a/utils/build-script-impl b/utils/build-script-impl index 5c656f5dd4af5..30321df13f000 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -71,7 +71,7 @@ KNOWN_SETTINGS=( llvm-enable-assertions "1" "enable assertions in LLVM and Clang" swift-build-type "Debug" "the CMake build variant for Swift" swift-enable-assertions "1" "enable assertions in Swift" - swift-analyze-code-coverage "Default" "enable code coverage analysis in Swift (set to Merged to merge profdata files and remove intermediaries)" + swift-analyze-code-coverage "default" "enable code coverage analysis in Swift (set to \`merged\` to merge profdata files and remove intermediaries)" swift-stdlib-build-type "Debug" "the CMake build variant for Swift" swift-stdlib-enable-assertions "1" "enable assertions in Swift" lldb-build-type "Debug" "the CMake build variant for LLDB" @@ -1589,7 +1589,7 @@ for deployment_target in "${HOST_TARGET}" "${CROSS_TOOLS_DEPLOYMENT_TARGETS[@]}" -DCMAKE_CXX_FLAGS="$(swift_c_flags ${deployment_target})" -DCMAKE_BUILD_TYPE:STRING="${SWIFT_BUILD_TYPE}" -DLLVM_ENABLE_ASSERTIONS:BOOL=$(true_false "${SWIFT_ENABLE_ASSERTIONS}") - -DSWIFT_ANALYZE_CODE_COVERAGE:BOOL=$(true_false "${SWIFT_ANALYZE_CODE_COVERAGE}") + -DSWIFT_ANALYZE_CODE_COVERAGE:STRING=$(toupper "${SWIFT_ANALYZE_CODE_COVERAGE}") -DSWIFT_STDLIB_BUILD_TYPE:STRING="${SWIFT_STDLIB_BUILD_TYPE}" -DSWIFT_STDLIB_ASSERTIONS:BOOL=$(true_false "${SWIFT_STDLIB_ENABLE_ASSERTIONS}") -DSWIFT_NATIVE_LLVM_TOOLS_PATH:STRING="${native_llvm_tools_path}" From 78612e573bb97ff95621fd4e1e2fccf4e0451712 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 10:01:39 -0800 Subject: [PATCH 06/38] [coverage] Added before_test and after_test hooks in SwiftTest --- test/lit.cfg | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/lit.cfg b/test/lit.cfg index cdba3d0dcba90..9699a33b682c1 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -137,26 +137,30 @@ class SwiftTest(lit.formats.ShTest): lit.formats.ShTest.__init__(self, execute_external) self.merged = merged - def execute(self, test, litConfig): + def before_test(self, test, litConfig): tmp_dir, tmp_base = lit.TestRunner.getTempPaths(test) - profdir = tmp_base + ".script.profdir" + profdir = tmp_base + ".profdir" if not os.path.exists(profdir): os.makedirs(profdir) test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") - result = super(SwiftTest, self).execute(test, litConfig) - + def after_test(test, litConfig, result): if self.merged: files = glob.glob(os.path.join(profdir, "*.profraw")) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', 12400)) sock.send("\n".join(files)) sock.close() - return result + + def execute(self, test, litConfig): + self.before_test(test, litConfig) + result = super(SwiftTest, self).execute(test, litConfig) + return self.after_test(self, test, litConfig, result) + # name: The name of this test suite. config.name = 'Swift' From 03964cc069be0ec5e8f66cc473f23a7f8680dc1d Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 11:04:16 -0800 Subject: [PATCH 07/38] [coverage] Simplified before_test and after_test --- test/lit.cfg | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/lit.cfg b/test/lit.cfg index 9699a33b682c1..4420172165b15 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -137,16 +137,19 @@ class SwiftTest(lit.formats.ShTest): lit.formats.ShTest.__init__(self, execute_external) self.merged = merged - def before_test(self, test, litConfig): - tmp_dir, tmp_base = lit.TestRunner.getTempPaths(test) + def profdir_for_test(self, test): + _, tmp_base = lit.TestRunner.getTempPaths(test) + return tmp_base + ".profdir" - profdir = tmp_base + ".profdir" + def before_test(self, test, litConfig): + profdir = self.profdir_for_test(test) if not os.path.exists(profdir): os.makedirs(profdir) test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") def after_test(test, litConfig, result): + profdir = self.profdir_for_test(test) if self.merged: files = glob.glob(os.path.join(profdir, "*.profraw")) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) From 3ea61fbcc39405c2aea4d74e52a1f877145e7697 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 16:35:35 -0800 Subject: [PATCH 08/38] [coverage] Fixed runtime error in lit.cfg --- test/lit.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit.cfg b/test/lit.cfg index 4420172165b15..5a9895dda9aa3 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -148,7 +148,7 @@ class SwiftTest(lit.formats.ShTest): test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") - def after_test(test, litConfig, result): + def after_test(self, test, litConfig, result): profdir = self.profdir_for_test(test) if self.merged: files = glob.glob(os.path.join(profdir, "*.profraw")) @@ -162,7 +162,7 @@ class SwiftTest(lit.formats.ShTest): def execute(self, test, litConfig): self.before_test(test, litConfig) result = super(SwiftTest, self).execute(test, litConfig) - return self.after_test(self, test, litConfig, result) + return self.after_test(test, litConfig, result) # name: The name of this test suite. config.name = 'Swift' From 9eb729ce79669db17f7285f3f599d9fbc3b7dce6 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 16:36:26 -0800 Subject: [PATCH 09/38] [coverage] Reworked CMake invocation for coverage testing given there are three states for SWIFT_ANALYZE_CODE_COVERAGE --- test/CMakeLists.txt | 23 +++++++++++++++++++ tools/SourceKit/CMakeLists.txt | 4 ++-- .../tools/complete-test/CMakeLists.txt | 2 +- .../tools/sourcekitd-repl/CMakeLists.txt | 2 +- .../tools/sourcekitd-test/CMakeLists.txt | 2 +- unittests/CMakeLists.txt | 2 +- 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dff17a08b7fc6..0c50f82be0b9e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -117,6 +117,7 @@ if(PYTHONINTERP_FOUND) set(TEST_MODES optimize_none optimize optimize_unchecked) + foreach(SDK ${SWIFT_SDKS}) foreach(ARCH ${SWIFT_SDK_${SDK}_ARCHITECTURES}) foreach(TEST_MODE ${TEST_MODES}) @@ -193,6 +194,11 @@ if(PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/../validation-test/lit.site.cfg.in" "${validation_test_bin_dir}/lit.site.cfg" "validation-test${VARIANT_SUFFIX}.lit.site.cfg") + set(profdata_merge_worker + "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge_worker.py") + + set(profdata_merge_target + "profdata-merge${test_mode_target_suffix}${VARIANT_SUFFIX}") add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} @@ -217,6 +223,23 @@ if(PYTHONINTERP_FOUND) DEPENDS ${test_dependencies} COMMENT "Running all Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) + + message(STATUS ${SWIFT_ANALYZE_CODE_COVERAGE}) + if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") + message(STATUS "Adding profdata targets...") + add_custom_command( + TARGET "check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" + PRE_BUILD + COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} -o ${swift_test_results_dir}" + COMMENT "Running profdata merge worker for ${VARIANT_TRIPLE}") + add_custom_command( + TARGET "check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" + POST_BUILD + COMMAND "echo 'PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL' | nc localhost 12400" + COMMENT "Stopping profdata merge worker") + endif() + + endforeach() endforeach() endforeach() diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index 9d3f2803998c8..581f5df6a2cc2 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -239,8 +239,8 @@ macro(add_sourcekit_executable name) PROPERTIES LINK_FLAGS "-Wl,-exported_symbol,_main") - if(SWIFT_ANALYZE_CODE_COVERAGE) - set_property(TARGET "${name}" APPEND_STRING PROPERTY + if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") + set_property(TARGET "${name}" APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/complete-test/CMakeLists.txt b/tools/SourceKit/tools/complete-test/CMakeLists.txt index a266ad864d64a..d2dd6c4ed7a5c 100644 --- a/tools/SourceKit/tools/complete-test/CMakeLists.txt +++ b/tools/SourceKit/tools/complete-test/CMakeLists.txt @@ -15,7 +15,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") - if(SWIFT_ANALYZE_CODE_COVERAGE) + if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") set_property(TARGET complete-test APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt index a718f33127c7a..34ee688c2bf34 100644 --- a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt @@ -9,7 +9,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") - if(SWIFT_ANALYZE_CODE_COVERAGE) +if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") set_property(TARGET sourcekitd-repl APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index 831f6b78ce0c1..88d4141c5e1c6 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -23,7 +23,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") - if(SWIFT_ANALYZE_CODE_COVERAGE) +if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") set_property(TARGET sourcekitd-test APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 5fd60ee32f8a6..f255c564ec6b4 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -27,7 +27,7 @@ function(add_swift_unittest test_dirname) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY LINK_FLAGS " -Xlinker -rpath -Xlinker ${SWIFT_LIBRARY_OUTPUT_INTDIR}/swift/macosx") - if(SWIFT_ANALYZE_CODE_COVERAGE) + if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() From fb6ee1a00da771e0cc78893c313d075feed2b75c Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 16:38:38 -0800 Subject: [PATCH 10/38] [coverage] Added argument parsing to profdata merge worker and cleaned up the implementation --- utils/profdata_merge_worker.py | 88 +++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index c233d26813431..6683bc07d9c23 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -1,46 +1,52 @@ #!/usr/bin/env python -from contextlib import closing import os +import shutil +import pipes import sys -import atexit import socket -import select -import time import SocketServer from multiprocessing import Lock, Process, Queue, JoinableQueue import thread import subprocess +import tempfile +import argparse -PID_FILE_PATH = os.path.join("/tmp", "profdata_merge_worker.pid") - -FINAL_PROFDATA_PATH = os.path.join("/tmp", "swift.profdata") SERVER_ADDRESS = ('localhost', 12400) - TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" - FILE_QUEUE = JoinableQueue() FILES_MERGED = set() +CONFIG = None -DEBUG = len(sys.argv) > 1 and sys.argv[1] == "--debug" +class Config(): + def __init__(self, debug, out_dir): + self.debug = debug + self.out_dir = out_dir + self.tmp_dir = tempfile.mkdtemp() + self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") + self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") printlock = Lock() def printsync(msg): - if DEBUG: + if CONFIG and CONFIG.debug: with printlock: print >>sys.stderr, msg class ProfdataTCPHandler(SocketServer.StreamRequestHandler): + def report(self, msg): + """Convenience method for reporting status from the workers.""" + printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg) + def handle(self): """Receive a newline-separated list of filenames from a TCP connection and add them to the shared merge queue, where the workers will execute llvm-profdata merge commands.""" data = self.rfile.read() - printsync(data) + self.report("received data (length %d): %s" % (len(data), data)) # Stop once we receive the sentinel if data.startswith(TESTS_FINISHED_SENTINEL): - printsync("received sentinel; killing server...") + self.report("received sentinel; killing server...") self.finish() self.connection.close() def kill_server(server): @@ -60,8 +66,8 @@ class ProfdataMergerProcess(Process): def __init__(self): Process.__init__(self) self.filename_buffer = [] - self.profdata_path = os.path.join("/tmp", "%s.profdata" % self.name) - self.profdata_tmp_path = self.profdata_path + ".tmp" + self.profdata_path = os.path.join(CONFIG.tmp_dir, "%s.profdata" % self.name) + self.profdata_tmp_path = self.profdata_path + ".copy" def report(self, msg): """Convenience method for reporting status from the workers.""" @@ -78,10 +84,14 @@ def merge_file_buffer(self): if os.path.exists(self.profdata_path): os.rename(self.profdata_path, self.profdata_tmp_path) self.filename_buffer.append(self.profdata_tmp_path) - llvm_cmd = ("xcrun llvm-profdata merge %s -o %s" % - (" ".join(self.filename_buffer), self.profdata_path)) + cleaned_files = ' '.join(pipes.quote(f) for f in self.filename_buffer) + llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" + % (self.profdata_path, cleaned_files)) self.report(llvm_cmd) - subprocess.call(llvm_cmd, shell=True) + ret = subprocess.call(llvm_cmd, shell=True) + if ret != 0: + self.report("llvm profdata command failed -- Exited with code %d" + % ret) for f in self.filename_buffer: if os.path.exists(f): os.remove(f) @@ -105,26 +115,16 @@ def run(self): self.merge_file_buffer() FILE_QUEUE.task_done() -@atexit.register -def remove_pidfile(): - """Always remove the pid file when we exit.""" - if os.path.exists(PID_FILE_PATH): - os.remove(PID_FILE_PATH) - def main(): - pid = os.getpid() - if not DEBUG: - pid = os.fork() - if pid != 0: - sys.exit(0) # kill the parent process we forked from. - if os.path.exists(PID_FILE_PATH): - with open(PID_FILE_PATH) as pidfile: + pid = os.getpid() + if os.path.exists(CONFIG.pid_file_path): + with open(CONFIG.pid_file_path) as pidfile: pid = pidfile.read() printsync('existing process found with pid %s' % pid) return - with open(PID_FILE_PATH, "w") as pidfile: + with open(CONFIG.pid_file_path, "w") as pidfile: pidfile.write(str(pid)) processes = [ProfdataMergerProcess() for _ in range(10)] @@ -144,7 +144,7 @@ def main(): # now that all workers have completed, merge all their files merge_final = ProfdataMergerProcess() - merge_final.profdata_path = FINAL_PROFDATA_PATH + merge_final.profdata_path = CONFIG.final_profdata_path for p in processes: if os.path.exists(p.profdata_path): printsync("merging " + p.profdata_path + "...") @@ -152,4 +152,24 @@ def main(): merge_final.merge_file_buffer() if __name__ == "__main__": - exit(main()) + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--debug", + help="Run in foreground and report status.", + action="store_true") + parser.add_argument("-o", "--output-dir", + help="The directory to write the PID file and final profdata file.", + default="/tmp") + args = parser.parse_args() + CONFIG = Config(args.debug, args.output_dir) + if not CONFIG.debug: + pid = os.fork() + if pid != 0: + sys.exit(0) # kill the parent process we forked from. + try: + main() + finally: + if os.path.exists(CONFIG.pid_file_path): + os.remove(CONFIG.pid_file_path) + if os.path.exists(CONFIG.tmp_dir): + shutil.rmtree(CONFIG.tmp_dir, ignore_errors=True) + From 259af4d732e015beb476df544f8e7d50a558d9c6 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 17:25:52 -0800 Subject: [PATCH 11/38] [coverage] Cleaned up target generation and lit merging code --- test/CMakeLists.txt | 33 +++++++++++++---------- test/lit.cfg | 19 +++++++------- test/lit.site.cfg.in | 9 ++----- utils/profdata_merge_worker.py | 48 ++++++++++++++++++++++------------ 4 files changed, 63 insertions(+), 46 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0c50f82be0b9e..e507463df65d1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -197,8 +197,13 @@ if(PYTHONINTERP_FOUND) set(profdata_merge_worker "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge_worker.py") - set(profdata_merge_target - "profdata-merge${test_mode_target_suffix}${VARIANT_SUFFIX}") + if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") + add_custom_target("profdata-merge${test_mode_target_suffix}${VARIANT_SUFFIX}" + COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} start -o ${swift_test_results_dir}" + DEPENDS ${test_dependencies} + COMMENT "Running profdata merge worker for ${VARIANT_TRIPLE}" + ${cmake_3_2_USES_TERMINAL}) + endif() add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} @@ -224,19 +229,12 @@ if(PYTHONINTERP_FOUND) COMMENT "Running all Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) - message(STATUS ${SWIFT_ANALYZE_CODE_COVERAGE}) if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") - message(STATUS "Adding profdata targets...") - add_custom_command( - TARGET "check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" - PRE_BUILD - COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} -o ${swift_test_results_dir}" - COMMENT "Running profdata merge worker for ${VARIANT_TRIPLE}") - add_custom_command( - TARGET "check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" - POST_BUILD - COMMAND "echo 'PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL' | nc localhost 12400" - COMMENT "Stopping profdata merge worker") + add_custom_target("profdata-teardown${test_mode_target_suffix}${VARIANT_SUFFIX}" + COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} stop" + DEPENDS ${test_dependencies} + COMMENT "Stopping profdata merge worker for ${VARIANT_TUPLE}" + ${cmake_3_2_USES_TERMINAL}) endif() @@ -265,6 +263,13 @@ if(PYTHONINTERP_FOUND) add_custom_target(check-swift-all${test_mode_target_suffix} DEPENDS "check-swift-all${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") + + add_custom_target(profdata-merge${test_mode_target_suffix} + DEPENDS "profdata-merge${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") + + add_custom_target(profdata-teardown${test_mode_target_suffix} + DEPENDS "profdata-teardown${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") + endforeach() endif() diff --git a/test/lit.cfg b/test/lit.cfg index 5a9895dda9aa3..c2ccff5803ce1 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -133,24 +133,25 @@ if config.test_exec_root is None: ### class SwiftTest(lit.formats.ShTest): - def __init__(self, merged=False, execute_external=True): + def __init__(self, coverage_mode=None, execute_external=True): lit.formats.ShTest.__init__(self, execute_external) - self.merged = merged + self.coverage_mode = None if coverage_mode == "NONE" else coverage_mode def profdir_for_test(self, test): _, tmp_base = lit.TestRunner.getTempPaths(test) return tmp_base + ".profdir" def before_test(self, test, litConfig): - profdir = self.profdir_for_test(test) - if not os.path.exists(profdir): - os.makedirs(profdir) + if self.coverage_mode: + profdir = self.profdir_for_test(test) + if not os.path.exists(profdir): + os.makedirs(profdir) - test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") + test.config.environment["LLVM_PROFILE_FILE"] = os.path.join(profdir, "swift-%p.profraw") def after_test(self, test, litConfig, result): - profdir = self.profdir_for_test(test) - if self.merged: + if self.coverage_mode == "MERGED": + profdir = self.profdir_for_test(test) files = glob.glob(os.path.join(profdir, "*.profraw")) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', 12400)) @@ -173,7 +174,7 @@ if platform.system() == 'Darwin': config.environment['TOOLCHAINS'] = 'default' # testFormat: The test format to use to interpret tests. -config.test_format = SwiftTest(merged=True) +config.test_format = SwiftTest(coverage_mode=config.coverage_mode) # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.swift', '.ll', '.sil', '.gyb', '.m'] diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index c7178e791accc..a410eaf75e16b 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -18,6 +18,8 @@ config.variant_sdk = "@VARIANT_SDK@" config.swiftlib_dir = "@LIT_SWIFTLIB_DIR@" config.darwin_xcrun_toolchain = "@SWIFT_DARWIN_XCRUN_TOOLCHAIN@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + if "@SWIFT_ASAN_BUILD@" == "TRUE": config.available_features.add("asan") else: @@ -42,13 +44,6 @@ if "@SWIFT_OPTIMIZED@" == "TRUE": if "@SWIFT_HAVE_WORKING_STD_REGEX@" == "FALSE": config.available_features.add('broken_std_regex') -# if "@SWIFT_ANALYZE_CODE_COVERAGE@" != "NONE": -# lit_config.useValgrind = True -# lit_config.valgrindArgs = [os.path.join(config.swift_src_root, -# "utils/use_profdir.py")] -# if "@SWIFT_ANALYZE_CODE_COVERAGE@" == "MERGED": -# lit_config.valgrindArgs.append("--merged") - # Let the main config do the real work. if config.test_exec_root is None: config.test_exec_root = os.path.dirname(os.path.realpath(__file__)) diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index 6683bc07d9c23..30f375ea9a84b 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -115,8 +115,7 @@ def run(self): self.merge_file_buffer() FILE_QUEUE.task_done() -def main(): - +def run_server(): pid = os.getpid() if os.path.exists(CONFIG.pid_file_path): with open(CONFIG.pid_file_path) as pidfile: @@ -151,25 +150,42 @@ def main(): merge_final.filename_buffer.append(p.profdata_path) merge_final.merge_file_buffer() -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("-d", "--debug", - help="Run in foreground and report status.", - action="store_true") - parser.add_argument("-o", "--output-dir", - help="The directory to write the PID file and final profdata file.", - default="/tmp") - args = parser.parse_args() +def stop_server(args): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(SERVER_ADDRESS) + sock.send(TESTS_FINISHED_SENTINEL) + sock.close() + +def start_server(args): + global CONFIG CONFIG = Config(args.debug, args.output_dir) - if not CONFIG.debug: - pid = os.fork() - if pid != 0: - sys.exit(0) # kill the parent process we forked from. try: - main() + if not CONFIG.debug: + pid = os.fork() + if pid != 0: + sys.exit(0) # kill the parent process we forked from. + run_server() finally: if os.path.exists(CONFIG.pid_file_path): os.remove(CONFIG.pid_file_path) if os.path.exists(CONFIG.tmp_dir): shutil.rmtree(CONFIG.tmp_dir, ignore_errors=True) +if __name__ == "__main__": + parser = argparse.ArgumentParser() + subparsers = parser.add_subparsers() + + start = subparsers.add_parser("start") + start.add_argument("-d", "--debug", + help="Run in foreground and report status.", + action="store_true") + start.add_argument("-o", "--output-dir", + help="The directory to write the PID file and final profdata file.", + default="/tmp") + start.set_defaults(func=start_server) + + stop = subparsers.add_parser("stop") + stop.set_defaults(func=stop_server) + + args = parser.parse_args() + args.func(args) From c83fb1c71fd3f3200d906d58d5263552fb030de8 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 26 Jan 2016 18:43:50 -0800 Subject: [PATCH 12/38] [coverage] Fixed CMake invocation of profdata merge worker --- test/CMakeLists.txt | 34 +++++++++++++++------------------- utils/profdata_merge_worker.py | 10 +++++----- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e507463df65d1..aa90bc4d50f00 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -198,17 +198,23 @@ if(PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge_worker.py") if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") - add_custom_target("profdata-merge${test_mode_target_suffix}${VARIANT_SUFFIX}" - COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} start -o ${swift_test_results_dir}" - DEPENDS ${test_dependencies} - COMMENT "Running profdata merge worker for ${VARIANT_TRIPLE}" - ${cmake_3_2_USES_TERMINAL}) + set(command_profdata_merge_start + COMMAND ${PYTHON_EXECUTABLE} ${profdata_merge_worker} start -o ${swift_test_results_dir}) + set(command_profdata_merge_stop + COMMAND ${PYTHON_EXECUTABLE} ${profdata_merge_worker} stop) + else() + set(command_profdata_merge_start) + set(command_profdata_merge_stop) endif() + message(STATUS ${command_profdata_merge_start}) + message(STATUS ${command_profdata_merge_stop}) add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} COMMENT "Running Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) @@ -216,7 +222,9 @@ if(PYTHONINTERP_FOUND) add_custom_target("check-swift-validation${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${validation_test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} COMMENT "Running Swift validation tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) @@ -224,19 +232,13 @@ if(PYTHONINTERP_FOUND) add_custom_target("check-swift-all${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} + ${command_profdata_merge_start} COMMAND ${lit_command} "${validation_test_bin_dir}" "${test_bin_dir}" + ${command_profdata_merge_stop} DEPENDS ${test_dependencies} COMMENT "Running all Swift tests for ${VARIANT_TRIPLE}" ${cmake_3_2_USES_TERMINAL}) - if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") - add_custom_target("profdata-teardown${test_mode_target_suffix}${VARIANT_SUFFIX}" - COMMAND "${PYTHON_EXECUTABLE} ${profdata_merge_worker} stop" - DEPENDS ${test_dependencies} - COMMENT "Stopping profdata merge worker for ${VARIANT_TUPLE}" - ${cmake_3_2_USES_TERMINAL}) - endif() - endforeach() endforeach() @@ -264,12 +266,6 @@ if(PYTHONINTERP_FOUND) add_custom_target(check-swift-all${test_mode_target_suffix} DEPENDS "check-swift-all${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") - add_custom_target(profdata-merge${test_mode_target_suffix} - DEPENDS "profdata-merge${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") - - add_custom_target(profdata-teardown${test_mode_target_suffix} - DEPENDS "profdata-teardown${test_mode_target_suffix}${SWIFT_PRIMARY_VARIANT_SUFFIX}") - endforeach() endif() diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index 30f375ea9a84b..68d3149c82e32 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -159,11 +159,11 @@ def stop_server(args): def start_server(args): global CONFIG CONFIG = Config(args.debug, args.output_dir) + if not CONFIG.debug: + pid = os.fork() + if pid != 0: + sys.exit(0) # kill the parent process we forked from. try: - if not CONFIG.debug: - pid = os.fork() - if pid != 0: - sys.exit(0) # kill the parent process we forked from. run_server() finally: if os.path.exists(CONFIG.pid_file_path): @@ -173,8 +173,8 @@ def start_server(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers() + subparsers = parser.add_subparsers() start = subparsers.add_parser("start") start.add_argument("-d", "--debug", help="Run in foreground and report status.", From 1b30a49406a2173938130326f79a9e2223fa1a97 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Wed, 27 Jan 2016 10:14:16 -0800 Subject: [PATCH 13/38] [coverage] Added license header to profdata_merge_worker and added explicit choices to --swift-analyze-code-coverage --- utils/build-script | 1 + utils/profdata_merge_worker.py | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/utils/build-script b/utils/build-script index 997095ddfd97b..2624fbce0ec2c 100755 --- a/utils/build-script +++ b/utils/build-script @@ -515,6 +515,7 @@ also build for Apple watchos, but disallow tests that require an watchOS device" help="""enable code coverage analysis in Swift (set to `merged` to merge and remove intermediary profdata files)""", const="default", + choices=["default", "merged"], nargs='?', dest="swift_analyze_code_coverage") diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index 68d3149c82e32..1cf5583ed9ced 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -1,5 +1,19 @@ #!/usr/bin/env python +# utils/profdata_merge_worker.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This script is used to prevent profile data filling up available disk space +# by listening for profile data and merging them into a universal profdata +# file while tests are executing. + import os import shutil import pipes @@ -19,6 +33,8 @@ CONFIG = None class Config(): + """A class to store configuration information specified by command-line arguments. + Used to encapsulate what would normally be global variables.""" def __init__(self, debug, out_dir): self.debug = debug self.out_dir = out_dir @@ -120,7 +136,10 @@ def run_server(): if os.path.exists(CONFIG.pid_file_path): with open(CONFIG.pid_file_path) as pidfile: pid = pidfile.read() - printsync('existing process found with pid %s' % pid) + printsync(("existing process found with pid %s." + + "Ensure there are no other test runners running," + + "and delete the file at %s") + % (pid, CONFIG.pid_file_path)) return with open(CONFIG.pid_file_path, "w") as pidfile: @@ -175,6 +194,7 @@ def start_server(args): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() + start = subparsers.add_parser("start") start.add_argument("-d", "--debug", help="Run in foreground and report status.", From 5a2a809de52ded7e242b583288b792bedeb30b7c Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 10:04:53 -0800 Subject: [PATCH 14/38] [coverage] Removed printing in CMake --- test/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aa90bc4d50f00..25f547049ec90 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -207,8 +207,6 @@ if(PYTHONINTERP_FOUND) set(command_profdata_merge_stop) endif() - message(STATUS ${command_profdata_merge_start}) - message(STATUS ${command_profdata_merge_stop}) add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" ${command_upload_stdlib} ${command_clean_test_results_dir} From bb8160a09215745fcd4f8a62076c02c28ca2c717 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 10:35:39 -0800 Subject: [PATCH 15/38] [coverage] Declared SWIFT_ANALYZE_CODE_COVERAGE in CMakeLists and documented options for build-script flag. --- CMakeLists.txt | 4 ++++ utils/build-script | 4 ++-- utils/build-script-impl | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbd570b3edf52..1d31554cfebea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,10 @@ option(SWIFT_INCLUDE_DOCS "Create targets for building docs." TRUE) +option(SWIFT_ANALYZE_CODE_COVERAGE + "Build Swift with code coverage instrumenting enabled." + "NONE") + set(SWIFT_VERSION "3.0" CACHE STRING "The user-visible version of the Swift compiler") set(SWIFT_VENDOR "" CACHE STRING diff --git a/utils/build-script b/utils/build-script index 044985ce27b70..5be424a900260 100755 --- a/utils/build-script +++ b/utils/build-script @@ -514,8 +514,8 @@ also build for Apple watchos, but disallow tests that require an watchOS device" parser.add_argument("--swift-analyze-code-coverage", help="""enable code coverage analysis in Swift (set to `merged` to merge and remove intermediary profdata files)""", - const="default", - choices=["default", "merged"], + const="not-merged", + choices=["none", "not-merged", "merged"], nargs='?', dest="swift_analyze_code_coverage") diff --git a/utils/build-script-impl b/utils/build-script-impl index fc2f1ae4ca84e..fb490fb52dfee 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -71,7 +71,7 @@ KNOWN_SETTINGS=( llvm-enable-assertions "1" "enable assertions in LLVM and Clang" swift-build-type "Debug" "the CMake build variant for Swift" swift-enable-assertions "1" "enable assertions in Swift" - swift-analyze-code-coverage "default" "enable code coverage analysis in Swift (set to \`merged\` to merge profdata files and remove intermediaries)" + swift-analyze-code-coverage "not-merged" "Code coverage analysis mode for Swift (none, not-merged, merged). Defaults to none if the argument is not present, and not-merged if the argument is present without a modifier." swift-stdlib-build-type "Debug" "the CMake build variant for Swift" swift-stdlib-enable-assertions "1" "enable assertions in Swift" lldb-build-type "Debug" "the CMake build variant for LLDB" From b4c7678eab1ded1d37a0a92203bb2ca84ff7bf96 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 11:12:50 -0800 Subject: [PATCH 16/38] [coverage] Documented modes for SWIFT_ANALYZE_CODE_COVERAGE and added comment about llvm-profdata --- CMakeLists.txt | 2 +- utils/profdata_merge_worker.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d31554cfebea..8299b772e2497 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ option(SWIFT_INCLUDE_DOCS TRUE) option(SWIFT_ANALYZE_CODE_COVERAGE - "Build Swift with code coverage instrumenting enabled." + "Build Swift with code coverage instrumenting enabled (modes are NONE, NOT-MERGED, and MERGED)" "NONE") set(SWIFT_VERSION "3.0" CACHE STRING diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index 1cf5583ed9ced..73b9ac7a1d9a6 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -101,6 +101,8 @@ def merge_file_buffer(self): os.rename(self.profdata_path, self.profdata_tmp_path) self.filename_buffer.append(self.profdata_tmp_path) cleaned_files = ' '.join(pipes.quote(f) for f in self.filename_buffer) + # FIXME: This doesn't necessarily always line up with the version + # of clang++ used to build the binaries. llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" % (self.profdata_path, cleaned_files)) self.report(llvm_cmd) From e997fc3ae3e953790035e82779fca63ea77dab4f Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 11:23:59 -0800 Subject: [PATCH 17/38] [coverage] Changed 'none' to 'false' for coverage default --- CMakeLists.txt | 4 ++-- tools/SourceKit/CMakeLists.txt | 2 +- utils/build-script | 5 ++++- utils/build-script-impl | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8299b772e2497..4a146df9dde22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,8 +59,8 @@ option(SWIFT_INCLUDE_DOCS TRUE) option(SWIFT_ANALYZE_CODE_COVERAGE - "Build Swift with code coverage instrumenting enabled (modes are NONE, NOT-MERGED, and MERGED)" - "NONE") + "Build Swift with code coverage instrumenting enabled (modes are FALSE, NOT-MERGED, and MERGED)" + FALSE) set(SWIFT_VERSION "3.0" CACHE STRING "The user-visible version of the Swift compiler") diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index 581f5df6a2cc2..fd77acb6b1615 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -239,7 +239,7 @@ macro(add_sourcekit_executable name) PROPERTIES LINK_FLAGS "-Wl,-exported_symbol,_main") - if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") + if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL FALSE) set_property(TARGET "${name}" APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/utils/build-script b/utils/build-script index 5be424a900260..58278ceb59ae2 100755 --- a/utils/build-script +++ b/utils/build-script @@ -515,8 +515,9 @@ also build for Apple watchos, but disallow tests that require an watchOS device" help="""enable code coverage analysis in Swift (set to `merged` to merge and remove intermediary profdata files)""", const="not-merged", - choices=["none", "not-merged", "merged"], + choices=["false", "not-merged", "merged"], nargs='?', + default="false", # so CMake can see the inert mode as a false value dest="swift_analyze_code_coverage") parser.add_argument("--build-subdir", @@ -555,6 +556,8 @@ the number of parallel build jobs to use""", '--host-target', ])) + print(args.swift_analyze_code_coverage) + if args.host_target is None: print_with_argv0("Unknown operating system.") return 1 diff --git a/utils/build-script-impl b/utils/build-script-impl index fb490fb52dfee..13e82cf51e2e2 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -71,7 +71,7 @@ KNOWN_SETTINGS=( llvm-enable-assertions "1" "enable assertions in LLVM and Clang" swift-build-type "Debug" "the CMake build variant for Swift" swift-enable-assertions "1" "enable assertions in Swift" - swift-analyze-code-coverage "not-merged" "Code coverage analysis mode for Swift (none, not-merged, merged). Defaults to none if the argument is not present, and not-merged if the argument is present without a modifier." + swift-analyze-code-coverage "not-merged" "Code coverage analysis mode for Swift (false, not-merged, merged). Defaults to false if the argument is not present, and not-merged if the argument is present without a modifier." swift-stdlib-build-type "Debug" "the CMake build variant for Swift" swift-stdlib-enable-assertions "1" "enable assertions in Swift" lldb-build-type "Debug" "the CMake build variant for LLDB" From 44be5001577c1f0b8eb8b54c046b9e3dee3af8dd Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 13:21:38 -0800 Subject: [PATCH 18/38] [coverage] Changed back to old coverage check in CMakeLists.txt files --- tools/SourceKit/CMakeLists.txt | 2 +- tools/SourceKit/tools/complete-test/CMakeLists.txt | 2 +- tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt | 2 +- tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt | 2 +- unittests/CMakeLists.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index fd77acb6b1615..ce32804ac1ee2 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -239,7 +239,7 @@ macro(add_sourcekit_executable name) PROPERTIES LINK_FLAGS "-Wl,-exported_symbol,_main") - if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL FALSE) + if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${name}" APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/tools/SourceKit/tools/complete-test/CMakeLists.txt b/tools/SourceKit/tools/complete-test/CMakeLists.txt index d2dd6c4ed7a5c..a266ad864d64a 100644 --- a/tools/SourceKit/tools/complete-test/CMakeLists.txt +++ b/tools/SourceKit/tools/complete-test/CMakeLists.txt @@ -15,7 +15,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") - if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") + if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET complete-test APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt index 34ee688c2bf34..3a942bfefa951 100644 --- a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt @@ -9,7 +9,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") -if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") +if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-repl APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index 88d4141c5e1c6..d58edd847fc09 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -23,7 +23,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") -if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") +if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-test APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index f255c564ec6b4..5fd60ee32f8a6 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -27,7 +27,7 @@ function(add_swift_unittest test_dirname) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY LINK_FLAGS " -Xlinker -rpath -Xlinker ${SWIFT_LIBRARY_OUTPUT_INTDIR}/swift/macosx") - if(NOT SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "NONE") + if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() From 7608f887a49c9edcbf7bf28b6c6baea23c82e6ec Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 13:35:37 -0800 Subject: [PATCH 19/38] [coverage] Removed print in built script --- utils/build-script | 2 -- 1 file changed, 2 deletions(-) diff --git a/utils/build-script b/utils/build-script index 58278ceb59ae2..1b894e97bf29c 100755 --- a/utils/build-script +++ b/utils/build-script @@ -556,8 +556,6 @@ the number of parallel build jobs to use""", '--host-target', ])) - print(args.swift_analyze_code_coverage) - if args.host_target is None: print_with_argv0("Unknown operating system.") return 1 From b1d6e17d9fe8cb99d6dbb5c7ace11b8be4b6f554 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 15:31:03 -0800 Subject: [PATCH 20/38] [coverage] Added coverage_mode definitions in validation test and Unit test lit.site.cfg.in --- test/Unit/lit.site.cfg.in | 2 ++ validation-test/lit.site.cfg.in | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/Unit/lit.site.cfg.in b/test/Unit/lit.site.cfg.in index d174b190e6032..e1e0395602305 100644 --- a/test/Unit/lit.site.cfg.in +++ b/test/Unit/lit.site.cfg.in @@ -8,5 +8,7 @@ config.build_mode = lit_config.params.get('build_mode', "@LLVM_BUILD_MODE@") config.swift_obj_root = "@SWIFT_BINARY_DIR@" config.target_triple = "@TARGET_TRIPLE@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + # Let the main config do the real work. lit_config.load_config(config, "@SWIFT_SOURCE_DIR@/test/Unit/lit.cfg") diff --git a/validation-test/lit.site.cfg.in b/validation-test/lit.site.cfg.in index c4fd908003f3e..072917fa5a1b7 100644 --- a/validation-test/lit.site.cfg.in +++ b/validation-test/lit.site.cfg.in @@ -16,6 +16,8 @@ config.variant_triple = "@VARIANT_TRIPLE@" config.variant_sdk = "@VARIANT_SDK@" config.darwin_xcrun_toolchain = "@SWIFT_DARWIN_XCRUN_TOOLCHAIN@" +config.coverage_mode = "@SWIFT_ANALYZE_CODE_COVERAGE@" + if "@SWIFT_ASAN_BUILD@" == "TRUE": config.available_features.add("asan") From 0e6f798af54063b4c05d87579622546ad72f414c Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Thu, 28 Jan 2016 16:44:41 -0800 Subject: [PATCH 21/38] [coverage] Made sure code coverage doesn't rename the ninja build directory if it's "false" --- utils/build-script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/build-script b/utils/build-script index 1b894e97bf29c..55000e80776be 100755 --- a/utils/build-script +++ b/utils/build-script @@ -728,7 +728,7 @@ the number of parallel build jobs to use""", swift_build_dir_label = args.swift_build_variant if args.swift_assertions: swift_build_dir_label += "Assert" - if args.swift_analyze_code_coverage: + if args.swift_analyze_code_coverage != "false": swift_build_dir_label += "Coverage" swift_stdlib_build_dir_label = args.swift_stdlib_build_variant From 96d426165ddc5667d8f6ac97300d078499947c01 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 11:51:47 -0800 Subject: [PATCH 22/38] [coverage] Made some stylistic changes for CMake consistency --- test/CMakeLists.txt | 12 ++++++------ test/lit.cfg | 8 ++++---- utils/build-script | 6 ++---- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e3c9a77b1118..20ee8ce8aca3e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -201,13 +201,13 @@ if(PYTHONINTERP_FOUND) "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge_worker.py") if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") - set(command_profdata_merge_start - COMMAND ${PYTHON_EXECUTABLE} ${profdata_merge_worker} start -o ${swift_test_results_dir}) - set(command_profdata_merge_stop - COMMAND ${PYTHON_EXECUTABLE} ${profdata_merge_worker} stop) + set(command_profdata_merge_start + COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" start -o ${swift_test_results_dir}) + set(command_profdata_merge_stop + COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" stop) else() - set(command_profdata_merge_start) - set(command_profdata_merge_stop) + set(command_profdata_merge_start) + set(command_profdata_merge_stop) endif() add_custom_target("check-swift${test_mode_target_suffix}${VARIANT_SUFFIX}" diff --git a/test/lit.cfg b/test/lit.cfg index b0380c5e38c8e..704e47dcba9d9 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -134,8 +134,8 @@ if config.test_exec_root is None: class SwiftTest(lit.formats.ShTest): def __init__(self, coverage_mode=None, execute_external=True): - lit.formats.ShTest.__init__(self, execute_external) - if coverage_mode == "false": + lit.formats.ShTest.__init__(self, execute_external=execute_external) + if coverage_mode == "FALSE": self.coverage_mode = None else: self.coverage_mode = coverage_mode @@ -154,7 +154,7 @@ class SwiftTest(lit.formats.ShTest): os.path.join(profdir, "swift-%p.profraw") def after_test(self, test, litConfig, result): - if self.coverage_mode == "merged": + if self.coverage_mode == "MERGED": profdir = self.profdir_for_test(test) files = glob.glob(os.path.join(profdir, "swift-*.profraw")) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -178,7 +178,7 @@ if platform.system() == 'Darwin': config.environment['TOOLCHAINS'] = 'default' # testFormat: The test format to use to interpret tests. -config.test_format = SwiftTest(coverage_mode=config.coverage_mode.lower()) +config.test_format = SwiftTest(coverage_mode=config.coverage_mode) # suffixes: A list of file extensions to treat as test files. config.suffixes = ['.swift', '.ll', '.sil', '.gyb', '.m'] diff --git a/utils/build-script b/utils/build-script index 55000e80776be..115e687da9107 100755 --- a/utils/build-script +++ b/utils/build-script @@ -512,11 +512,9 @@ also build for Apple watchos, but disallow tests that require an watchOS device" action="store_true") parser.add_argument("--swift-analyze-code-coverage", - help="""enable code coverage analysis in Swift (set to `merged` to merge -and remove intermediary profdata files)""", - const="not-merged", + help="""enable code coverage analysis in Swift (false, not-merged, + merged).""", choices=["false", "not-merged", "merged"], - nargs='?', default="false", # so CMake can see the inert mode as a false value dest="swift_analyze_code_coverage") From 1777c20affafcd143ba371888e3393f8a079749b Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 12:38:22 -0800 Subject: [PATCH 23/38] [coverage] Fixed indentation in CMake --- tools/SourceKit/CMakeLists.txt | 6 +++--- tools/SourceKit/tools/complete-test/CMakeLists.txt | 2 +- tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt | 4 ++-- tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt | 4 ++-- unittests/CMakeLists.txt | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/SourceKit/CMakeLists.txt b/tools/SourceKit/CMakeLists.txt index ce32804ac1ee2..bf127bc549794 100644 --- a/tools/SourceKit/CMakeLists.txt +++ b/tools/SourceKit/CMakeLists.txt @@ -239,9 +239,9 @@ macro(add_sourcekit_executable name) PROPERTIES LINK_FLAGS "-Wl,-exported_symbol,_main") - if(SWIFT_ANALYZE_CODE_COVERAGE) - set_property(TARGET "${name}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + if(SWIFT_ANALYZE_CODE_COVERAGE) + set_property(TARGET "${name}" APPEND_STRING PROPERTY + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endif() diff --git a/tools/SourceKit/tools/complete-test/CMakeLists.txt b/tools/SourceKit/tools/complete-test/CMakeLists.txt index a266ad864d64a..2986840c0ed29 100644 --- a/tools/SourceKit/tools/complete-test/CMakeLists.txt +++ b/tools/SourceKit/tools/complete-test/CMakeLists.txt @@ -17,7 +17,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET complete-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt index 3a942bfefa951..ab0969f21e1b6 100644 --- a/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-repl/CMakeLists.txt @@ -9,9 +9,9 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") -if(SWIFT_ANALYZE_CODE_COVERAGE) + if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-repl APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt index d58edd847fc09..ed6a88607434b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt +++ b/tools/SourceKit/tools/sourcekitd-test/CMakeLists.txt @@ -23,9 +23,9 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") PROPERTIES LINK_FLAGS "-Wl,-rpath -Wl,@executable_path/../lib") -if(SWIFT_ANALYZE_CODE_COVERAGE) + if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET sourcekitd-test APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 5fd60ee32f8a6..d629f9ecbfb9a 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -29,7 +29,7 @@ function(add_swift_unittest test_dirname) if(SWIFT_ANALYZE_CODE_COVERAGE) set_property(TARGET "${test_dirname}" APPEND_STRING PROPERTY - LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") + LINK_FLAGS " -fprofile-instr-generate -fcoverage-mapping") endif() endif() endfunction() From d41bd9a161c1214aad3aa70532ab5a93112ca087 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 13:20:43 -0800 Subject: [PATCH 24/38] [coverage] Made SWIFT_ANALYZE_CODE_COVERAGE a string option instead of boolean in CMake --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a146df9dde22..f988c2ab7f632 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,9 +58,10 @@ option(SWIFT_INCLUDE_DOCS "Create targets for building docs." TRUE) -option(SWIFT_ANALYZE_CODE_COVERAGE - "Build Swift with code coverage instrumenting enabled (modes are FALSE, NOT-MERGED, and MERGED)" - FALSE) +set(SWIFT_ANALYZE_CODE_COVERAGE FALSE CACHE STRING + "Build Swift with code coverage instrumenting enabled [FALSE, NOT-MERGED, MERGED]") +set_property(CACHE SWIFT_ANALYZE_CODE_COVERAGE PROPERTY + STRINGS FALSE "NOT-MERGED" "MERGED") set(SWIFT_VERSION "3.0" CACHE STRING "The user-visible version of the Swift compiler") From f99fd759f4d4d2f08ce10ed249095caf25d23e73 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 15:09:22 -0800 Subject: [PATCH 25/38] [coverage] Converted global non-constants to explicitly passed parameters --- utils/profdata_merge_worker.py | 84 +++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index a6113adfe8543..1eb7d7d810076 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -28,9 +28,6 @@ SERVER_ADDRESS = ('localhost', 12400) TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" -FILE_QUEUE = JoinableQueue() -FILES_MERGED = set() -CONFIG = None class Config(): """A class to store configuration information specified by command-line arguments. @@ -44,15 +41,16 @@ def __init__(self, debug, out_dir, no_remove_files): self.remove_files = not no_remove_files printlock = Lock() -def printsync(msg): - if CONFIG and CONFIG.debug: - with printlock: - print >>sys.stderr, msg +def printsync(msg, config=None): + if not config.debug: + return + with printlock: + print >>sys.stderr, msg class ProfdataTCPHandler(SocketServer.StreamRequestHandler): def report(self, msg): """Convenience method for reporting status from the workers.""" - printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg) + printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg, self.server.config) def handle(self): """Receive a newline-separated list of filenames from a TCP connection @@ -74,21 +72,30 @@ def kill_server(server): # Add all the files to the queue for f in data.splitlines(): f = f.strip() - if f in FILES_MERGED: + if f in self.server.files_merged: return - FILES_MERGED.add(f) - FILE_QUEUE.put(f) + self.server.files_merged.add(f) + self.server.file_queue.put(f) + +class ProfdataServer(SocketServer.TCPServer, object): + def __init__(self, config, file_queue): + super(ProfdataServer, self).__init__(SERVER_ADDRESS, ProfdataTCPHandler) + self.config = config + self.file_queue = file_queue + self.files_merged = set() class ProfdataMergerProcess(Process): - def __init__(self): + def __init__(self, config, file_queue): Process.__init__(self) + self.config = config + self.file_queue = file_queue self.filename_buffer = [] - self.profdata_path = os.path.join(CONFIG.tmp_dir, "%s.profdata" % self.name) + self.profdata_path = os.path.join(config.tmp_dir, "%s.profdata" % self.name) self.profdata_tmp_path = self.profdata_path + ".copy" def report(self, msg): """Convenience method for reporting status from the workers.""" - printsync("\n===== %s =====\n%s\n" % (self.name, msg)) + printsync("\n===== %s =====\n%s\n" % (self.name, msg), self.config) def merge_file_buffer(self): """Merge all files in this worker's buffer and clear them. @@ -111,7 +118,7 @@ def merge_file_buffer(self): if ret != 0: self.report("llvm profdata command failed -- Exited with code %d" % ret) - if CONFIG.remove_files: + if self.config.remove_files: for f in self.filename_buffer: if os.path.exists(f): os.remove(f) @@ -122,54 +129,56 @@ def run(self): and execute merges. If it finds None in the queue, then it knows to stop waiting for the queue, merge its current buffer, and kill itself""" while True: - filename = FILE_QUEUE.get() + filename = self.file_queue.get() self.report("received filename: %s" % filename) if filename is None: self.report("received sentinel; merging...") self.merge_file_buffer() - FILE_QUEUE.task_done() + self.file_queue.task_done() break self.filename_buffer.append(filename) self.report("Adding %s to filename_buffer." % filename) if len(self.filename_buffer) >= 10: self.merge_file_buffer() - FILE_QUEUE.task_done() + self.file_queue.task_done() -def run_server(): +def run_server(config): pid = os.getpid() - if os.path.exists(CONFIG.pid_file_path): - with open(CONFIG.pid_file_path) as pidfile: + if os.path.exists(config.pid_file_path): + with open(config.pid_file_path) as pidfile: pid = pidfile.read() printsync(("existing process found with pid %s." + "Ensure there are no other test runners running," + "and delete the file at %s") - % (pid, CONFIG.pid_file_path)) + % (pid, config.pid_file_path), config) return - with open(CONFIG.pid_file_path, "w") as pidfile: + with open(config.pid_file_path, "w") as pidfile: pidfile.write(str(pid)) - processes = [ProfdataMergerProcess() for _ in range(10)] + file_queue = JoinableQueue() + + processes = [ProfdataMergerProcess(config, file_queue) for _ in range(10)] for p in processes: p.start() - server = SocketServer.TCPServer(SERVER_ADDRESS, ProfdataTCPHandler) + server = ProfdataServer(config, file_queue) server.serve_forever() for p in processes: # force each merge worker to gracefully exit - FILE_QUEUE.put(None) + file_queue.put(None) for p in processes: - printsync("waiting for %s to finish..." % p.name) + printsync("waiting for %s to finish..." % p.name, config) p.join() # now that all workers have completed, merge all their files - merge_final = ProfdataMergerProcess() - merge_final.profdata_path = CONFIG.final_profdata_path + merge_final = ProfdataMergerProcess(config, file_queue) + merge_final.profdata_path = config.final_profdata_path for p in processes: if os.path.exists(p.profdata_path): - printsync("merging " + p.profdata_path + "...") + printsync("merging " + p.profdata_path + "...", config) merge_final.filename_buffer.append(p.profdata_path) merge_final.merge_file_buffer() @@ -180,19 +189,18 @@ def stop_server(args): sock.close() def start_server(args): - global CONFIG - CONFIG = Config(args.debug, args.output_dir, args.no_remove) - if not CONFIG.debug: + config = Config(args.debug, args.output_dir, args.no_remove) + if not config.debug: pid = os.fork() if pid != 0: sys.exit(0) # kill the parent process we forked from. try: - run_server() + run_server(config) finally: - if os.path.exists(CONFIG.pid_file_path): - os.remove(CONFIG.pid_file_path) - if os.path.exists(CONFIG.tmp_dir): - shutil.rmtree(CONFIG.tmp_dir, ignore_errors=True) + if os.path.exists(config.pid_file_path): + os.remove(config.pid_file_path) + if os.path.exists(config.tmp_dir): + shutil.rmtree(config.tmp_dir, ignore_errors=True) if __name__ == "__main__": parser = argparse.ArgumentParser() From d7eff48da19e7e75307043a8ea9ee2abeaa1fa8e Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 15:11:54 -0800 Subject: [PATCH 26/38] [coverage] Converted to print function --- utils/profdata_merge_worker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py index 1eb7d7d810076..b5d9eb44fc8d3 100755 --- a/utils/profdata_merge_worker.py +++ b/utils/profdata_merge_worker.py @@ -14,6 +14,7 @@ # by listening for profile data and merging them into a universal profdata # file while tests are executing. +from __future__ import print_function import os import shutil import pipes @@ -45,7 +46,7 @@ def printsync(msg, config=None): if not config.debug: return with printlock: - print >>sys.stderr, msg + print(msg, file=sys.stderr) class ProfdataTCPHandler(SocketServer.StreamRequestHandler): def report(self, msg): From 693d7dad72e16deb0fd32f941b0b1c19df0e99ce Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 15:16:48 -0800 Subject: [PATCH 27/38] [coverage] Converted to explicit super in lit.cfg --- test/lit.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lit.cfg b/test/lit.cfg index 704e47dcba9d9..7fc74ad30cdfa 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -132,9 +132,9 @@ if config.test_exec_root is None: ### -class SwiftTest(lit.formats.ShTest): +class SwiftTest(lit.formats.ShTest, object): def __init__(self, coverage_mode=None, execute_external=True): - lit.formats.ShTest.__init__(self, execute_external=execute_external) + super(SwiftTest, self).__init__(execute_external=execute_external) if coverage_mode == "FALSE": self.coverage_mode = None else: From 65722679319e45588683bf19bb1bc5736b6993e1 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 15:52:56 -0800 Subject: [PATCH 28/38] [coverage] Split profdata_merge_worker.py into separate files within a module --- test/CMakeLists.txt | 2 +- utils/profdata_merge/__init__.py | 0 utils/profdata_merge/config.py | 14 ++ utils/profdata_merge/main.py | 56 ++++++++ utils/profdata_merge/process.py | 76 +++++++++++ utils/profdata_merge/runner.py | 83 +++++++++++ utils/profdata_merge/server.py | 55 ++++++++ utils/profdata_merge_worker.py | 227 ------------------------------- 8 files changed, 285 insertions(+), 228 deletions(-) create mode 100644 utils/profdata_merge/__init__.py create mode 100644 utils/profdata_merge/config.py create mode 100755 utils/profdata_merge/main.py create mode 100644 utils/profdata_merge/process.py create mode 100644 utils/profdata_merge/runner.py create mode 100644 utils/profdata_merge/server.py delete mode 100755 utils/profdata_merge_worker.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 20ee8ce8aca3e..7222be840e267 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -198,7 +198,7 @@ if(PYTHONINTERP_FOUND) "${validation_test_bin_dir}/lit.site.cfg" "validation-test${VARIANT_SUFFIX}.lit.site.cfg") set(profdata_merge_worker - "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge_worker.py") + "${CMAKE_CURRENT_SOURCE_DIR}/../utils/profdata_merge/main.py") if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") set(command_profdata_merge_start diff --git a/utils/profdata_merge/__init__.py b/utils/profdata_merge/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py new file mode 100644 index 0000000000000..b77413bbe66b0 --- /dev/null +++ b/utils/profdata_merge/config.py @@ -0,0 +1,14 @@ +import tempfile +import os + +class Config(): + """A class to store configuration information specified by command-line arguments. + Used to encapsulate what would normally be global variables.""" + def __init__(self, debug, out_dir, no_remove_files): + self.debug = debug + self.out_dir = out_dir + self.tmp_dir = tempfile.mkdtemp() + self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") + self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") + self.remove_files = not no_remove_files + diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py new file mode 100755 index 0000000000000..c11d9595f1a4c --- /dev/null +++ b/utils/profdata_merge/main.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# utils/profdata_merge/main.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This module is used to prevent profile data filling up available disk space +# by listening for profile data and merging them into a universal profdata +# file while tests are executing. +# This file invokes the runner after parsing arguments. + +from __future__ import print_function +import sys +import argparse +from multiprocessing import Lock + +import runner + +SERVER_ADDRESS = ('localhost', 12400) +TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" + +printlock = Lock() +def printsync(msg, config): + if not config.debug: + return + with printlock: + print(msg, file=sys.stderr) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + subparsers = parser.add_subparsers() + + start = subparsers.add_parser("start") + start.add_argument("-d", "--debug", + help="Run in foreground and report status.", + action="store_true") + start.add_argument("-o", "--output-dir", + help="The directory to write the PID file and final profdata file.", + default="/tmp") + start.add_argument("--no-remove", + action="store_true", + help="Don't remove profraw files after merging them.") + start.set_defaults(func=runner.start_server) + + stop = subparsers.add_parser("stop") + stop.set_defaults(func=runner.stop_server) + + args = parser.parse_args() + args.func(args) diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py new file mode 100644 index 0000000000000..269ee36c512f5 --- /dev/null +++ b/utils/profdata_merge/process.py @@ -0,0 +1,76 @@ +# utils/profdata_merge/process.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the worker processes that watch a file queue for files and +# merge profile data in parallel. + +from multiprocessing import Process +from main import printsync +import pipes +import os + +class ProfdataMergerProcess(Process): + def __init__(self, config, file_queue): + Process.__init__(self) + self.config = config + self.file_queue = file_queue + self.filename_buffer = [] + self.profdata_path = os.path.join(config.tmp_dir, "%s.profdata" % self.name) + self.profdata_tmp_path = self.profdata_path + ".copy" + + def report(self, msg): + """Convenience method for reporting status from the workers.""" + printsync("\n===== %s =====\n%s\n" % (self.name, msg), self.config) + + def merge_file_buffer(self): + """Merge all files in this worker's buffer and clear them. + This method makes a copy of the working merge progress, then + calls llvm-cov merge with up to 10 filenames, plus the current + in-progress merge.""" + if not self.filename_buffer: + self.report("no files to merge...") + return + if os.path.exists(self.profdata_path): + os.rename(self.profdata_path, self.profdata_tmp_path) + self.filename_buffer.append(self.profdata_tmp_path) + cleaned_files = ' '.join(pipes.quote(f) for f in self.filename_buffer) + # FIXME: This doesn't necessarily always line up with the version + # of clang++ used to build the binaries. + llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" + % (self.profdata_path, cleaned_files)) + self.report(llvm_cmd) + ret = subprocess.call(llvm_cmd, shell=True) + if ret != 0: + self.report("llvm profdata command failed -- Exited with code %d" + % ret) + if self.config.remove_files: + for f in self.filename_buffer: + if os.path.exists(f): + os.remove(f) + self.filename_buffer = [] + + def run(self): + """Blocks and waits for the file queue, so it can fill its buffer + and execute merges. If it finds None in the queue, then it knows to + stop waiting for the queue, merge its current buffer, and kill itself""" + while True: + filename = self.file_queue.get() + self.report("received filename: %s" % filename) + if filename is None: + self.report("received sentinel; merging...") + self.merge_file_buffer() + self.file_queue.task_done() + break + self.filename_buffer.append(filename) + self.report("Adding %s to filename_buffer." % filename) + if len(self.filename_buffer) >= 10: + self.merge_file_buffer() + self.file_queue.task_done() + diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py new file mode 100644 index 0000000000000..eab97856e396e --- /dev/null +++ b/utils/profdata_merge/runner.py @@ -0,0 +1,83 @@ +# utils/profdata_merge/runner.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the main subroutines that invoke or stop the merge worker. + +import shutil +import os +import socket +import sys + +from multiprocessing import Lock, Process, Queue, JoinableQueue +from process import ProfdataMergerProcess +from server import ProfdataServer +from main import printsync, TESTS_FINISHED_SENTINEL +from config import Config + +def run_server(config): + pid = os.getpid() + if os.path.exists(config.pid_file_path): + with open(config.pid_file_path) as pidfile: + pid = pidfile.read() + printsync(("existing process found with pid %s." + + "Ensure there are no other test runners running," + + "and delete the file at %s") + % (pid, config.pid_file_path), config) + return + + with open(config.pid_file_path, "w") as pidfile: + pidfile.write(str(pid)) + + file_queue = JoinableQueue() + + processes = [ProfdataMergerProcess(config, file_queue) for _ in range(10)] + for p in processes: + p.start() + + server = ProfdataServer(config, file_queue) + server.serve_forever() + + for p in processes: + # force each merge worker to gracefully exit + file_queue.put(None) + + for p in processes: + printsync("waiting for %s to finish..." % p.name, config) + p.join() + + # now that all workers have completed, merge all their files + merge_final = ProfdataMergerProcess(config, file_queue) + merge_final.profdata_path = config.final_profdata_path + for p in processes: + if os.path.exists(p.profdata_path): + printsync("merging " + p.profdata_path + "...", config) + merge_final.filename_buffer.append(p.profdata_path) + merge_final.merge_file_buffer() + +def stop_server(args): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(SERVER_ADDRESS) + sock.send(TESTS_FINISHED_SENTINEL) + sock.close() + +def start_server(args): + config = Config(args.debug, args.output_dir, args.no_remove) + if not config.debug: + pid = os.fork() + if pid != 0: + sys.exit(0) # kill the parent process we forked from. + try: + run_server(config) + finally: + if os.path.exists(config.pid_file_path): + os.remove(config.pid_file_path) + if os.path.exists(config.tmp_dir): + shutil.rmtree(config.tmp_dir, ignore_errors=True) + diff --git a/utils/profdata_merge/server.py b/utils/profdata_merge/server.py new file mode 100644 index 0000000000000..8074d9ed61fd7 --- /dev/null +++ b/utils/profdata_merge/server.py @@ -0,0 +1,55 @@ +# utils/profdata_merge/server.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the server and handler definitions that pass files to +# the merge worker processes. + +import SocketServer +import thread + +from main import printsync, SERVER_ADDRESS, TESTS_FINISHED_SENTINEL + +class ProfdataTCPHandler(SocketServer.StreamRequestHandler): + def report(self, msg): + """Convenience method for reporting status from the workers.""" + printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg, + self.server.config) + + def handle(self): + """Receive a newline-separated list of filenames from a TCP connection + and add them to the shared merge queue, where the workers will + execute llvm-profdata merge commands.""" + data = self.rfile.read() + self.report("received data (length %d): %s" % (len(data), data)) + + # Stop once we receive the sentinel + if data.startswith(TESTS_FINISHED_SENTINEL): + self.report("received sentinel; killing server...") + self.finish() + self.connection.close() + def kill_server(server): + server.shutdown() + # must be killed on another thread, or else deadlock + thread.start_new_thread(kill_server, (self.server,)) + else: + # Add all the files to the queue + for f in data.splitlines(): + f = f.strip() + if f in self.server.files_merged: + return + self.server.files_merged.add(f) + self.server.file_queue.put(f) + +class ProfdataServer(SocketServer.TCPServer, object): + def __init__(self, config, file_queue): + super(ProfdataServer, self).__init__(SERVER_ADDRESS, ProfdataTCPHandler) + self.config = config + self.file_queue = file_queue + self.files_merged = set() diff --git a/utils/profdata_merge_worker.py b/utils/profdata_merge_worker.py deleted file mode 100755 index b5d9eb44fc8d3..0000000000000 --- a/utils/profdata_merge_worker.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/env python - -# utils/profdata_merge_worker.py -# -# This source file is part of the Swift.org open source project -# -# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors -# Licensed under Apache License v2.0 with Runtime Library Exception -# -# See http://swift.org/LICENSE.txt for license information -# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors - -# This script is used to prevent profile data filling up available disk space -# by listening for profile data and merging them into a universal profdata -# file while tests are executing. - -from __future__ import print_function -import os -import shutil -import pipes -import sys -import socket -import SocketServer -from multiprocessing import Lock, Process, Queue, JoinableQueue -import thread -import subprocess -import tempfile -import argparse - -SERVER_ADDRESS = ('localhost', 12400) -TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" - -class Config(): - """A class to store configuration information specified by command-line arguments. - Used to encapsulate what would normally be global variables.""" - def __init__(self, debug, out_dir, no_remove_files): - self.debug = debug - self.out_dir = out_dir - self.tmp_dir = tempfile.mkdtemp() - self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") - self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") - self.remove_files = not no_remove_files - -printlock = Lock() -def printsync(msg, config=None): - if not config.debug: - return - with printlock: - print(msg, file=sys.stderr) - -class ProfdataTCPHandler(SocketServer.StreamRequestHandler): - def report(self, msg): - """Convenience method for reporting status from the workers.""" - printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg, self.server.config) - - def handle(self): - """Receive a newline-separated list of filenames from a TCP connection - and add them to the shared merge queue, where the workers will - execute llvm-profdata merge commands.""" - data = self.rfile.read() - self.report("received data (length %d): %s" % (len(data), data)) - - # Stop once we receive the sentinel - if data.startswith(TESTS_FINISHED_SENTINEL): - self.report("received sentinel; killing server...") - self.finish() - self.connection.close() - def kill_server(server): - server.shutdown() - # must be killed on another thread, or else deadlock - thread.start_new_thread(kill_server, (self.server,)) - else: - # Add all the files to the queue - for f in data.splitlines(): - f = f.strip() - if f in self.server.files_merged: - return - self.server.files_merged.add(f) - self.server.file_queue.put(f) - -class ProfdataServer(SocketServer.TCPServer, object): - def __init__(self, config, file_queue): - super(ProfdataServer, self).__init__(SERVER_ADDRESS, ProfdataTCPHandler) - self.config = config - self.file_queue = file_queue - self.files_merged = set() - -class ProfdataMergerProcess(Process): - def __init__(self, config, file_queue): - Process.__init__(self) - self.config = config - self.file_queue = file_queue - self.filename_buffer = [] - self.profdata_path = os.path.join(config.tmp_dir, "%s.profdata" % self.name) - self.profdata_tmp_path = self.profdata_path + ".copy" - - def report(self, msg): - """Convenience method for reporting status from the workers.""" - printsync("\n===== %s =====\n%s\n" % (self.name, msg), self.config) - - def merge_file_buffer(self): - """Merge all files in this worker's buffer and clear them. - This method makes a copy of the working merge progress, then - calls llvm-cov merge with up to 10 filenames, plus the current - in-progress merge.""" - if not self.filename_buffer: - self.report("no files to merge...") - return - if os.path.exists(self.profdata_path): - os.rename(self.profdata_path, self.profdata_tmp_path) - self.filename_buffer.append(self.profdata_tmp_path) - cleaned_files = ' '.join(pipes.quote(f) for f in self.filename_buffer) - # FIXME: This doesn't necessarily always line up with the version - # of clang++ used to build the binaries. - llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" - % (self.profdata_path, cleaned_files)) - self.report(llvm_cmd) - ret = subprocess.call(llvm_cmd, shell=True) - if ret != 0: - self.report("llvm profdata command failed -- Exited with code %d" - % ret) - if self.config.remove_files: - for f in self.filename_buffer: - if os.path.exists(f): - os.remove(f) - self.filename_buffer = [] - - def run(self): - """Blocks and waits for the file queue, so it can fill its buffer - and execute merges. If it finds None in the queue, then it knows to - stop waiting for the queue, merge its current buffer, and kill itself""" - while True: - filename = self.file_queue.get() - self.report("received filename: %s" % filename) - if filename is None: - self.report("received sentinel; merging...") - self.merge_file_buffer() - self.file_queue.task_done() - break - self.filename_buffer.append(filename) - self.report("Adding %s to filename_buffer." % filename) - if len(self.filename_buffer) >= 10: - self.merge_file_buffer() - self.file_queue.task_done() - -def run_server(config): - pid = os.getpid() - if os.path.exists(config.pid_file_path): - with open(config.pid_file_path) as pidfile: - pid = pidfile.read() - printsync(("existing process found with pid %s." + - "Ensure there are no other test runners running," + - "and delete the file at %s") - % (pid, config.pid_file_path), config) - return - - with open(config.pid_file_path, "w") as pidfile: - pidfile.write(str(pid)) - - file_queue = JoinableQueue() - - processes = [ProfdataMergerProcess(config, file_queue) for _ in range(10)] - for p in processes: - p.start() - - server = ProfdataServer(config, file_queue) - server.serve_forever() - - for p in processes: - # force each merge worker to gracefully exit - file_queue.put(None) - - for p in processes: - printsync("waiting for %s to finish..." % p.name, config) - p.join() - - # now that all workers have completed, merge all their files - merge_final = ProfdataMergerProcess(config, file_queue) - merge_final.profdata_path = config.final_profdata_path - for p in processes: - if os.path.exists(p.profdata_path): - printsync("merging " + p.profdata_path + "...", config) - merge_final.filename_buffer.append(p.profdata_path) - merge_final.merge_file_buffer() - -def stop_server(args): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.connect(SERVER_ADDRESS) - sock.send(TESTS_FINISHED_SENTINEL) - sock.close() - -def start_server(args): - config = Config(args.debug, args.output_dir, args.no_remove) - if not config.debug: - pid = os.fork() - if pid != 0: - sys.exit(0) # kill the parent process we forked from. - try: - run_server(config) - finally: - if os.path.exists(config.pid_file_path): - os.remove(config.pid_file_path) - if os.path.exists(config.tmp_dir): - shutil.rmtree(config.tmp_dir, ignore_errors=True) - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - - subparsers = parser.add_subparsers() - - start = subparsers.add_parser("start") - start.add_argument("-d", "--debug", - help="Run in foreground and report status.", - action="store_true") - start.add_argument("-o", "--output-dir", - help="The directory to write the PID file and final profdata file.", - default="/tmp") - start.add_argument("--no-remove", - action="store_true", - help="Don't remove profraw files after merging them.") - start.set_defaults(func=start_server) - - stop = subparsers.add_parser("stop") - stop.set_defaults(func=stop_server) - - args = parser.parse_args() - args.func(args) From dea48f43dc91ea2b3baca8c01612455fb74c64e1 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 15:55:05 -0800 Subject: [PATCH 29/38] [coverage] Fixed missing import --- utils/profdata_merge/process.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py index 269ee36c512f5..1a5a40eb808b0 100644 --- a/utils/profdata_merge/process.py +++ b/utils/profdata_merge/process.py @@ -15,6 +15,7 @@ from main import printsync import pipes import os +import subprocess class ProfdataMergerProcess(Process): def __init__(self, config, file_queue): From eb9fb9cde918e290540adb407afdab091a346d39 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 16:03:57 -0800 Subject: [PATCH 30/38] [coverage] Used gettempdir() instead of hard-coding /tmp --- utils/profdata_merge/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index c11d9595f1a4c..bfee54994b392 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -18,6 +18,7 @@ from __future__ import print_function import sys import argparse +import tempfile from multiprocessing import Lock import runner @@ -43,12 +44,13 @@ def printsync(msg, config): action="store_true") start.add_argument("-o", "--output-dir", help="The directory to write the PID file and final profdata file.", - default="/tmp") + default=tempfile.gettempdir()) start.add_argument("--no-remove", action="store_true", help="Don't remove profraw files after merging them.") start.set_defaults(func=runner.start_server) + stop = subparsers.add_parser("stop") stop.set_defaults(func=runner.stop_server) From 45df8491f434636599f90c4a57f75f79ac72cdae Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 16:05:13 -0800 Subject: [PATCH 31/38] [coverage] Explicit super in process.py --- utils/profdata_merge/process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py index 1a5a40eb808b0..f495d9124624d 100644 --- a/utils/profdata_merge/process.py +++ b/utils/profdata_merge/process.py @@ -19,7 +19,7 @@ class ProfdataMergerProcess(Process): def __init__(self, config, file_queue): - Process.__init__(self) + super(ProfdataMergerProcess, self).__init__() self.config = config self.file_queue = file_queue self.filename_buffer = [] From 873af8e3d3c889bc8025c338de39ebd89a2c6ae5 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 16:13:07 -0800 Subject: [PATCH 32/38] [coverage] Fail gracefully on non-Darwin --- utils/profdata_merge/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index bfee54994b392..4828af42d2e0f 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -34,6 +34,9 @@ def printsync(msg, config): print(msg, file=sys.stderr) if __name__ == "__main__": + if sys.platform != "darwin": + sys.exit("Error: The profile data merge worker requires OS X.") + parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() From f713042ebceaf31e388c725f18c11322c0d3d766 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 17:02:27 -0800 Subject: [PATCH 33/38] [coverage] Added license header to config.py --- utils/profdata_merge/config.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py index b77413bbe66b0..aed702b44aee9 100644 --- a/utils/profdata_merge/config.py +++ b/utils/profdata_merge/config.py @@ -1,3 +1,16 @@ +# utils/profdata_merge/config.py +# +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors + +# This file contains the data structure that transforms arguments into usable +# values + import tempfile import os From c75fa764c86048c34ccba56739bc349dbb0010e2 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Mon, 1 Feb 2016 17:30:17 -0800 Subject: [PATCH 34/38] [coverage] Added README for the profdata module --- utils/profdata_merge/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 utils/profdata_merge/README.md diff --git a/utils/profdata_merge/README.md b/utils/profdata_merge/README.md new file mode 100644 index 0000000000000..2c5a3fcff2b56 --- /dev/null +++ b/utils/profdata_merge/README.md @@ -0,0 +1,17 @@ +# Profdata Merge + +Because LLVM's instrumented builds produce a `profraw` file every time a +they are executed, the Swift test suite, when run with coverage enabled, +produces thousands of 30MB profile files. + +When build-script is run with `--swift-analyze-code-coverage merged`, this +module will get called before the tests run and spin up a small server that +runs alongside the test suite. The server accepts filenames that it receives +after each test directory is finished executing, and feeds them to 10 worker +processes which concurrently merge those files together. Doing so means the +test results directory doesn't balloon in size, as the files are merged together +and removed often. + +Once the test suite is finished, it sends a crafted message to the server that +tells it to merge any files left in the queue, then merges all the workers +together into one final file. From 6ec0dd30663a8f5c2fec33627d66de191b652048 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 2 Feb 2016 10:43:29 -0800 Subject: [PATCH 35/38] [coverage] Added log file output to profdata merger --- utils/profdata_merge/config.py | 3 ++- utils/profdata_merge/main.py | 4 +++- utils/profdata_merge/runner.py | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py index aed702b44aee9..957440fbd1a0e 100644 --- a/utils/profdata_merge/config.py +++ b/utils/profdata_merge/config.py @@ -17,11 +17,12 @@ class Config(): """A class to store configuration information specified by command-line arguments. Used to encapsulate what would normally be global variables.""" - def __init__(self, debug, out_dir, no_remove_files): + def __init__(self, debug, out_dir, no_remove_files, log_file): self.debug = debug self.out_dir = out_dir self.tmp_dir = tempfile.mkdtemp() self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") self.remove_files = not no_remove_files + self.log_file = log_file diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index 4828af42d2e0f..47270cadb6ddc 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -31,7 +31,7 @@ def printsync(msg, config): if not config.debug: return with printlock: - print(msg, file=sys.stderr) + print(msg, file=config.log_file) if __name__ == "__main__": if sys.platform != "darwin": @@ -48,6 +48,8 @@ def printsync(msg, config): start.add_argument("-o", "--output-dir", help="The directory to write the PID file and final profdata file.", default=tempfile.gettempdir()) + start.add_argument("-l", "--log-file", + help="The file to write logs in debug mode.") start.add_argument("--no-remove", action="store_true", help="Don't remove profraw files after merging them.") diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py index eab97856e396e..2dfaa8cfdeff9 100644 --- a/utils/profdata_merge/runner.py +++ b/utils/profdata_merge/runner.py @@ -68,7 +68,8 @@ def stop_server(args): sock.close() def start_server(args): - config = Config(args.debug, args.output_dir, args.no_remove) + log_file = open(args.log_file, 'w') if args.log_file else sys.stderr + config = Config(args.debug, args.output_dir, args.no_remove, log_file) if not config.debug: pid = os.fork() if pid != 0: @@ -80,4 +81,5 @@ def start_server(args): os.remove(config.pid_file_path) if os.path.exists(config.tmp_dir): shutil.rmtree(config.tmp_dir, ignore_errors=True) - + if log_file != sys.stderr: + log_file.close() From cf30318aeb28308defe6d41ed4a09d4a950cdb9b Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 2 Feb 2016 12:59:13 -0800 Subject: [PATCH 36/38] [coverage] Converted to python standard logging framework --- test/CMakeLists.txt | 4 +++- utils/profdata_merge/config.py | 8 +++----- utils/profdata_merge/main.py | 20 ++++++++++---------- utils/profdata_merge/process.py | 8 ++++---- utils/profdata_merge/runner.py | 24 +++++++++++------------- utils/profdata_merge/server.py | 11 +++++------ 6 files changed, 36 insertions(+), 39 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7222be840e267..bb03809d1dd06 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -202,7 +202,9 @@ if(PYTHONINTERP_FOUND) if(SWIFT_ANALYZE_CODE_COVERAGE STREQUAL "MERGED") set(command_profdata_merge_start - COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" start -o ${swift_test_results_dir}) + COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" start + -o "${swift_test_results_dir}" + -l "${swift_test_results_dir}/profdata_merge.log") set(command_profdata_merge_stop COMMAND "${PYTHON_EXECUTABLE}" "${profdata_merge_worker}" stop) else() diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py index 957440fbd1a0e..c2ba31d84c39f 100644 --- a/utils/profdata_merge/config.py +++ b/utils/profdata_merge/config.py @@ -15,14 +15,12 @@ import os class Config(): - """A class to store configuration information specified by command-line arguments. - Used to encapsulate what would normally be global variables.""" - def __init__(self, debug, out_dir, no_remove_files, log_file): - self.debug = debug + """A class to store configuration information specified by command-line + arguments.""" + def __init__(self, out_dir, no_remove_files): self.out_dir = out_dir self.tmp_dir = tempfile.mkdtemp() self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") self.remove_files = not no_remove_files - self.log_file = log_file diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index 47270cadb6ddc..f24bbf53250b3 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -19,6 +19,7 @@ import sys import argparse import tempfile +import logging from multiprocessing import Lock import runner @@ -26,18 +27,13 @@ SERVER_ADDRESS = ('localhost', 12400) TESTS_FINISHED_SENTINEL = "PROFDATA_MERGE_WORKER_TESTS_FINISHED_SENTINEL" -printlock = Lock() -def printsync(msg, config): - if not config.debug: - return - with printlock: - print(msg, file=config.log_file) - if __name__ == "__main__": if sys.platform != "darwin": sys.exit("Error: The profile data merge worker requires OS X.") parser = argparse.ArgumentParser() + parser.add_argument("-l", "--log-file", + help="The file to write logs in debug mode.") subparsers = parser.add_subparsers() @@ -48,16 +44,20 @@ def printsync(msg, config): start.add_argument("-o", "--output-dir", help="The directory to write the PID file and final profdata file.", default=tempfile.gettempdir()) - start.add_argument("-l", "--log-file", - help="The file to write logs in debug mode.") start.add_argument("--no-remove", action="store_true", help="Don't remove profraw files after merging them.") start.set_defaults(func=runner.start_server) - stop = subparsers.add_parser("stop") stop.set_defaults(func=runner.stop_server) args = parser.parse_args() + + log_args = {'level': logging.DEBUG} + if args.log_file: + log_args['filename'] = args.log_file + + logging.basicConfig(**log_args) + args.func(args) diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py index f495d9124624d..9acbef4a7e82a 100644 --- a/utils/profdata_merge/process.py +++ b/utils/profdata_merge/process.py @@ -12,10 +12,10 @@ # merge profile data in parallel. from multiprocessing import Process -from main import printsync import pipes import os import subprocess +import logging class ProfdataMergerProcess(Process): def __init__(self, config, file_queue): @@ -26,9 +26,9 @@ def __init__(self, config, file_queue): self.profdata_path = os.path.join(config.tmp_dir, "%s.profdata" % self.name) self.profdata_tmp_path = self.profdata_path + ".copy" - def report(self, msg): + def report(self, msg, level=logging.INFO): """Convenience method for reporting status from the workers.""" - printsync("\n===== %s =====\n%s\n" % (self.name, msg), self.config) + logging.log(level, "[%s]: %s" % (self.name, msg)) def merge_file_buffer(self): """Merge all files in this worker's buffer and clear them. @@ -50,7 +50,7 @@ def merge_file_buffer(self): ret = subprocess.call(llvm_cmd, shell=True) if ret != 0: self.report("llvm profdata command failed -- Exited with code %d" - % ret) + % ret, level=logging.ERROR) if self.config.remove_files: for f in self.filename_buffer: if os.path.exists(f): diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py index 2dfaa8cfdeff9..49c981dae3e3d 100644 --- a/utils/profdata_merge/runner.py +++ b/utils/profdata_merge/runner.py @@ -14,11 +14,12 @@ import os import socket import sys +import logging from multiprocessing import Lock, Process, Queue, JoinableQueue from process import ProfdataMergerProcess from server import ProfdataServer -from main import printsync, TESTS_FINISHED_SENTINEL +from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL from config import Config def run_server(config): @@ -26,10 +27,10 @@ def run_server(config): if os.path.exists(config.pid_file_path): with open(config.pid_file_path) as pidfile: pid = pidfile.read() - printsync(("existing process found with pid %s." + - "Ensure there are no other test runners running," + - "and delete the file at %s") - % (pid, config.pid_file_path), config) + logging.error(("existing process found with pid %s." + + "Ensure there are no other test runners running," + + "and delete the file at %s") + % (pid, config.pid_file_path)) return with open(config.pid_file_path, "w") as pidfile: @@ -41,7 +42,7 @@ def run_server(config): for p in processes: p.start() - server = ProfdataServer(config, file_queue) + server = ProfdataServer(file_queue) server.serve_forever() for p in processes: @@ -49,7 +50,7 @@ def run_server(config): file_queue.put(None) for p in processes: - printsync("waiting for %s to finish..." % p.name, config) + logging.info("waiting for %s to finish..." % p.name) p.join() # now that all workers have completed, merge all their files @@ -57,7 +58,7 @@ def run_server(config): merge_final.profdata_path = config.final_profdata_path for p in processes: if os.path.exists(p.profdata_path): - printsync("merging " + p.profdata_path + "...", config) + logging.info("merging " + p.profdata_path + "...") merge_final.filename_buffer.append(p.profdata_path) merge_final.merge_file_buffer() @@ -68,9 +69,8 @@ def stop_server(args): sock.close() def start_server(args): - log_file = open(args.log_file, 'w') if args.log_file else sys.stderr - config = Config(args.debug, args.output_dir, args.no_remove, log_file) - if not config.debug: + config = Config(args.output_dir, args.no_remove) + if not args.debug: pid = os.fork() if pid != 0: sys.exit(0) # kill the parent process we forked from. @@ -81,5 +81,3 @@ def start_server(args): os.remove(config.pid_file_path) if os.path.exists(config.tmp_dir): shutil.rmtree(config.tmp_dir, ignore_errors=True) - if log_file != sys.stderr: - log_file.close() diff --git a/utils/profdata_merge/server.py b/utils/profdata_merge/server.py index 8074d9ed61fd7..327ddd928c2f2 100644 --- a/utils/profdata_merge/server.py +++ b/utils/profdata_merge/server.py @@ -13,21 +13,21 @@ import SocketServer import thread +import logging -from main import printsync, SERVER_ADDRESS, TESTS_FINISHED_SENTINEL +from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL class ProfdataTCPHandler(SocketServer.StreamRequestHandler): def report(self, msg): """Convenience method for reporting status from the workers.""" - printsync("\n===== ProfdataTCPHandler =====\n%s\n" % msg, - self.server.config) + logging.info("[ProfdataTCPHandler]: %s" % msg) def handle(self): """Receive a newline-separated list of filenames from a TCP connection and add them to the shared merge queue, where the workers will execute llvm-profdata merge commands.""" data = self.rfile.read() - self.report("received data (length %d): %s" % (len(data), data)) + self.report("received data (length %d): %s" % (len(data), repr(data))) # Stop once we receive the sentinel if data.startswith(TESTS_FINISHED_SENTINEL): @@ -48,8 +48,7 @@ def kill_server(server): self.server.file_queue.put(f) class ProfdataServer(SocketServer.TCPServer, object): - def __init__(self, config, file_queue): + def __init__(self, file_queue): super(ProfdataServer, self).__init__(SERVER_ADDRESS, ProfdataTCPHandler) - self.config = config self.file_queue = file_queue self.files_merged = set() From 13bae93d999c2c7df5472358aa6c3219affa685e Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 2 Feb 2016 13:26:27 -0800 Subject: [PATCH 37/38] [coverage] pep8 --- utils/profdata_merge/config.py | 5 +++-- utils/profdata_merge/main.py | 15 ++++++++------- utils/profdata_merge/process.py | 15 ++++++++------- utils/profdata_merge/runner.py | 12 ++++++++---- utils/profdata_merge/server.py | 8 +++++++- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/utils/profdata_merge/config.py b/utils/profdata_merge/config.py index c2ba31d84c39f..b727239b2fc56 100644 --- a/utils/profdata_merge/config.py +++ b/utils/profdata_merge/config.py @@ -14,13 +14,14 @@ import tempfile import os + class Config(): """A class to store configuration information specified by command-line arguments.""" def __init__(self, out_dir, no_remove_files): self.out_dir = out_dir self.tmp_dir = tempfile.mkdtemp() - self.pid_file_path = os.path.join(self.out_dir, "profdata_merge_worker.pid") + self.pid_file_path = os.path.join(self.out_dir, + "profdata_merge_worker.pid") self.final_profdata_path = os.path.join(self.out_dir, "swift.profdata") self.remove_files = not no_remove_files - diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index f24bbf53250b3..332ee5a775dd2 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -33,20 +33,21 @@ parser = argparse.ArgumentParser() parser.add_argument("-l", "--log-file", - help="The file to write logs in debug mode.") + help="The file to write logs in debug mode.") subparsers = parser.add_subparsers() start = subparsers.add_parser("start") start.add_argument("-d", "--debug", - help="Run in foreground and report status.", - action="store_true") + help="Run in foreground and report status.", + action="store_true") start.add_argument("-o", "--output-dir", - help="The directory to write the PID file and final profdata file.", - default=tempfile.gettempdir()) + help=("The directory to write the PID file" + + "and final profdata file."), + default=tempfile.gettempdir()) start.add_argument("--no-remove", - action="store_true", - help="Don't remove profraw files after merging them.") + action="store_true", + help="Don't remove profraw files after merging them.") start.set_defaults(func=runner.start_server) stop = subparsers.add_parser("stop") diff --git a/utils/profdata_merge/process.py b/utils/profdata_merge/process.py index 9acbef4a7e82a..f7608a4e72916 100644 --- a/utils/profdata_merge/process.py +++ b/utils/profdata_merge/process.py @@ -17,13 +17,15 @@ import subprocess import logging + class ProfdataMergerProcess(Process): def __init__(self, config, file_queue): super(ProfdataMergerProcess, self).__init__() self.config = config self.file_queue = file_queue self.filename_buffer = [] - self.profdata_path = os.path.join(config.tmp_dir, "%s.profdata" % self.name) + self.profdata_path = os.path.join(config.tmp_dir, + "%s.profdata" % self.name) self.profdata_tmp_path = self.profdata_path + ".copy" def report(self, msg, level=logging.INFO): @@ -45,12 +47,12 @@ def merge_file_buffer(self): # FIXME: This doesn't necessarily always line up with the version # of clang++ used to build the binaries. llvm_cmd = ("xcrun llvm-profdata merge -o %s %s" - % (self.profdata_path, cleaned_files)) + % (self.profdata_path, cleaned_files)) self.report(llvm_cmd) ret = subprocess.call(llvm_cmd, shell=True) if ret != 0: self.report("llvm profdata command failed -- Exited with code %d" - % ret, level=logging.ERROR) + % ret, level=logging.ERROR) if self.config.remove_files: for f in self.filename_buffer: if os.path.exists(f): @@ -58,9 +60,9 @@ def merge_file_buffer(self): self.filename_buffer = [] def run(self): - """Blocks and waits for the file queue, so it can fill its buffer - and execute merges. If it finds None in the queue, then it knows to - stop waiting for the queue, merge its current buffer, and kill itself""" + """Blocks and waits for the file queue so it can fill its buffer and + execute merges. If it finds None in the queue, then it knows to stop + waiting for the queue, merge its current buffer, and kill itself""" while True: filename = self.file_queue.get() self.report("received filename: %s" % filename) @@ -74,4 +76,3 @@ def run(self): if len(self.filename_buffer) >= 10: self.merge_file_buffer() self.file_queue.task_done() - diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py index 49c981dae3e3d..c65ef8608dea0 100644 --- a/utils/profdata_merge/runner.py +++ b/utils/profdata_merge/runner.py @@ -22,15 +22,16 @@ from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL from config import Config + def run_server(config): pid = os.getpid() if os.path.exists(config.pid_file_path): with open(config.pid_file_path) as pidfile: pid = pidfile.read() logging.error(("existing process found with pid %s." + - "Ensure there are no other test runners running," + - "and delete the file at %s") - % (pid, config.pid_file_path)) + "Ensure there are no other test runners running," + + "and delete the file at %s") % + (pid, config.pid_file_path)) return with open(config.pid_file_path, "w") as pidfile: @@ -62,18 +63,21 @@ def run_server(config): merge_final.filename_buffer.append(p.profdata_path) merge_final.merge_file_buffer() + def stop_server(args): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(SERVER_ADDRESS) sock.send(TESTS_FINISHED_SENTINEL) sock.close() + def start_server(args): config = Config(args.output_dir, args.no_remove) if not args.debug: pid = os.fork() if pid != 0: - sys.exit(0) # kill the parent process we forked from. + # kill the parent process we forked from. + sys.exit(0) try: run_server(config) finally: diff --git a/utils/profdata_merge/server.py b/utils/profdata_merge/server.py index 327ddd928c2f2..80cb48bf6cc06 100644 --- a/utils/profdata_merge/server.py +++ b/utils/profdata_merge/server.py @@ -17,7 +17,9 @@ from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL + class ProfdataTCPHandler(SocketServer.StreamRequestHandler): + def report(self, msg): """Convenience method for reporting status from the workers.""" logging.info("[ProfdataTCPHandler]: %s" % msg) @@ -34,8 +36,10 @@ def handle(self): self.report("received sentinel; killing server...") self.finish() self.connection.close() + def kill_server(server): server.shutdown() + # must be killed on another thread, or else deadlock thread.start_new_thread(kill_server, (self.server,)) else: @@ -47,8 +51,10 @@ def kill_server(server): self.server.files_merged.add(f) self.server.file_queue.put(f) + class ProfdataServer(SocketServer.TCPServer, object): def __init__(self, file_queue): - super(ProfdataServer, self).__init__(SERVER_ADDRESS, ProfdataTCPHandler) + super(ProfdataServer, self).__init__(SERVER_ADDRESS, + ProfdataTCPHandler) self.file_queue = file_queue self.files_merged = set() From 2b2204030bd2ffc3e9867872efd62e85f4213844 Mon Sep 17 00:00:00 2001 From: Harlan Haskins Date: Tue, 2 Feb 2016 13:30:25 -0800 Subject: [PATCH 38/38] [coverage] Removed unused imports --- utils/profdata_merge/main.py | 1 - utils/profdata_merge/runner.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/profdata_merge/main.py b/utils/profdata_merge/main.py index 332ee5a775dd2..96c1bede542c8 100755 --- a/utils/profdata_merge/main.py +++ b/utils/profdata_merge/main.py @@ -20,7 +20,6 @@ import argparse import tempfile import logging -from multiprocessing import Lock import runner diff --git a/utils/profdata_merge/runner.py b/utils/profdata_merge/runner.py index c65ef8608dea0..708260d324026 100644 --- a/utils/profdata_merge/runner.py +++ b/utils/profdata_merge/runner.py @@ -16,7 +16,7 @@ import sys import logging -from multiprocessing import Lock, Process, Queue, JoinableQueue +from multiprocessing import JoinableQueue from process import ProfdataMergerProcess from server import ProfdataServer from main import SERVER_ADDRESS, TESTS_FINISHED_SENTINEL