You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2018/09/10 20:19:47 UTC

[4/6] qpid-proton git commit: PROTON-1798: cmake runtime-check improvements

PROTON-1798: cmake runtime-check improvements

Usage notes:
- new CMake variable: RUNTIME_CHECK, choose from [memcheck helgrind asan tsan OFF]
- defaults to 'memcheck' if available, else OFF
- old ENABLE_ variables for valgrind/sanitizers are deprecated
- example_test scripts check for stderr output including from killed processes

Implementation details:

- moved all runtime-check setup code to seprate runtime-check.cmake
- tool-agnostic internal CMake variables for running tests
- removed all valgrind-specific code outside of runtime-check.cmake
- example_test.py check stderr as well as exit status to catch broker issues.

NOTE: asan,tsan not yet working for python/ruby bindings, they are disabled in
san builds. See tests/preload_asan.sh for current status of the work.

NOTE: Some python soak tests for obscure messenger features were removed, they
have faulty start-up timing logic and can fail under valgrind. We can restore
them if needed but we'll need to fix the -X feature of msgr-recv to report ready
only after connections are remote open.


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/27edd9ac
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/27edd9ac
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/27edd9ac

Branch: refs/heads/master
Commit: 27edd9aca3b2b1078089ceaa2a387f0016ac6f0a
Parents: 7885bd3
Author: Alan Conway <ac...@redhat.com>
Authored: Fri Sep 7 13:20:42 2018 -0400
Committer: Alan Conway <ac...@redhat.com>
Committed: Mon Sep 10 15:41:24 2018 -0400

----------------------------------------------------------------------
 CMakeLists.txt                      |  71 +++------------
 c/examples/CMakeLists.txt           |   7 +-
 c/examples/example_test.py          |  58 +++++-------
 c/tests/CMakeLists.txt              |   4 +-
 c/tests/fuzz/CMakeLists.txt         |   2 +-
 c/tests/threaderciser.supp          |  18 ----
 c/tests/threaderciser.tsupp         |   5 -
 cpp/CMakeLists.txt                  |   2 +-
 cpp/examples/CMakeLists.txt         |  14 ++-
 cpp/examples/broker.cpp             |   4 +-
 cpp/examples/example_test.py        |  42 ++-------
 misc/config.sh.in                   |   6 --
 python/CMakeLists.txt               |   8 +-
 python/tests/proton_tests/common.py |  35 +------
 python/tests/proton_tests/soak.py   |  83 ++---------------
 python/tests/proton_tests/ssl.py    |  33 -------
 python/tox.ini.in                   |   2 +-
 ruby/CMakeLists.txt                 |   8 +-
 runtime_check.cmake                 | 123 +++++++++++++++++++++++++
 scripts/env.py                      |   5 -
 tests/preload_asan.sh               |  51 +++++++++++
 tests/py/test_subprocess.py         | 105 +++++++++++++++++++++
 tests/valgrind.supp                 | 151 +++++++++++++++++++++++++++++++
 23 files changed, 517 insertions(+), 320 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 560dc05..105f22e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,6 +38,9 @@ find_package (CyrusSASL)
 
 enable_testing ()
 
+# Set up runtime checks (valgrind, sanitizers etc.)
+include(runtime_check.cmake)  
+
 ## Variables used across components
 
 set (PN_ENV_SCRIPT "${PYTHON_EXECUTABLE}" "${CMAKE_SOURCE_DIR}/scripts/env.py")
@@ -188,23 +191,6 @@ if (CMAKE_C_COMPILER_ID MATCHES "Clang")
   set (CXX_WARNING_FLAGS "${COMPILE_WARNING_FLAGS} -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-float-equal -Wno-padded -Wno-sign-conversion -Wno-switch-enum -Wno-weak-vtables -Wno-exit-time-destructors -Wno-global-constructors -Wno-shorten-64-to-32 -Wno-documentation -Wno-documentation-unknown-command -Wno-old-style-cast -Wno-missing-noreturn")
 endif()
 
-# Sanitizer flags apply to to both GNU and clang, C and C++
-if(ENABLE_SANITIZERS)
-  set(SANITIZE_FLAGS "-g -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -fsanitize=undefined")
-endif()
-if(ENABLE_TSAN)
-  set(SANITIZE_FLAGS "-g -fno-omit-frame-pointer -fsanitize=thread")
-endif()
-if (SANITIZE_FLAGS)
-  mark_as_advanced(SANITIZE_FLAGS)
-  if(CMAKE_C_COMPILER_ID MATCHES "Clang|GNU")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZE_FLAGS}")
-  endif()
-  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZE_FLAGS}")
-  endif()
-endif()
-
 if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
   if (NOT CMAKE_OSX_ARCHITECTURES)
     set(CMAKE_OSX_ARCHITECTURES "x86_64")
@@ -300,41 +286,6 @@ endforeach()
 set (PROTON_SHARE ${SHARE_INSTALL_DIR}/proton-${PN_VERSION})
 # End of variables used during install
 
-# Can't do valgrind and coverage at athe same time - coverage takes precedence
-if (CMAKE_BUILD_TYPE MATCHES "Coverage")
-  message(STATUS "Building for coverage analysis; no run-time error detection")
-else ()
-  find_program(VALGRIND_EXECUTABLE valgrind DOC "Location of the valgrind program")
-  mark_as_advanced (VALGRIND_EXECUTABLE)
-
-  option(ENABLE_VALGRIND "Use valgrind to detect run-time problems" ON)
-  if (ENABLE_VALGRIND)
-    if (VALGRIND_EXECUTABLE)
-      set (VALGRIND_SUPPRESSIONS ${CMAKE_SOURCE_DIR}/c/tests/valgrind.supp CACHE STRING "Default valgrind suppressions")
-      set (VALGRIND_OPTIONS "--error-exitcode=42 --quiet --leak-check=full --trace-children=yes" CACHE STRING "Default valgrind options")
-      mark_as_advanced(VALGRIND_SUPPRESSIONS VALGRIND_OPTIONS)
-      set (VALGRIND_ENV "VALGRIND=${VALGRIND_EXECUTABLE}" "VALGRIND_ARGS=${VALGRIND_OPTIONS} --suppressions=${VALGRIND_SUPPRESSIONS}")
-      separate_arguments(VALGRIND_OPTIONS_LIST UNIX_COMMAND ${VALGRIND_OPTIONS})
-      set (memcheck-cmd ${VALGRIND_EXECUTABLE} ${VALGRIND_OPTIONS_LIST} "--suppressions=${VALGRIND_SUPPRESSIONS}")
-      set (racecheck-cmd ${VALGRIND_EXECUTABLE} --tool=helgrind --error-exitcode=42 --quiet)
-    else ()
-      message(STATUS "Can't locate the valgrind command; no run-time error detection")
-    endif ()
-  endif ()
-endif ()
-
-# Options to enable sanitizing compile flags. Compile flags are set in c/CMakeLists.txt
-option(ENABLE_SANITIZERS "Compile with sanitizers (ASan, UBSan, TSan); incompatible with Valgrind" OFF)
-option(ENABLE_TSAN "Compile with Thread Sanitizer (TSan); incompatible with Valgrind" OFF)
-if (ENABLE_SANITIZERS OR ENABLE_TSAN)
-  set(DISABLE ENABLE_VALGRIND ENABLE_UNDEFINED_ERROR BUILD_GO)
-  message(STATUS "Building with sanitizers; disables ${DISABLE}")
-  foreach(d ${DISABLE})
-    set(${d} OFF CACHE BOOL "Disabled to run sanitizers" FORCE)
-  endforeach()
-  unset(VALGRIND_ENV)
-endif()
-
 # Set result to a native search path - used by examples and binding tests.
 # args after result are directories or search paths.
 macro(set_search_path result)
@@ -379,11 +330,11 @@ endif()
 find_program(GO_EXE go)
 mark_as_advanced(GO_EXE)
 if (GO_EXE)
-  if(WIN32)
+  set (DEFAULT_GO ON)
+  if(WIN32 OR RUNTIME_CHECK)
     # Go on windows requires gcc tool chain
+    # Go does not work with C-based runtime checkers.
     set (DEFAULT_GO OFF)
-  else()
-    set (DEFAULT_GO ON)
   endif()
 endif (GO_EXE)
 
@@ -405,15 +356,17 @@ if(SWIG_FOUND)
 
   # Prerequisites for Python wrapper:
   find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT)
-  if (PYTHONLIBS_FOUND)
+  # TODO aconway 2018-09-07: get python binding tests working with sanitizers
+  if (PYTHONLIBS_FOUND AND NOT SANITIZE_FLAGS)
     set (DEFAULT_PYTHON ON)
-  endif (PYTHONLIBS_FOUND)
+  endif ()
 
   # Prerequisites for Ruby:
   find_package(Ruby)
-  if (RUBY_FOUND)
+  # TODO aconway 2018-09-07: get ruby binding tests working with sanitizers
+  if (RUBY_FOUND AND NOT SANITIZE_FLAGS)
     set (DEFAULT_RUBY ON)
-  endif (RUBY_FOUND)
+  endif ()
 endif()
 
 # To kick-start a build with just a few bindings enabled by default, e.g. ruby and go:

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/examples/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/c/examples/CMakeLists.txt b/c/examples/CMakeLists.txt
index 6128f62..b04e444 100644
--- a/c/examples/CMakeLists.txt
+++ b/c/examples/CMakeLists.txt
@@ -45,6 +45,9 @@ endif()
 
 add_test(
   NAME c-example-tests
-  COMMAND ${PN_ENV_SCRIPT} "PATH=${test_path}" ${VALGRIND_ENV} --
-          ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v
+  COMMAND ${PN_ENV_SCRIPT}
+  "PATH=${test_path}"
+  "PYTHONPATH=${CMAKE_SOURCE_DIR}/tests/py"
+  ${TEST_ENV} --
+  ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v
   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/examples/example_test.py
----------------------------------------------------------------------
diff --git a/c/examples/example_test.py b/c/examples/example_test.py
index 1834989..35a8993 100644
--- a/c/examples/example_test.py
+++ b/c/examples/example_test.py
@@ -20,31 +20,14 @@
 # Run the C examples and verify that they behave as expected.
 # Example executables must be in PATH
 
-import unittest, sys, time, re
+import unittest
 
-import subprocess
+from test_subprocess import Popen, TestProcessError, check_output
 
-class Server(subprocess.Popen):
+class Server(Popen):
     def __init__(self, *args, **kwargs):
-        self.kill_me = kwargs.pop('kill_me', False)
-        kwargs.update({'universal_newlines': True,
-                       'stdout': subprocess.PIPE})
         super(Server, self).__init__(*args, **kwargs)
-
-    def __enter__(self):
-        line = self.stdout.readline()
-        self.port = re.search("listening on ([0-9]+)$", line).group(1)
-        return self
-
-    def __exit__(self, *args):
-        if self.kill_me:
-            self.kill()
-            self.stdout.close() # Doesn't get closed if killed
-        self.wait()
-
-def check_output(*args, **kwargs):
-    kwargs.update({'universal_newlines': True})
-    return subprocess.check_output(*args, **kwargs)
+        self.port = self.expect("listening on ([0-9]+)$").group(1)
 
 MESSAGES=10
 
@@ -64,6 +47,10 @@ class ExampleTest(unittest.TestCase):
         """Run an example with standard arguments, return output"""
         return check_output([name, "", port, "xtest", str(messages)])
 
+    def startex(self, name, port, messages=MESSAGES):
+        """Start an example sub-process with standard arguments"""
+        return Popen([name, "", port, "xtest", str(messages)])
+
     def test_send_receive(self):
         """Send first then receive"""
         with Broker() as b:
@@ -73,20 +60,21 @@ class ExampleTest(unittest.TestCase):
     def test_receive_send(self):
         """Start receiving  first, then send."""
         with Broker() as b:
+            r = self.startex("receive", b.port)
             self.assertEqual(send_expect(), self.runex("send", b.port))
-            self.assertMultiLineEqual(receive_expect(), self.runex("receive", b.port))
+            self.assertMultiLineEqual(receive_expect(), r.communicate()[0])
 
     def test_send_direct(self):
         """Send to direct server"""
-        with Server(["direct", "", "0"]) as d:
-            self.assertEqual(send_expect(), self.runex("send", d.port))
-            self.assertMultiLineEqual(receive_expect(), d.communicate()[0])
+        d = Server(["direct", "", "0"])
+        self.assertEqual(send_expect(), self.runex("send", d.port))
+        self.assertMultiLineEqual(receive_expect(), d.communicate()[0])
 
     def test_receive_direct(self):
         """Receive from direct server"""
-        with Server(["direct", "", "0"]) as d:
-            self.assertMultiLineEqual(receive_expect(), self.runex("receive", d.port))
-            self.assertEqual("10 messages sent and acknowledged\n", d.communicate()[0])
+        d =  Server(["direct", "", "0"])
+        self.assertMultiLineEqual(receive_expect(), self.runex("receive", d.port))
+        self.assertEqual("10 messages sent and acknowledged\n", d.communicate()[0])
 
     def test_send_abort_broker(self):
         """Sending aborted messages to a broker"""
@@ -101,13 +89,13 @@ class ExampleTest(unittest.TestCase):
 
     def test_send_abort_direct(self):
         """Send aborted messages to the direct server"""
-        with Server(["direct", "", "0", "examples", "20"]) as d:
-            self.assertEqual(send_expect(), self.runex("send", d.port))
-            self.assertEqual(send_abort_expect(), self.runex("send-abort", d.port))
-            self.assertEqual(send_expect(), self.runex("send", d.port))
-            expect = receive_expect_messages() + "Message aborted\n"*MESSAGES + receive_expect_messages()+receive_expect_total(20)
-            self.maxDiff = None
-            self.assertMultiLineEqual(expect, d.communicate()[0])
+        d = Server(["direct", "", "0", "examples", "20"])
+        self.assertEqual(send_expect(), self.runex("send", d.port))
+        self.assertEqual(send_abort_expect(), self.runex("send-abort", d.port))
+        self.assertEqual(send_expect(), self.runex("send", d.port))
+        expect = receive_expect_messages() + "Message aborted\n"*MESSAGES + receive_expect_messages()+receive_expect_total(20)
+        self.maxDiff = None
+        self.assertMultiLineEqual(expect, d.communicate()[0])
 
     def test_send_ssl_receive(self):
         """Send with SSL, then receive"""

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/c/tests/CMakeLists.txt b/c/tests/CMakeLists.txt
index 87dfdb6..a4bfb67 100644
--- a/c/tests/CMakeLists.txt
+++ b/c/tests/CMakeLists.txt
@@ -32,7 +32,7 @@ macro (pn_add_c_test_nolib test)
   if (BUILD_WITH_CXX)
     set_source_files_properties (${ARGN} PROPERTIES LANGUAGE CXX)
   endif (BUILD_WITH_CXX)
-  add_test(NAME ${test} COMMAND ${test_env} ${memcheck-cmd} $<TARGET_FILE:${test}>)
+  add_test(NAME ${test} COMMAND ${test_env} ${TEST_EXE_PREFIX_CMD} $<TARGET_FILE:${test}>)
 endmacro (pn_add_c_test_nolib)
 
 # Add test with qpid-proton-core linked
@@ -58,7 +58,7 @@ if(HAS_PROACTOR)
   pn_add_c_test (c-proactor-tests proactor.c)
   target_link_libraries (c-proactor-tests qpid-proton-proactor)
 
-  # TODO Enable by default when races are cleared up
+  # TODO Enable by default when races and xcode problems are cleared up
   option(THREADERCISER "Run the threaderciser concurrency tests" OFF)
   if (THREADERCISER)
     pn_add_c_test(c-threaderciser threaderciser.c)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/tests/fuzz/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/c/tests/fuzz/CMakeLists.txt b/c/tests/fuzz/CMakeLists.txt
index 19825b6..5d4187d 100644
--- a/c/tests/fuzz/CMakeLists.txt
+++ b/c/tests/fuzz/CMakeLists.txt
@@ -37,7 +37,7 @@ macro (pn_add_fuzz_test test)
   if (FUZZ_REGRESSION_TESTS)
     # StandaloneFuzzTargetMain cannot walk directory trees
     file(GLOB_RECURSE files ${CMAKE_CURRENT_SOURCE_DIR}/${test}/*)
-    add_test (NAME ${test} COMMAND ${memcheck-cmd} $<TARGET_FILE:${test}> ${files})
+    add_test (NAME ${test} COMMAND ${TEST_EXE_PREFIX_CMD} $<TARGET_FILE:${test}> ${files})
   else ()
     add_test (NAME ${test} COMMAND $<TARGET_FILE:${test}> -runs=1 ${CMAKE_CURRENT_SOURCE_DIR}/${test}>)
   endif ()

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/tests/threaderciser.supp
----------------------------------------------------------------------
diff --git a/c/tests/threaderciser.supp b/c/tests/threaderciser.supp
deleted file mode 100644
index d14ff61..0000000
--- a/c/tests/threaderciser.supp
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-   IGNORE: benign race in pni_log_enabled
-   Helgrind:Race
-   fun:pni_log_enabled
-}
-
-{
-   IGNORE: NSS library poking around in its own data segment upsets helgrind
-   Helgrind:Race
-   fun:strpbrk
-   fun:_nss_files_parse_servent
-}
-{
-   IGNORE: NSS library poking around in its own text segment upsets helgrind
-   Helgrind:Race
-   fun:*
-   fun:_nss_files_getservbyname_r
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/c/tests/threaderciser.tsupp
----------------------------------------------------------------------
diff --git a/c/tests/threaderciser.tsupp b/c/tests/threaderciser.tsupp
deleted file mode 100644
index 18ceb23..0000000
--- a/c/tests/threaderciser.tsupp
+++ /dev/null
@@ -1,5 +0,0 @@
-# TSAN suppressions for threaderciser
-
-# Benign race in pni_log_enabled
-race:pni_log_enabled
-

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/cpp/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 0dcede2..a06e67d 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -242,7 +242,7 @@ macro(add_cpp_test test)
       "PATH=$<TARGET_FILE_DIR:qpid-proton>"
       $<TARGET_FILE:${test}> ${ARGN})
   else ()
-    add_test (NAME cpp-${test} COMMAND ${memcheck-cmd} $<TARGET_FILE:${test}> ${ARGN})
+    add_test (NAME cpp-${test} COMMAND ${TEST_EXE_PREFIX_CMD} $<TARGET_FILE:${test}> ${ARGN})
   endif ()
 endmacro(add_cpp_test)
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/cpp/examples/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt
index 18d922e..ba18d83 100644
--- a/cpp/examples/CMakeLists.txt
+++ b/cpp/examples/CMakeLists.txt
@@ -104,7 +104,7 @@ if(HAS_ENOUGH_CPP11)
 endif()
 
 # Add a test with the correct environment to find test executables and valgrind.
-macro(add_cpp_test name)
+macro(add_cpp_example_test name)
   if(WIN32)
     set(test_path "$<TARGET_FILE_DIR:broker>;$<TARGET_FILE_DIR:qpid-proton>;$<TARGET_FILE_DIR:qpid-proton-cpp>")
   else(WIN32)
@@ -112,13 +112,17 @@ macro(add_cpp_test name)
   endif(WIN32)
   add_test(
     NAME ${name}
-    COMMAND ${PN_ENV_SCRIPT} "PATH=${test_path}" ${VALGRIND_ENV}
-            "HAS_CPP11=$<$<BOOL:${HAS_ENOUGH_CPP11}>:1>" -- ${ARGN}
+    COMMAND ${PN_ENV_SCRIPT}
+    "PATH=${test_path}"
+    "PYTHONPATH=${CMAKE_SOURCE_DIR}/tests/py"
+    "HAS_CPP11=$<$<BOOL:${HAS_ENOUGH_CPP11}>:1>"
+    ${TEST_ENV} --
+    ${ARGN}
     )
 endmacro()
 
-add_cpp_test(cpp-example-container ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v ContainerExampleTest)
+add_cpp_example_test(cpp-example-container ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v ContainerExampleTest)
 
 if (NOT SSL_IMPL STREQUAL none)
-add_cpp_test(cpp-example-container-ssl ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v ContainerExampleSSLTest)
+add_cpp_example_test(cpp-example-container-ssl ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example_test.py -v ContainerExampleSSLTest)
 endif()

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/cpp/examples/broker.cpp
----------------------------------------------------------------------
diff --git a/cpp/examples/broker.cpp b/cpp/examples/broker.cpp
index d45309e..b15ac39 100644
--- a/cpp/examples/broker.cpp
+++ b/cpp/examples/broker.cpp
@@ -355,7 +355,7 @@ public:
     }
 
     void on_error(const proton::error_condition& e) OVERRIDE {
-        std::cerr << "error: " << e.what() << std::endl;
+        std::cout << "protocol error: " << e.what() << std::endl;
     }
 
     // The container calls on_transport_close() last.
@@ -429,7 +429,7 @@ int main(int argc, char **argv) {
     } catch (const example::bad_option& e) {
         std::cout << opts << std::endl << e.what() << std::endl;
     } catch (const std::exception& e) {
-        std::cerr << "broker shutdown: " << e.what() << std::endl;
+        std::cout << "broker shutdown: " << e.what() << std::endl;
     }
     return 1;
 }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/cpp/examples/example_test.py
----------------------------------------------------------------------
diff --git a/cpp/examples/example_test.py b/cpp/examples/example_test.py
index edcde27..38a9a6e 100644
--- a/cpp/examples/example_test.py
+++ b/cpp/examples/example_test.py
@@ -20,40 +20,21 @@
 # Run the C++ examples and verify that they behave as expected.
 # Example executables must be in PATH
 
-import unittest, sys, time, re, shutil, os
+import unittest, sys, shutil, os
+from test_subprocess import Popen, TestProcessError, check_output
 from os.path import dirname
 from string import Template
 
-import subprocess
-
-class Server(subprocess.Popen):
+class Server(Popen):
+    """A process that prints 'listening on <port>' to stdout"""
     def __init__(self, *args, **kwargs):
-        self.port = None
-        self.kill_me = kwargs.pop('kill_me', False)
-        kwargs.update({'universal_newlines': True,
-                       'stdout': subprocess.PIPE})
         super(Server, self).__init__(*args, **kwargs)
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, *args):
-        if self.kill_me:
-            self.kill()
-            self.stdout.close() # Doesn't get closed if killed
-        self.wait()
+        self.port = self.expect("listening on ([0-9]+)$").group(1)
 
     @property
     def addr(self):
-        if not self.port:
-            line = self.stdout.readline()
-            self.port = re.search("listening on ([0-9]+)$", line).group(1)
         return ":%s/example" % self.port
 
-def check_output(*args, **kwargs):
-    kwargs.update({'universal_newlines': True})
-    return subprocess.check_output(*args, **kwargs)
-
 def _cyrusSetup(conf_dir):
   """Write out simple SASL config.tests
   """
@@ -77,12 +58,9 @@ mech_list: EXTERNAL DIGEST-MD5 SCRAM-SHA-1 CRAM-MD5 PLAIN ANONYMOUS
 # Globally initialize Cyrus SASL configuration
 _cyrusSetup('sasl-conf')
 
-def wait_listening(p):
-    return re.search(b"listening on ([0-9]+)$", p.stdout.readline()).group(1)
-
 class Broker(Server):
-  def __init__(self):
-    super(Broker, self).__init__(["broker", "-a", "//:0"], kill_me=True)
+    def __init__(self):
+        super(Broker, self).__init__(["broker", "-a", "//:0"], kill_me=True)
 
 CLIENT_EXPECT="""Twas brillig, and the slithy toves => TWAS BRILLIG, AND THE SLITHY TOVES
 Did gire and gymble in the wabe. => DID GIRE AND GYMBLE IN THE WABE.
@@ -104,7 +82,7 @@ class ContainerExampleTest(unittest.TestCase):
         self.assertMultiLineEqual(recv_expect(), check_output(["simple_recv", "-a", Broker.addr]))
 
     def test_simple_recv_send(self):
-        recv = Server(["simple_recv", "-a", Broker.addr])
+        recv = Popen(["simple_recv", "-a", Broker.addr])
         self.assertMultiLineEqual("all messages confirmed\n", check_output(["simple_send", "-a", Broker.addr]))
         self.assertMultiLineEqual(recv_expect(), recv.communicate()[0])
 
@@ -119,8 +97,8 @@ class ContainerExampleTest(unittest.TestCase):
         self.assertMultiLineEqual("all messages confirmed\n", send.communicate()[0])
 
     def test_request_response(self):
-        with Server(["server", Broker.addr, "example"], kill_me=True) as server:
-            self.assertIn("connected to", server.stdout.readline())
+        with Popen(["server", Broker.addr, "example"], kill_me=True) as server:
+            server.expect("connected to %s" % Broker.addr)
             self.assertMultiLineEqual(CLIENT_EXPECT, check_output(["client", "-a", Broker.addr]))
 
     def test_request_response_direct(self):

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/misc/config.sh.in
----------------------------------------------------------------------
diff --git a/misc/config.sh.in b/misc/config.sh.in
index a61c757..dd23602 100755
--- a/misc/config.sh.in
+++ b/misc/config.sh.in
@@ -52,12 +52,6 @@ export LD_LIBRARY_PATH="$(merge_paths $PROTON_BUILD $LD_LIBRARY_PATH)"
 # Test applications
 export PATH="$(merge_paths $PATH $PROTON_BUILD/c/tools $PROTON_HOME/python/tests)"
 
-# Can the test harness use valgrind?
-if [[ -x "$(type -p valgrind)" && "@ENABLE_VALGRIND@" == "ON" ]] ; then
-    export VALGRIND=$(type -p valgrind)
-    export VALGRIND_ARGS="@VALGRIND_OPTIONS@"
-fi
-
 # Can the test harness use saslpasswd2?
 if [[ -x "$(type -p saslpasswd2)" ]] ; then
     export SASLPASSWD=$(type -p saslpasswd2)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/python/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index d63ab96..181128c 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -208,8 +208,8 @@ add_test (NAME python-test
   COMMAND ${PN_ENV_SCRIPT}
   "PATH=${py_path}" "PYTHONPATH=${py_pythonpath}"
   "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}"
-  ${VALGRIND_ENV}
-  ${PYTHON_EXECUTABLE} -- ${python_coverage_options} "${py_tests}/proton-test")
+  ${TEST_ENV}
+  ${TEST_WRAP_PREFIX_CMD} ${PYTHON_EXECUTABLE} -- ${python_coverage_options} "${py_tests}/proton-test")
 set_tests_properties(python-test PROPERTIES PASS_REGULAR_EXPRESSION "Totals: .* 0 failed")
 
 check_python_module("tox" TOX_MODULE_FOUND)
@@ -237,8 +237,8 @@ else ()
         "PATH=${py_path}"
         "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}"
         "SWIG=${SWIG_EXECUTABLE}"
-        ${VALGRIND_ENV} --
-        ${PYTHON_EXECUTABLE} -m tox)
+        ${TEST_ENV} --
+        ${TEST_WRAP_PREFIX_CMD} ${PYTHON_EXECUTABLE} -m tox)
       set_tests_properties(python-tox-test
         PROPERTIES
         PASS_REGULAR_EXPRESSION "Totals: .* ignored, 0 failed"

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/python/tests/proton_tests/common.py
----------------------------------------------------------------------
diff --git a/python/tests/proton_tests/common.py b/python/tests/proton_tests/common.py
index fd4decc..a56a2c0 100644
--- a/python/tests/proton_tests/common.py
+++ b/python/tests/proton_tests/common.py
@@ -281,7 +281,7 @@ class MessengerApp(object):
                 del cmd[0:1]
                 cmd.insert(0, foundfile)
                 cmd.insert(0, sys.executable)
-            self._process = Popen(cmd, stdout=PIPE, stderr=STDOUT,
+            self._process = Popen(cmd, stdout=PIPE,
                                   bufsize=4096, universal_newlines=True)
         except OSError:
             e = sys.exc_info()[1]
@@ -425,7 +425,8 @@ class MessengerReceiver(MessengerApp):
 
     # command string?
     def _build_command(self):
-        self._cmdline = self._command
+        self._cmdline = os.environ.get("TEST_EXE_PREFIX", "").split()
+        self._cmdline += self._command
         self._do_common_options()
         self._cmdline += ["-X", "READY"]
         assert self.subscriptions, "Missing subscriptions, required for receiver!"
@@ -468,47 +469,17 @@ class MessengerSenderC(MessengerSender):
         MessengerSender.__init__(self)
         self._command = ["msgr-send"]
 
-def setup_valgrind(self):
-    if "VALGRIND" not in os.environ:
-        raise Skipped("Skipping test - $VALGRIND not set.")
-    super(type(self), self).__init__()
-    self._command = [os.environ["VALGRIND"]] + os.environ["VALGRIND_ARGS"].split(' ') + self._command
-
-class MessengerSenderValgrind(MessengerSenderC):
-    """ Run the C sender under Valgrind
-    """
-    def __init__(self, suppressions=None):
-        setup_valgrind(self)
-
 class MessengerReceiverC(MessengerReceiver):
     def __init__(self):
         MessengerReceiver.__init__(self)
         self._command = ["msgr-recv"]
 
-class MessengerReceiverValgrind(MessengerReceiverC):
-    """ Run the C receiver under Valgrind
-    """
-    def __init__(self, suppressions=None):
-        setup_valgrind(self)
-
 class ReactorSenderC(MessengerSender):
     def __init__(self):
         MessengerSender.__init__(self)
         self._command = ["reactor-send"]
 
-class ReactorSenderValgrind(ReactorSenderC):
-    """ Run the C sender under Valgrind
-    """
-    def __init__(self, suppressions=None):
-        setup_valgrind(self)
-
 class ReactorReceiverC(MessengerReceiver):
     def __init__(self):
         MessengerReceiver.__init__(self)
         self._command = ["reactor-recv"]
-
-class ReactorReceiverValgrind(ReactorReceiverC):
-    """ Run the C receiver under Valgrind
-    """
-    def __init__(self, suppressions=None):
-        setup_valgrind(self)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/python/tests/proton_tests/soak.py
----------------------------------------------------------------------
diff --git a/python/tests/proton_tests/soak.py b/python/tests/proton_tests/soak.py
index 5e103fd..e015db5 100644
--- a/python/tests/proton_tests/soak.py
+++ b/python/tests/proton_tests/soak.py
@@ -24,9 +24,7 @@ import os
 
 from .common import Test, Skipped, free_tcp_ports, \
     MessengerReceiverC, MessengerSenderC, \
-    MessengerReceiverValgrind, MessengerSenderValgrind, \
     ReactorReceiverC, ReactorSenderC, \
-    ReactorReceiverValgrind, ReactorSenderValgrind, \
     isSSLPresent
 
 #
@@ -192,52 +190,14 @@ class MessengerTests(AppTests):
 
         self._do_test(iterations)
 
-    def _do_relay_test(self, receiver, relay, sender, domain="amqp"):
-        """ Send N messages to a receiver, which replies to each and forwards
-        each of them to different receiver.
-        Parameters:
-        iterations - repeat the senders this many times
-        target_count - # targets to send to
-        send_count = # messages sent to each target
-        send_batch - wait for replies after this many messages sent
-        forward_count - forward to this many targets
-        """
-        iterations = self.iterations
-        send_count = self.send_count
-        target_count = self.target_count
-        send_batch = self.send_batch
-        forward_count = self.forward_count
-
-        send_total = send_count * target_count
-        receive_total = send_total * iterations
-
-        port = free_tcp_ports()[0]
-
-        receiver.subscriptions = ["%s://~0.0.0.0:%s" % (domain, port)]
-        receiver.receive_count = receive_total
-        receiver.send_reply = True
-        # forward to 'relay' - uses two links
-        # ## THIS FAILS:
-        # receiver.forwards = ["amqp://Relay/%d" % j for j in range(forward_count)]
-        receiver.forwards = ["%s://Relay" % domain]
-        receiver.timeout = MessengerTests._timeout
-        self.receivers.append( receiver )
-
-        relay.subscriptions = ["%s://0.0.0.0:%s" % (domain, port)]
-        relay.name = "Relay"
-        relay.receive_count = receive_total
-        relay.timeout = MessengerTests._timeout
-        self.receivers.append( relay )
-
-        # send to 'receiver'
-        sender.targets = ["%s://0.0.0.0:%s/X%dY" % (domain, port, j) for j in range(target_count)]
-        sender.send_count = send_total
-        sender.get_reply = True
-        sender.timeout = MessengerTests._timeout
-        self.senders.append( sender )
-
-        self._do_test(iterations)
-
+    # Removed messenger "relay" tests. The test start-up is faulty:
+    # msgr-recv prints it's -X ready message when it starts to open a
+    # connection but it does not wait for the remote open. The relay
+    # tests depends on mapping a container name from an incoming
+    # connection. They can fail under if the sender starts before the
+    # connection is complete (esp. valgrind with SSL connections) We
+    # could fix the tests but since messenger is deprecated it does
+    # not seem worthwhile.
 
     def _do_star_topology_test(self, r_factory, s_factory, domain="amqp"):
         """
@@ -291,10 +251,6 @@ class MessengerTests(AppTests):
         self._ssl_check()
         self._do_oneway_test(MessengerReceiverC(), MessengerSenderC(), "amqps")
 
-    def test_oneway_valgrind(self):
-        self.valgrind_test()
-        self._do_oneway_test(MessengerReceiverValgrind(), MessengerSenderValgrind())
-
     def test_echo_C(self):
         self._do_echo_test(MessengerReceiverC(), MessengerSenderC())
 
@@ -302,21 +258,6 @@ class MessengerTests(AppTests):
         self._ssl_check()
         self._do_echo_test(MessengerReceiverC(), MessengerSenderC(), "amqps")
 
-    def test_echo_valgrind(self):
-        self.valgrind_test()
-        self._do_echo_test(MessengerReceiverValgrind(), MessengerSenderValgrind())
-
-    def test_relay_C(self):
-        self._do_relay_test(MessengerReceiverC(), MessengerReceiverC(), MessengerSenderC())
-
-    def test_relay_C_SSL(self):
-        self._ssl_check()
-        self._do_relay_test(MessengerReceiverC(), MessengerReceiverC(), MessengerSenderC(), "amqps")
-
-    def test_relay_valgrind(self):
-        self.valgrind_test()
-        self._do_relay_test(MessengerReceiverValgrind(), MessengerReceiverValgrind(), MessengerSenderValgrind())
-
     def test_star_topology_C(self):
         self._do_star_topology_test( MessengerReceiverC, MessengerSenderC )
 
@@ -324,13 +265,5 @@ class MessengerTests(AppTests):
         self._ssl_check()
         self._do_star_topology_test( MessengerReceiverC, MessengerSenderC, "amqps" )
 
-    def test_star_topology_valgrind(self):
-        self.valgrind_test()
-        self._do_star_topology_test( MessengerReceiverValgrind, MessengerSenderValgrind )
-
     def test_oneway_reactor(self):
         self._do_oneway_test(ReactorReceiverC(), ReactorSenderC())
-
-    def test_oneway_reactor_valgrind(self):
-        self.valgrind_test()
-        self._do_oneway_test(ReactorReceiverValgrind(), ReactorSenderValgrind())

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/python/tests/proton_tests/ssl.py
----------------------------------------------------------------------
diff --git a/python/tests/proton_tests/ssl.py b/python/tests/proton_tests/ssl.py
index fcc3795..9419292 100644
--- a/python/tests/proton_tests/ssl.py
+++ b/python/tests/proton_tests/ssl.py
@@ -882,39 +882,6 @@ class SslTest(common.Test):
         receiver.wait()
         assert receiver.status() == 0, "Command '%s' failed" % str(receiver.cmdline())
 
-    def DISABLED_test_defaults_valgrind(self):
-        """ Run valgrind over a simple SSL connection (no certificates)
-        """
-        # the openssl libraries produce far too many valgrind errors to be
-        # useful.  AFAIK, there is no way to wriate a valgrind suppression
-        # expression that will ignore all errors from a given library.
-        # Until we can, skip this test.
-        port = common.free_tcp_ports()[0]
-
-        receiver = common.MessengerReceiverValgrind()
-        receiver.subscriptions = ["amqps://~127.0.0.1:%s" % port]
-        receiver.receive_count = 1
-        receiver.timeout = self.timeout
-        receiver.start()
-
-        sender = common.MessengerSenderValgrind()
-        sender.targets = ["amqps://127.0.0.1:%s/X" % port]
-        sender.send_count = 1
-        sender.timeout = self.timeout
-        sender.start()
-        sender.wait()
-        assert sender.status() == 0, "Command '%s' failed" % str(sender.cmdline())
-
-        receiver.wait()
-        assert receiver.status() == 0, "Command '%s' failed" % str(receiver.cmdline())
-
-        # self.server_domain.set_credentials(self._testpath("client-certificate.pem"),
-        #                                    self._testpath("client-private-key.pem"),
-        #                                    "client-password")
-
-        # self.client_domain.set_trusted_ca_db(self._testpath("ca-certificate.pem"))
-        # self.client_domain.set_peer_authentication( SSLDomain.VERIFY_PEER )
-
     def test_singleton(self):
         """Verify that only a single instance of SSL can exist per Transport"""
         transport = Transport()

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/python/tox.ini.in
----------------------------------------------------------------------
diff --git a/python/tox.ini.in b/python/tox.ini.in
index c514078..ec4a77d 100644
--- a/python/tox.ini.in
+++ b/python/tox.ini.in
@@ -13,7 +13,7 @@ passenv =
     PKG_CONFIG_PATH
     CFLAGS
     SASLPASSWD
-    VALGRIND
+    TEST_EXE_PREFIX
 commands =
     @CMAKE_SOURCE_DIR@/python/tests/proton-test '{posargs:--ignore-file=@CMAKE_SOURCE_DIR@/python/tests/tox-blacklist}'
 deps =

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/ruby/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/ruby/CMakeLists.txt b/ruby/CMakeLists.txt
index e205250..eb99907 100644
--- a/ruby/CMakeLists.txt
+++ b/ruby/CMakeLists.txt
@@ -125,14 +125,18 @@ to_native_path("${bin};${c_lib_dir};$ENV{PATH}" PATH)
 execute_process(COMMAND ${RUBY_EXECUTABLE} -r minitest -e ""
   RESULT_VARIABLE result OUTPUT_QUIET ERROR_QUIET)
 if (result EQUAL 0)  # Have minitest
-  set(test_env ${PN_ENV_SCRIPT} -- "PATH=${PATH}" "RUBYLIB=${RUBYLIB}" "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}")
+  set(test_env ${PN_ENV_SCRIPT} --
+    "PATH=${PATH}"
+    "RUBYLIB=${RUBYLIB}"
+    "SASLPASSWD=${CyrusSASL_Saslpasswd_EXECUTABLE}"
+    ${TEST_ENV})
 
   macro(add_ruby_test script)
     get_filename_component(name ${script} NAME_WE)
     string(REPLACE "_" "-" name "ruby-${name}")
     add_test(
       NAME ${name}
-      COMMAND ${test_env} ${RUBY_EXECUTABLE} ${script} -v
+      COMMAND ${test_env} ${TEST_WRAP_PREFIX_CMD} ${RUBY_EXECUTABLE} ${script} -v
       ${ARGN})
 
   endmacro()

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/runtime_check.cmake
----------------------------------------------------------------------
diff --git a/runtime_check.cmake b/runtime_check.cmake
new file mode 100644
index 0000000..e1d76c3
--- /dev/null
+++ b/runtime_check.cmake
@@ -0,0 +1,123 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Configuration for code analysis tools: runtime checking and coverage.
+
+# Any test that needs runtime checks should use TEST_EXE_PREFIX and TEST_ENV.
+# Normally they are set as a result of the RUNTIME_CHECK value,
+# but can be set directly for unsupported tools or unusual flags
+# e.g. -DTEST_EXE_PREFIX=rr or -DTEST_EXE_PREFIX="valgrind --tool=massif"
+
+set(TEST_EXE_PREFIX "" CACHE STRING "Prefix for test executable command line")
+set(TEST_WRAP_PREFIX "" CACHE STRING "Prefix for interpreter tests (e.g. python, ruby) that load proton as an extension")
+set(TEST_ENV "" CACHE STRING "Extra environment for tests: name1=value1;name2=value2")
+mark_as_advanced(TEST_EXE_PREFIX TEST_WRAP_PREFIX TEST_ENV)
+
+# Check for valgrind
+find_program(VALGRIND_EXECUTABLE valgrind DOC "location of valgrind program")
+set(VALGRIND_SUPPRESSIONS "${CMAKE_SOURCE_DIR}/tests/valgrind.supp" CACHE STRING "Suppressions file for valgrind")
+set(VALGRIND_COMMON_ARGS "--error-exitcode=42 --quiet --suppressions=${VALGRIND_SUPPRESSIONS}")
+mark_as_advanced(VALGRIND_EXECUTABLE VALGRIND_SUPPRESSIONS VALGRIND_COMMON_ARGS)
+
+# Check for compiler sanitizers
+if((CMAKE_C_COMPILER_ID MATCHES "GNU"
+      AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.8)
+    OR (CMAKE_C_COMPILER_ID MATCHES "Clang"
+      AND CMAKE_C_COMPILER_VERSION VERSION_GREATER 4.1))
+  set(HAS_SANITIZERS TRUE)
+endif()
+
+# Valid values for RUNTIME_CHECK
+set(runtime_checks OFF asan tsan memcheck helgrind)
+
+# Set the default
+if(NOT CMAKE_BUILD_TYPE MATCHES "Coverage" AND VALGRIND_EXECUTABLE)
+  set(RUNTIME_CHECK_DEFAULT "memcheck")
+endif()
+
+# Deprecated options to enable runtime checks
+macro(deprecated_enable_check old new doc)
+  option(${old} ${doc} OFF)
+  if (${old})
+    message("WARNING: option ${old} is deprecated, use RUNTIME_CHECK=${new} instead")
+    set(RUNTIME_CHECK_DEFAULT ${new})
+  endif()
+endmacro()
+deprecated_enable_check(ENABLE_VALGRIND memcheck "Use valgrind to detect run-time problems")
+deprecated_enable_check(ENABLE_SANITIZERS asan "Compile with memory sanitizers (asan, ubsan)")
+deprecated_enable_check(ENABLE_TSAN tsan "Compile with thread sanitizer (tsan)")
+
+set(RUNTIME_CHECK ${RUNTIME_CHECK_DEFAULT} CACHE string "Enable runtime checks. Valid values: ${runtime_checks}")
+
+if(CMAKE_BUILD_TYPE MATCHES "Coverage" AND RUNTIME_CHECK)
+  message(FATAL_ERROR "Cannot set RUNTIME_CHECK with CMAKE_BUILD_TYPE=Coverage")
+endif()
+
+macro(assert_has_sanitizers)
+  if(NOT HAS_SANITIZERS)
+    message(FATAL_ERROR "compiler sanitizers are not available")
+  endif()
+endmacro()
+
+macro(assert_has_valgrind)
+  if(NOT VALGRIND_EXECUTABLE)
+    message(FATAL_ERROR "valgrind is not available")
+  endif()
+endmacro()
+
+if(RUNTIME_CHECK STREQUAL "memcheck")
+  assert_has_valgrind()
+  message(STATUS "Runtime memory checker: valgrind memcheck")
+  set(TEST_EXE_PREFIX "${VALGRIND_EXECUTABLE} --tool=memcheck --leak-check=full ${VALGRIND_COMMON_ARGS}")
+  # FIXME aconway 2018-09-06: NO TEST_WRAP_PREFIX, need --trace-children + many suppressions
+
+elseif(RUNTIME_CHECK STREQUAL "helgrind")
+  assert_has_valgrind()
+  message(STATUS "Runtime race checker: valgrind helgrind")
+  set(TEST_EXE_PREFIX "${VALGRIND_EXECUTABLE} --tool=helgrind ${VALGRIND_COMMON_ARGS}")
+  # FIXME aconway 2018-09-06: NO TEST_WRAP_PREFIX, need --trace-children + many suppressions
+
+elseif(RUNTIME_CHECK STREQUAL "asan")
+  assert_has_sanitizers()
+  message(STATUS "Runtime memory checker: gcc/clang memory sanitizers")
+  set(SANITIZE_FLAGS "-g -fno-omit-frame-pointer -fsanitize=address,undefined")
+  set(TEST_WRAP_PREFIX "${CMAKE_SOURCE_DIR}/tests/preload_asan.sh $<TARGET_FILE:qpid-proton-core>")
+
+elseif(RUNTIME_CHECK STREQUAL "tsan")
+  assert_has_sanitizers()
+  message(STATUS "Runtime race checker: gcc/clang thread sanitizer")
+  set(SANITIZE_FLAGS "-g -fno-omit-frame-pointer -fsanitize=thread")
+
+elseif(RUNTIME_CHECK)
+  message(FATAL_ERROR "'RUNTIME_CHECK=${RUNTIME_CHECK}' is invalid, valid values: ${runtime_checks}")
+endif()
+
+if(SANITIZE_FLAGS)
+  set(ENABLE_UNDEFINED_ERROR OFF CACHE BOOL "Disabled for sanitizers" FORCE)
+  string(APPEND CMAKE_C_FLAGS " ${SANITIZE_FLAGS}")
+  string(APPEND CMAKE_CXX_FLAGS "${SANITIZE_FLAGS}")
+endif()
+
+if(TEST_EXE_PREFIX)
+  # Add TEST_EXE_PREFIX to TEST_ENV so test runner scripts can use it.
+  list(APPEND TEST_ENV "TEST_EXE_PREFIX=${TEST_EXE_PREFIX}")
+  # Make a CMake-list form of TEST_EXE_PREFIX for add_test() commands
+  separate_arguments(TEST_EXE_PREFIX_CMD UNIX_COMMAND "${TEST_EXE_PREFIX}")
+endif()
+separate_arguments(TEST_WRAP_PREFIX_CMD UNIX_COMMAND "${TEST_WRAP_PREFIX}")

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/scripts/env.py
----------------------------------------------------------------------
diff --git a/scripts/env.py b/scripts/env.py
index 04fa8bb..14df6d1 100644
--- a/scripts/env.py
+++ b/scripts/env.py
@@ -58,11 +58,6 @@ def main(argv=None):
     if len(args) == 0 or len(args[0]) == 0:
         raise Exception("Error: syntax error in command arguments")
 
-    if new_env.get("VALGRIND") and new_env.get("VALGRIND_ALL"):
-        # Python generates a lot of possibly-lost errors that are not errors, don't show them.
-        args = [new_env.get("VALGRIND"), "--show-reachable=no", "--show-possibly-lost=no",
-                "--error-exitcode=42"] + args
-
     p = subprocess.Popen(args, env=new_env)
     return p.wait()
 

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/tests/preload_asan.sh
----------------------------------------------------------------------
diff --git a/tests/preload_asan.sh b/tests/preload_asan.sh
new file mode 100755
index 0000000..e4928f9
--- /dev/null
+++ b/tests/preload_asan.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+if test $# -lt 2; then
+    echo <<EOF
+usage: $0 LIB EXE [args ...]
+Get the libasan linked to LIB and preload it to run `EXE args ...`
+EOF
+fi
+
+LIB=$1; shift
+EXE=$2
+
+case $EXE in
+    *ruby*|*.rb|*python*|*.py)
+        # ruby has spurious leaks and causes asan errors.
+        #
+        # python tests have many leaks that may be real, but need to be
+        # analysed & fixed or suppressed before turning this on
+
+        # Disable link order check to run with limited sanitizing
+        # Still seeing problems.
+        export ASAN_OPTIONS=verify_asan_link_order=0
+        ;;
+    *)
+        # Preload the asan library linked to LIB. Note we need to
+        # check the actual linkage, there may be multiple asan lib
+        # versions installed and we must use the same one.
+        libasan=$(ldd $LIB | awk "/(libasan\\.so[.0-9]*)/ { print \$1 }")
+        export LD_PRELOAD="$libasan:$LD_PRELOAD"
+        ;;
+esac
+
+exec "$@"

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/tests/py/test_subprocess.py
----------------------------------------------------------------------
diff --git a/tests/py/test_subprocess.py b/tests/py/test_subprocess.py
new file mode 100644
index 0000000..1c7d2b9
--- /dev/null
+++ b/tests/py/test_subprocess.py
@@ -0,0 +1,105 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License
+#
+
+# Extends the subprocess module to use runtime checkers, and report stderr output.
+
+import subprocess, re, os, tempfile
+
+from subprocess import PIPE
+
+class TestProcessError(Exception):
+    def __init__(self, proc, what, output=None):
+        self.output = output
+        sep = "\n%s stderr(%s) %s\n" %("_" * 32, proc.pid, "_" * 32)
+        error = sep + proc.error + sep if proc.error else ""
+        super(TestProcessError, self).__init__("%s pid=%s exit=%s: %s%s" % (
+            proc.cmd, proc.pid, proc.returncode, what, error))
+
+class Popen(subprocess.Popen):
+    """
+    Adds TEST_EXE_PREFIX to the command and checks stderr for runtime checker output.
+    In a 'with' statement it runs check_wait() on exit from the block, or
+    check_kill() if initialized with kill_me=True
+    """
+
+    def __init__(self, *args, **kwargs):
+        """
+        Takes all args and kwargs of subprocess.Popen except stdout, stderr, universal_newlines
+        kill_me=True runs check_kill() in __exit__() instead of check_wait()
+        """
+        self.cmd = args[0]
+        self.on_exit = self.check_kill if kwargs.pop('kill_me', False) else self.check_wait
+        self.errfile = tempfile.NamedTemporaryFile(delete=False)
+        kwargs.update({'universal_newlines': True, 'stdout': PIPE, 'stderr': self.errfile})
+        args = ((os.environ.get("TEST_EXE_PREFIX", "").split() + args[0]),) + args[1:]
+        super(Popen, self).__init__(*args, **kwargs)
+
+    def check_wait(self):
+        if self.wait() or self.error:
+            raise TestProcessError(self, "check_wait")
+
+    def communicate(self, *args, **kwargs):
+        result = super(Popen, self).communicate(*args, **kwargs)
+        if self.returncode or self.error:
+            raise TestProcessError(self, "check_communicate", result[0])
+        return result
+
+    def check_kill(self):
+        """Raise if process has already exited, kill and raise if self.error is not empty"""
+        if self.poll() is None:
+            self.kill()
+            self.wait()
+            self.stdout.close()     # Doesn't get closed if killed
+            if self.error:
+                raise TestProcessError(self, "check_kill found error output")
+        else:
+            raise TestProcessError(self, "check_kill process not running")
+
+    def expect(self, pattern):
+        line = self.stdout.readline()
+        match = re.search(pattern, line)
+        if not match:
+            raise Exception("%s: can't find '%s' in '%s'" % (self.cmd, pattern, line))
+        return match
+
+    @property
+    def error(self):
+        """Return stderr as string, may only be used after process has terminated."""
+        assert(self.poll is not None)
+        if not hasattr(self, "_error"):
+            self.errfile.close() # Not auto-deleted
+            with open(self.errfile.name) as f: # Re-open to read
+                self._error = f.read().strip()
+            os.unlink(self.errfile.name)
+        return self._error
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args):
+        self.on_exit()
+
+def check_output(*args, **kwargs):
+    return Popen(*args, **kwargs).communicate()[0]
+
+class Server(Popen):
+    """A process that prints 'listening on <port>' to stdout"""
+    def __init__(self, *args, **kwargs):
+        super(Server, self).__init__(*args, **kwargs)
+        self.port = self.expect("listening on ([0-9]+)$").group(1)

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/27edd9ac/tests/valgrind.supp
----------------------------------------------------------------------
diff --git a/tests/valgrind.supp b/tests/valgrind.supp
new file mode 100644
index 0000000..3fee095
--- /dev/null
+++ b/tests/valgrind.supp
@@ -0,0 +1,151 @@
+{
+   SSL does a number of uninitialized accesses (expected) 1
+   Memcheck:Cond
+   fun:BN_bin2bn
+   obj:*
+   obj:*
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 2
+   Memcheck:Cond
+   fun:BN_num_bits_word
+   fun:BN_num_bits
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 3
+   Memcheck:Value8
+   fun:BN_num_bits_word
+   fun:BN_num_bits
+   fun:BN_mod_exp_mont_consttime
+   obj:*
+   fun:ssl3_ctx_ctrl
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 4
+   Memcheck:Value8
+   fun:BN_mod_exp_mont_consttime
+   obj:*
+   fun:ssl3_ctx_ctrl
+}
+
+{
+   SSL does a number of uninitialized accesses (FreeBSD version)
+   Memcheck:Value8
+   fun:BN_num_bits_word
+   fun:BN_num_bits
+   fun:BN_mod_exp_mont_consttime
+   fun:BN_mod_exp_mont
+   obj:*libcrypto.so*
+   fun:ssl3_ctx_ctrl
+}
+
+{
+   SSL does a number of uninitialized accesses (FreeBSD version)
+   Memcheck:Value8
+   fun:BN_mod_exp_mont_consttime
+   fun:BN_mod_exp_mont
+   obj:*libcrypto.so*
+   fun:ssl3_ctx_ctrl
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 5
+   Memcheck:Value4
+   fun:BN_mod_exp_mont_consttime
+   fun:BN_mod_exp_mont
+   obj:*
+   obj:*
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 6
+   Memcheck:Value4
+   fun:BN_num_bits_word
+   fun:BN_mod_exp_mont_consttime
+   fun:BN_mod_exp_mont
+   obj:*
+   obj:*
+}
+
+{
+   SSL does a number of uninitialized accesses (expected) 7
+   Memcheck:Cond
+   fun:ASN1_STRING_set
+   fun:ASN1_mbstring_ncopy
+   fun:ASN1_mbstring_copy
+}
+
+{
+   Since we can never safely uninitialize SSL, allow this
+   Memcheck:Leak
+   fun:_vgrZU_libcZdsoZa_realloc
+   fun:CRYPTO_realloc
+   fun:lh_insert
+   obj:/lib64/libcrypto.so.0.9.8e
+   fun:ERR_load_strings
+   fun:ERR_load_X509V3_strings
+   fun:ERR_load_crypto_strings
+   fun:SSL_load_error_strings
+}
+
+{
+   Since we can never safely uninitialize SSL, allow this
+   Memcheck:Leak
+   fun:_vgrZU_libcZdsoZa_malloc
+   fun:CRYPTO_malloc
+   fun:lh_new
+   fun:OBJ_NAME_init
+   fun:OBJ_NAME_add
+   fun:EVP_add_cipher
+   fun:SSL_library_init
+}
+
+{
+   Since we can never safely uninitialize SSL, allow this
+   Memcheck:Leak
+   fun:malloc
+   obj:*
+   fun:CRYPTO_malloc
+}
+
+{
+   Known memory leak in cyrus-sasl (fixed in 2.1.26)
+   Memcheck:Leak
+   fun:malloc
+   fun:*
+   fun:sasl_config_init
+   fun:sasl_server_init
+}
+
+{
+   Known bug in glibc which tries to free ipv6 related static when getaddrinfo used
+   Memcheck:Free
+   fun:free
+   fun:__libc_freeres
+   fun:_vgnU_freeres
+   fun:__run_exit_handlers
+   fun:exit
+}
+
+{
+   Benign race in pni_log_enabled
+   Helgrind:Race
+   fun:pni_log_enabled
+}
+
+{
+   NSS library poking around in its own data segment upsets helgrind
+   Helgrind:Race
+   fun:strpbrk
+   fun:_nss_files_parse_servent
+}
+
+{
+   NSS library poking around in its own text segment upsets helgrind
+   Helgrind:Race
+   fun:*
+   fun:_nss_files_getservbyname_r
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org