diff --git a/.travis.yml b/.travis.yml
index 481738cef2..499649f88c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,88 +1,89 @@
 group: deprecated-2017Q2
 sudo: required
 dist: trusty
 os: linux
 language: generic
 addons:
   apt:
     sources:
       - ubuntu-toolchain-r-test
     packages:
       - g++-7
 cache:
   directories:
   - depends/built
   - depends/sdk-sources
   - $HOME/.ccache
 env:
   global:
     - MAKEJOBS=-j3
     - RUN_TESTS=false
     - CHECK_DOC=0
     - BOOST_TEST_RANDOM=1$TRAVIS_BUILD_ID
     - CCACHE_SIZE=100M
     - CCACHE_TEMPDIR=/tmp/.ccache-temp
     - CCACHE_COMPRESS=1
     - BASE_OUTDIR=$TRAVIS_BUILD_DIR/out
     - SDK_URL=https://bitcoincore.org/depends-sources/sdks
     - PYTHON_DEBUG=1
     - WINEDEBUG=fixme-all
     - MATRIX_EVAL="CC=gcc && CXX=g++"
   matrix:
 # 32-bit + dash
     - HOST=i686-pc-linux-gnu PACKAGES="g++-7-multilib bc python3-zmq" DEP_OPTS="NO_QT=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports LDFLAGS=-static-libstdc++" USE_SHELL="/bin/dash"
 # bitcoind
     - HOST=x86_64-unknown-linux-gnu PACKAGES="bc python3-zmq" DEP_OPTS="NO_QT=1 NO_UPNP=1 DEBUG=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-zmq --enable-glibc-back-compat --enable-reduce-exports CPPFLAGS=-DDEBUG_LOCKORDER"
 # No wallet
     - HOST=x86_64-unknown-linux-gnu PACKAGES="python3 xvfb" DEP_OPTS="NO_WALLET=1" RUN_TESTS=true GOAL="install" BITCOIN_CONFIG="--enable-glibc-back-compat --enable-reduce-exports"
 # Cross-Mac
     - HOST=x86_64-apple-darwin11 PACKAGES="cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev" BITCOIN_CONFIG="--enable-gui --enable-reduce-exports" OSX_SDK=10.11 GOAL="deploy"
 
 before_install:
     - export PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/python/d' | tr "\n" ":" | sed "s|::|:|g")
     - export PATH=$(echo $PATH | tr ':' "\n" | sed '/\/opt\/pyenv/d' | tr "\n" ":" | sed "s|::|:|g")
     - eval "${MATRIX_EVAL}"
 install:
     - if [ -n "$PPA" ]; then travis_retry sudo add-apt-repository "$PPA" -y; fi
     - if [ -n "$DPKG_ADD_ARCH" ]; then sudo dpkg --add-architecture "$DPKG_ADD_ARCH" ; fi
     - if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get update; fi
     - if [ -n "$PACKAGES" ]; then travis_retry sudo apt-get install --no-install-recommends --no-upgrade -qq $PACKAGES; fi
     - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 50
     - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 50
 before_script:
     - unset CC; unset CXX
     - eval "${MATRIX_EVAL}"
     - export CC; export CXX
     - COMPILERS="CC=$CC CXX=$CXX"
     - if [ "$HOST" = "x86_64-apple-darwin11" ]; then unset CC; unset CXX; COMPILERS=""; fi
     - if [ "$HOST" = "i686-pc-linux-gnu" ]; then sudo ln -s /usr/include/asm-generic /usr/include/asm ; fi
     - if [ "$CHECK_DOC" = 1 ]; then contrib/devtools/check-doc.py; fi
     - mkdir -p depends/SDKs depends/sdk-sources
     - if [ -n "$OSX_SDK" -a ! -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then curl --location --fail $SDK_URL/MacOSX${OSX_SDK}.sdk.tar.gz -o depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi
     - if [ -n "$OSX_SDK" -a -f depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz ]; then tar -C depends/SDKs -xf depends/sdk-sources/MacOSX${OSX_SDK}.sdk.tar.gz; fi
     - make $MAKEJOBS -C depends HOST=$HOST $DEP_OPTS
     # Start xvfb if needed, as documented at https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI
     - if [ "$RUN_TESTS" = "true" -a "${DEP_OPTS#*NO_QT=1}" = "$DEP_OPTS" ]; then export DISPLAY=:99.0; /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac; fi
 script:
     - if [ "$RUN_TESTS" = "true" -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then while read LINE; do travis_retry gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys $LINE; done < contrib/verify-commits/trusted-keys; fi
     - if [ "$RUN_TESTS" = "true" -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then git fetch --unshallow; fi
     - if [ "$RUN_TESTS" = "true" -a "$TRAVIS_REPO_SLUG" = "bitcoin/bitcoin" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then contrib/verify-commits/verify-commits.sh; fi
     - export TRAVIS_COMMIT_LOG=`git log --format=fuller -1`
     - if [ -n "$USE_SHELL" ]; then export CONFIG_SHELL="$USE_SHELL"; fi
     - OUTDIR=$BASE_OUTDIR/$TRAVIS_PULL_REQUEST/$TRAVIS_JOB_NUMBER-$HOST
     - BITCOIN_CONFIG_ALL="$COMPILERS --disable-dependency-tracking --prefix=$TRAVIS_BUILD_DIR/depends/$HOST --bindir=$OUTDIR/bin --libdir=$OUTDIR/lib"
     - depends/$HOST/native/bin/ccache --max-size=$CCACHE_SIZE
     - set -e
     - test -n "$USE_SHELL" && eval '"$USE_SHELL" -c "./autogen.sh"' || ./autogen.sh
     - mkdir build && cd build
     - ../configure --cache-file=config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false)
     - make distdir VERSION=$HOST
     - cd bitcoin-abc-$HOST
     - ./configure --cache-file=../config.cache $BITCOIN_CONFIG_ALL $BITCOIN_CONFIG || ( cat config.log && false)
     - make $MAKEJOBS $GOAL || ( echo "Build failure. Verbose build follows." && make $GOAL V=1 ; false )
     - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib
     - if [ "$RUN_TESTS" = "true" ]; then make $MAKEJOBS check VERBOSE=1; fi
-    - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage; fi
+    - if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --quiet --exclude pruning"; fi
+    - if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage ${extended}; fi
 after_script:
     - echo $TRAVIS_COMMIT_RANGE
     - echo $TRAVIS_COMMIT_LOG
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index d56a4ac313..47d1b45d75 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -1,428 +1,427 @@
 #!/usr/bin/env python3
 # Copyright (c) 2014-2016 The Bitcoin Core developers
 # Distributed under the MIT software license, see the accompanying
 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
-# Base class for RPC testing
-
 from collections import deque
 from enum import Enum
 import logging
 import optparse
 import os
 import shutil
 import subprocess
 import sys
 import tempfile
 import time
 import traceback
 
 from .util import (
     PortSeed,
     MAX_NODES,
     bitcoind_processes,
     check_json_precision,
     connect_nodes_bi,
     disable_mocktime,
     disconnect_nodes,
     enable_coverage,
     enable_mocktime,
     get_mocktime,
     get_rpc_proxy,
     initialize_datadir,
     get_datadir_path,
     log_filename,
     p2p_port,
     rpc_url,
     set_node_times,
     start_node,
     start_nodes,
     stop_node,
     stop_nodes,
     sync_blocks,
     sync_mempools,
     wait_for_bitcoind_start,
 )
 from .authproxy import JSONRPCException
 
 
 class TestStatus(Enum):
     PASSED = 1
     FAILED = 2
     SKIPPED = 3
 
 
 TEST_EXIT_PASSED = 0
 TEST_EXIT_FAILED = 1
 TEST_EXIT_SKIPPED = 77
 
 
 class BitcoinTestFramework(object):
     """Base class for a bitcoin test script.
 
     Individual bitcoin test scripts should subclass this class and override the following methods:
 
     - __init__()
     - add_options()
     - setup_chain()
     - setup_network()
     - run_test()
 
     The main() method should not be overridden.
 
     This class also contains various public and private helper methods."""
 
     # Methods to override in subclass test scripts.
     def __init__(self):
         self.num_nodes = 4
         self.setup_clean_chain = False
         self.nodes = None
 
     def add_options(self, parser):
         pass
 
     def setup_chain(self):
         self.log.info("Initializing test directory " + self.options.tmpdir)
         if self.setup_clean_chain:
             self._initialize_chain_clean(self.options.tmpdir, self.num_nodes)
         else:
             self._initialize_chain(self.options.tmpdir,
                                    self.num_nodes, self.options.cachedir)
 
     def setup_network(self):
         '''
         Sets up network including starting up nodes.
         '''
         self.setup_nodes()
 
         # Connect the nodes as a "chain".  This allows us
         # to split the network between nodes 1 and 2 to get
         # two halves that can work on competing chains.
 
         for i in range(self.num_nodes - 1):
             connect_nodes_bi(self.nodes, i, i + 1)
         self.sync_all()
 
     def setup_nodes(self):
         extra_args = None
         if hasattr(self, "extra_args"):
             extra_args = self.extra_args
         self.nodes = start_nodes(
             self.num_nodes, self.options.tmpdir, extra_args)
 
     def run_test(self):
         raise NotImplementedError
 
     # Main function. This should not be overridden by the subclass test scripts.
 
     def main(self):
 
         parser = optparse.OptionParser(usage="%prog [options]")
         parser.add_option(
             "--nocleanup", dest="nocleanup", default=False, action="store_true",
             help="Leave bitcoinds and test.* datadir on exit or error")
         parser.add_option(
             "--noshutdown", dest="noshutdown", default=False, action="store_true",
             help="Don't stop bitcoinds after the test execution")
         parser.add_option(
             "--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"),
             help="Source directory containing bitcoind/bitcoin-cli (default: %default)")
         parser.add_option(
             "--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
             help="Directory for caching pregenerated datadirs")
         parser.add_option(
             "--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"),
             help="Root directory for datadirs")
         parser.add_option("-l", "--loglevel", dest="loglevel", default="INFO",
                           help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.")
         parser.add_option(
             "--tracerpc", dest="trace_rpc", default=False, action="store_true",
             help="Print out all RPC calls as they are made")
         parser.add_option(
             "--portseed", dest="port_seed", default=os.getpid(), type='int',
             help="The seed to use for assigning port numbers (default: current process id)")
         parser.add_option("--coveragedir", dest="coveragedir",
                           help="Write tested RPC commands into this directory")
         parser.add_option("--configfile", dest="configfile",
                           help="Location of the test framework config file")
         self.add_options(parser)
         (self.options, self.args) = parser.parse_args()
 
         # backup dir variable for removal at cleanup
         self.options.root, self.options.tmpdir = self.options.tmpdir, self.options.tmpdir + \
             '/' + str(self.options.port_seed)
 
         if self.options.coveragedir:
             enable_coverage(self.options.coveragedir)
 
         PortSeed.n = self.options.port_seed
 
         os.environ['PATH'] = self.options.srcdir + ":" + \
             self.options.srcdir + "/qt:" + os.environ['PATH']
 
         check_json_precision()
 
         # Set up temp directory and start logging
         os.makedirs(self.options.tmpdir, exist_ok=False)
         self._start_logging()
 
         success = TestStatus.FAILED
 
         try:
             self.setup_chain()
             self.setup_network()
             self.run_test()
             success = TestStatus.PASSED
         except JSONRPCException as e:
             self.log.exception("JSONRPC error")
         except SkipTest as e:
             self.log.warning("Test Skipped: %s" % e.message)
             success = TestStatus.SKIPPED
         except AssertionError as e:
             self.log.exception("Assertion failed")
         except KeyError as e:
             self.log.exception("Key error")
         except Exception as e:
             self.log.exception("Unexpected exception caught during testing")
         except KeyboardInterrupt as e:
             self.log.warning("Exiting after keyboard interrupt")
 
         if not self.options.noshutdown:
             self.log.info("Stopping nodes")
             if self.nodes:
                 self.stop_nodes()
         else:
             self.log.info(
                 "Note: bitcoinds were not stopped and may still be running")
 
         if not self.options.nocleanup and not self.options.noshutdown and success != TestStatus.FAILED:
             self.log.info("Cleaning up")
             shutil.rmtree(self.options.tmpdir)
             if not os.listdir(self.options.root):
                 os.rmdir(self.options.root)
         else:
             self.log.warning("Not cleaning up dir %s" % self.options.tmpdir)
             if os.getenv("PYTHON_DEBUG", ""):
                 # Dump the end of the debug logs, to aid in debugging rare
                 # travis failures.
                 import glob
-                filenames = glob.glob(
-                    self.options.tmpdir + "/node*/regtest/debug.log")
+                filenames = [self.options.tmpdir + "/test_framework.log"]
+                filenames += glob.glob(self.options.tmpdir +
+                                       "/node*/regtest/debug.log")
                 MAX_LINES_TO_PRINT = 1000
                 for fn in filenames:
                     try:
                         with open(fn, 'r') as f:
                             print("From", fn, ":")
                             print("".join(deque(f, MAX_LINES_TO_PRINT)))
                     except OSError:
                         print("Opening file %s failed." % fn)
                         traceback.print_exc()
 
         if success == TestStatus.PASSED:
             self.log.info("Tests successful")
             sys.exit(TEST_EXIT_PASSED)
         elif success == TestStatus.SKIPPED:
             self.log.info("Test skipped")
             sys.exit(TEST_EXIT_SKIPPED)
         else:
             self.log.error(
                 "Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
             logging.shutdown()
             sys.exit(TEST_EXIT_FAILED)
 
     # Public helper methods. These can be accessed by the subclass test scripts.
 
     def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
         return start_node(i, dirname, extra_args, rpchost, timewait, binary, stderr)
 
     def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
         return start_nodes(num_nodes, dirname, extra_args, rpchost, timewait, binary)
 
     def stop_node(self, num_node):
         stop_node(self.nodes[num_node], num_node)
 
     def stop_nodes(self):
         stop_nodes(self.nodes)
 
     def split_network(self):
         """
         Split the network of four nodes into nodes 0/1 and 2/3.
         """
         disconnect_nodes(self.nodes[1], 2)
         disconnect_nodes(self.nodes[2], 1)
         self.sync_all([self.nodes[:2], self.nodes[2:]])
 
     def join_network(self):
         """
         Join the (previously split) network halves together.
         """
         connect_nodes_bi(self.nodes, 1, 2)
         self.sync_all()
 
     def sync_all(self, node_groups=None):
         if not node_groups:
             node_groups = [self.nodes]
 
         for group in node_groups:
             sync_blocks(group)
             sync_mempools(group)
 
     # Private helper methods. These should not be accessed by the subclass test scripts.
 
     def _start_logging(self):
         # Add logger and logging handlers
         self.log = logging.getLogger('TestFramework')
         self.log.setLevel(logging.DEBUG)
         # Create file handler to log all messages
         fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log')
         fh.setLevel(logging.DEBUG)
         # Create console handler to log messages to stderr. By default this
         # logs only error messages, but can be configured with --loglevel.
         ch = logging.StreamHandler(sys.stdout)
         # User can provide log level as a number or string (eg DEBUG). loglevel
         # was caught as a string, so try to convert it to an int
         ll = int(
             self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper()
         ch.setLevel(ll)
         # Format logs the same as bitcoind's debug.log with microprecision (so
         # log files can be concatenated and sorted)
         formatter = logging.Formatter(
             fmt='%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
         formatter.converter = time.gmtime
         fh.setFormatter(formatter)
         ch.setFormatter(formatter)
 
         # add the handlers to the logger
         self.log.addHandler(fh)
         self.log.addHandler(ch)
 
         if self.options.trace_rpc:
             rpc_logger = logging.getLogger("BitcoinRPC")
             rpc_logger.setLevel(logging.DEBUG)
             rpc_handler = logging.StreamHandler(sys.stdout)
             rpc_handler.setLevel(logging.DEBUG)
             rpc_logger.addHandler(rpc_handler)
 
     def _initialize_chain(self, test_dir, num_nodes, cachedir):
         """Initialize a pre-mined blockchain for use by the test.
 
         Create a cache of a 200-block-long chain (with wallet) for MAX_NODES
         Afterward, create num_nodes copies from the cache."""
 
         assert num_nodes <= MAX_NODES
         create_cache = False
         for i in range(MAX_NODES):
             if not os.path.isdir(os.path.join(cachedir, 'node' + str(i))):
                 create_cache = True
                 break
 
         if create_cache:
             self.log.debug("Creating data directories from cached datadir")
 
             # find and delete old cache directories if any exist
             for i in range(MAX_NODES):
                 if os.path.isdir(os.path.join(cachedir, "node" + str(i))):
                     shutil.rmtree(os.path.join(cachedir, "node" + str(i)))
 
             # Create cache directories, run bitcoinds:
             for i in range(MAX_NODES):
                 datadir = initialize_datadir(cachedir, i)
                 args = [os.getenv("BITCOIND", "bitcoind"), "-server",
                         "-keypool=1", "-datadir=" + datadir, "-discover=0"]
                 if i > 0:
                     args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
                 bitcoind_processes[i] = subprocess.Popen(args)
                 self.log.debug(
                     "initialize_chain: bitcoind started, waiting for RPC to come up")
                 wait_for_bitcoind_start(bitcoind_processes[i], datadir, i)
                 self.log.debug("initialize_chain: RPC successfully started")
 
             self.nodes = []
             for i in range(MAX_NODES):
                 try:
                     self.nodes.append(get_rpc_proxy(
                         rpc_url(get_datadir_path(cachedir, i), i), i))
                 except:
                     self.log.exception("Error connecting to node %d" % i)
                     sys.exit(1)
 
             # Create a 200-block-long chain; each of the 4 first nodes
             # gets 25 mature blocks and 25 immature.
             # Note: To preserve compatibility with older versions of
             # initialize_chain, only 4 nodes will generate coins.
             #
             # blocks are created with timestamps 10 minutes apart
             # starting from 2010 minutes in the past
             enable_mocktime()
             block_time = get_mocktime() - (201 * 10 * 60)
             for i in range(2):
                 for peer in range(4):
                     for j in range(25):
                         set_node_times(self.nodes, block_time)
                         self.nodes[peer].generate(1)
                         block_time += 10 * 60
                     # Must sync before next peer starts generating blocks
                     sync_blocks(self.nodes)
 
             # Shut them down, and clean up cache directories:
             self.stop_nodes()
             self.nodes = []
             disable_mocktime()
             for i in range(MAX_NODES):
                 os.remove(log_filename(cachedir, i, "debug.log"))
                 os.remove(log_filename(cachedir, i, "db.log"))
                 os.remove(log_filename(cachedir, i, "peers.dat"))
                 os.remove(log_filename(cachedir, i, "fee_estimates.dat"))
 
         for i in range(num_nodes):
             from_dir = os.path.join(cachedir, "node" + str(i))
             to_dir = os.path.join(test_dir, "node" + str(i))
             shutil.copytree(from_dir, to_dir)
             # Overwrite port/rpcport in bitcoin.conf
             initialize_datadir(test_dir, i)
 
     def _initialize_chain_clean(self, test_dir, num_nodes):
         """Initialize empty blockchain for use by the test.
 
         Create an empty blockchain and num_nodes wallets.
         Useful if a test case wants complete control over initialization."""
         for i in range(num_nodes):
             initialize_datadir(test_dir, i)
 
 # Test framework for doing p2p comparison testing, which sets up some bitcoind
 # binaries:
 # 1 binary: test binary
 # 2 binaries: 1 test binary, 1 ref binary
 # n>2 binaries: 1 test binary, n-1 ref binaries
 
 
 class SkipTest(Exception):
     """This exception is raised to skip a test"""
 
     def __init__(self, message):
         self.message = message
 
 
 class ComparisonTestFramework(BitcoinTestFramework):
 
     def __init__(self):
         super().__init__()
         self.num_nodes = 2
         self.setup_clean_chain = True
 
     def add_options(self, parser):
         parser.add_option("--testbinary", dest="testbinary",
                           default=os.getenv("BITCOIND", "bitcoind"),
                           help="bitcoind binary to test")
         parser.add_option("--refbinary", dest="refbinary",
                           default=os.getenv("BITCOIND", "bitcoind"),
                           help="bitcoind binary to use for reference nodes (if any)")
 
     def setup_network(self):
         extra_args = [['-whitelist=127.0.0.1']] * self.num_nodes
         if hasattr(self, "extra_args"):
             extra_args = self.extra_args
         self.nodes = self.start_nodes(
             self.num_nodes, self.options.tmpdir, extra_args,
             binary=[self.options.testbinary] +
             [self.options.refbinary] * (self.num_nodes - 1))
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 237ee6a7ef..fe68155730 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -1,455 +1,463 @@
 #!/usr/bin/env python3
 # Copyright (c) 2014-2016 The Bitcoin Core developers
 # Copyright (c) 2017 The Bitcoin developers
 # Distributed under the MIT software license, see the accompanying
 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 """Run regression test suite.
 
 This module calls down into individual test cases via subprocess. It will
 forward all unrecognized arguments onto the individual test scripts.
 
 Functional tests are disabled on Windows by default. Use --force to run them anyway.
 
 For a description of arguments recognized by test scripts, see
 `test/functional/test_framework/test_framework.py:BitcoinTestFramework.main`.
 
 """
 
 import argparse
 import configparser
 import os
 import time
 import shutil
 import signal
 import sys
 import subprocess
 import tempfile
 import re
+import logging
 
 BOLD = ("", "")
 RED = ("", "")
 GREEN = ("", "")
 if os.name == 'posix':
     # primitive formatting on supported
     # terminal via ANSI escape sequences:
     BOLD = ('\033[0m', '\033[1m')
     RED = ("\033[0m", "\033[31m")
     GREEN = ("\033[0m", "\033[32m")
 
 TEST_EXIT_PASSED = 0
 TEST_EXIT_SKIPPED = 77
 
 BASE_SCRIPTS = [
     # Longest test should go first, to favor running tests in parallel
     'wallet-hd.py',
     'walletbackup.py',
     # vv Tests less than 5m vv
     'p2p-fullblocktest.py',
     'fundrawtransaction.py',
     'p2p-compactblocks.py',
     # vv Tests less than 2m vv
     'wallet.py',
     'wallet-accounts.py',
     'wallet-dump.py',
     'listtransactions.py',
     # vv Tests less than 60s vv
     'sendheaders.py',
     'zapwallettxes.py',
     'importmulti.py',
     'mempool_limit.py',
     'merkle_blocks.py',
     'receivedby.py',
     'abandonconflict.py',
     'bip68-112-113-p2p.py',
     'rawtransactions.py',
     'reindex.py',
     # vv Tests less than 30s vv
     'zmq_test.py',
     'mempool_resurrect_test.py',
     'txn_doublespend.py --mineblock',
     'txn_clone.py',
     'getchaintips.py',
     'rest.py',
     'mempool_spendcoinbase.py',
     'mempool_reorg.py',
     'mempool_persist.py',
     'httpbasics.py',
     'multi_rpc.py',
     'proxy_test.py',
     'signrawtransactions.py',
     'disconnect_ban.py',
     'decodescript.py',
     'blockchain.py',
     'disablewallet.py',
     'net.py',
     'keypool.py',
     'p2p-mempool.py',
     'prioritise_transaction.py',
     'high_priority_transaction.py',
     'invalidblockrequest.py',
     'invalidtxrequest.py',
     'p2p-versionbits-warning.py',
     'preciousblock.py',
     'importprunedfunds.py',
     'signmessages.py',
     'nulldummy.py',
     'import-rescan.py',
     'rpcnamedargs.py',
     'listsinceblock.py',
     'p2p-leaktests.py',
     'abc-cmdline.py',
     'abc-p2p-fullblocktest.py',
     'abc-rpc.py',
     'mempool-accept-txn.py',
 ]
 
 EXTENDED_SCRIPTS = [
     # Longest test should go first, to favor running tests in parallel
     'pruning.py',
     # vv Tests less than 20m vv
     'smartfees.py',
     # vv Tests less than 5m vv
     'maxuploadtarget.py',
     'mempool_packages.py',
     # vv Tests less than 2m vv
     'bip68-sequence.py',
     'getblocktemplate_longpoll.py',
     'p2p-timeouts.py',
     # vv Tests less than 60s vv
     'bip9-softforks.py',
     'p2p-feefilter.py',
     'rpcbind_test.py',
     # vv Tests less than 30s vv
     'assumevalid.py',
     'bip65-cltv.py',
     'bip65-cltv-p2p.py',
     'bipdersig-p2p.py',
     'bipdersig.py',
     'getblocktemplate_proposals.py',
     'txn_doublespend.py',
     'txn_clone.py --mineblock',
     'forknotify.py',
     'invalidateblock.py',
     'p2p-acceptblock.py',
 ]
 
 # Place EXTENDED_SCRIPTS first since it has the 3 longest running tests
 ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS
 
 
 NON_SCRIPTS = [
     # These are python files that live in the functional tests directory, but are not test scripts.
     "combine_logs.py",
     "create_cache.py",
     "test_runner.py",
 ]
 
 
 def main():
     # Parse arguments and pass through unrecognised args
     parser = argparse.ArgumentParser(add_help=False,
                                      usage='%(prog)s [test_runner.py options] [script options] [scripts]',
                                      description=__doc__,
                                      epilog='''
     Help text and arguments for individual test script:''',
                                      formatter_class=argparse.RawTextHelpFormatter)
     parser.add_argument('--coverage', action='store_true',
                         help='generate a basic coverage report for the RPC interface')
     parser.add_argument(
         '--exclude', '-x', help='specify a comma-seperated-list of scripts to exclude. Do not include the .py extension in the name.')
     parser.add_argument('--extended', action='store_true',
                         help='run the extended test suite in addition to the basic tests')
     parser.add_argument('--force', '-f', action='store_true',
                         help='run tests even on platforms where they are disabled by default (e.g. windows).')
     parser.add_argument('--help', '-h', '-?',
                         action='store_true', help='print help text and exit')
     parser.add_argument('--jobs', '-j', type=int, default=4,
                         help='how many test scripts to run in parallel. Default=4.')
+    parser.add_argument('--quiet', '-q', action='store_true',
+                        help='only print results summary and failure logs')
     args, unknown_args = parser.parse_known_args()
 
     # Create a set to store arguments and create the passon string
     tests = set(arg for arg in unknown_args if arg[:2] != "--")
     passon_args = [arg for arg in unknown_args if arg[:2] == "--"]
 
     # Read config generated by configure.
     config = configparser.ConfigParser()
     configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini"
     config.read_file(open(configfile))
 
     passon_args.append("--configfile=%s" % configfile)
 
+    # Set up logging
+    logging_level = logging.INFO if args.quiet else logging.DEBUG
+    logging.basicConfig(format='%(message)s', level=logging_level)
+
     enable_wallet = config["components"].getboolean("ENABLE_WALLET")
     enable_utils = config["components"].getboolean("ENABLE_UTILS")
     enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND")
 
     if config["environment"]["EXEEXT"] == ".exe" and not args.force:
         # https://github.com/bitcoin/bitcoin/commit/d52802551752140cf41f0d9a225a43e84404d3e9
         # https://github.com/bitcoin/bitcoin/pull/5677#issuecomment-136646964
         print(
             "Tests currently disabled on Windows by default. Use --force option to enable")
         sys.exit(0)
 
     if not (enable_wallet and enable_utils and enable_bitcoind):
         print(
             "No functional tests to run. Wallet, utils, and bitcoind must all be enabled")
         print(
             "Rerun `configure` with -enable-wallet, -with-utils and -with-daemon and rerun make")
         sys.exit(0)
 
     # Build list of tests
     if tests:
         # Individual tests have been specified. Run specified tests that exist
         # in the ALL_SCRIPTS list. Accept the name with or without .py
         # extension.
         test_list = [t for t in ALL_SCRIPTS if
                      (t in tests or re.sub(".py$", "", t) in tests)]
     else:
         # No individual tests have been specified.
         # Run all base tests, and optionally run extended tests.
         test_list = BASE_SCRIPTS
         if args.extended:
             test_list += EXTENDED_SCRIPTS
             # TODO: BASE_SCRIPTS and EXTENDED_SCRIPTS are sorted by runtime
             # (for parallel running efficiency). This combined list will is no
             # longer sorted.
 
     # Remove the test cases that the user has explicitly asked to exclude.
     if args.exclude:
         for exclude_test in args.exclude.split(','):
             if exclude_test + ".py" in test_list:
                 test_list.remove(exclude_test + ".py")
 
     if not test_list:
         print("No valid test scripts specified. Check that your test is in one "
               "of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests")
         sys.exit(0)
 
     if args.help:
         # Print help for test_runner.py, then print help of the first script
         # and exit.
         parser.print_help()
         subprocess.check_call(
             (config["environment"]["SRCDIR"] + '/test/functional/' + test_list[0]).split() + ['-h'])
         sys.exit(0)
 
     check_script_list(config["environment"]["SRCDIR"])
 
     run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"],
               config["environment"]["EXEEXT"], args.jobs, args.coverage, passon_args)
 
 
 def run_tests(test_list, src_dir, build_dir, exeext, jobs=1, enable_coverage=False, args=[]):
     # Set env vars
     if "BITCOIND" not in os.environ:
         os.environ["BITCOIND"] = build_dir + '/src/bitcoind' + exeext
 
     tests_dir = src_dir + '/test/functional/'
     flags = ["--srcdir={}".format(src_dir)] + args
     flags.append("--cachedir=%s/test/cache" % build_dir)
 
     if enable_coverage:
         coverage = RPCCoverage()
         flags.append(coverage.flag)
-        print("Initializing coverage directory at {dir}\n".format(
-            dir=coverage.dir))
+        logging.debug("Initializing coverage directory at %s" % coverage.dir)
     else:
         coverage = None
 
     if len(test_list) > 1 and jobs > 1:
         # Populate cache
         subprocess.check_output([tests_dir + 'create_cache.py'] + flags)
 
     # Run Tests
     all_passed = True
     time_sum = 0
     time0 = time.time()
 
     job_queue = TestHandler(jobs, tests_dir, test_list, flags)
 
     max_len_name = len(max(test_list, key=len))
-    results = BOLD[1] + "%s | %s | %s\n\n" % (
+    results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % (
         "TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0]
     for _ in range(len(test_list)):
         (name, stdout, stderr, status, duration) = job_queue.get_next()
         all_passed = all_passed and status != "Failed"
         time_sum += duration
 
-        print('\n' + BOLD[1] + name + BOLD[0] + ":")
-        print('' if status == "Passed" else stdout + '\n', end='')
-        print('' if stderr == '' else 'stderr:\n' + stderr + '\n', end='')
-        print("Pass: {bold}{result}{unbold}, Duration: {duration}s\n".format(
-            bold=BOLD[1], result=status, unbold=BOLD[0], duration=duration))
-        result = "{name} | {passed} | {duration}s\n".format(name=name.ljust(
-            max_len_name), passed=str(status).ljust(6), duration=duration)
         if status == "Passed":
-            results += GREEN[1] + result + GREEN[0]
+            logging.debug("\n%s%s%s passed, Duration: %s s" %
+                          (BOLD[1], name, BOLD[0], duration))
+        elif status == "Skipped":
+            logging.debug("\n%s%s%s skipped" % (BOLD[1], name, BOLD[0]))
         else:
-            results += RED[1] + result + RED[0]
+            print("\n%s%s%s failed, Duration: %s s\n" %
+                  (BOLD[1], name, BOLD[0], duration))
+            print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n')
+            print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n')
+
+        results += "%s | %s | %s s\n" % (name.ljust(max_len_name),
+                                         status.ljust(7), duration)
 
     results += BOLD[1] + "\n{name} | {passed} | {duration}s (accumulated)".format(
         name="ALL".ljust(max_len_name), passed=str(all_passed).ljust(6), duration=time_sum) + BOLD[0]
     print(results)
     print("\nRuntime: {} s".format(int(time.time() - time0)))
 
     if coverage:
         coverage.report_rpc_coverage()
 
-        print("Cleaning up coverage data")
+        logging.debug("Cleaning up coverage data")
         coverage.cleanup()
 
     sys.exit(not all_passed)
 
 
 class TestHandler:
 
     """
     Trigger the testscrips passed in via the list.
     """
 
     def __init__(self, num_tests_parallel, tests_dir, test_list=None, flags=None):
         assert(num_tests_parallel >= 1)
         self.num_jobs = num_tests_parallel
         self.tests_dir = tests_dir
         self.test_list = test_list
         self.flags = flags
         self.num_running = 0
         # In case there is a graveyard of zombie bitcoinds, we can apply a
         # pseudorandom offset to hopefully jump over them.
         # (625 is PORT_RANGE/MAX_NODES)
         self.portseed_offset = int(time.time() * 1000) % 625
         self.jobs = []
 
     def get_next(self):
         while self.num_running < self.num_jobs and self.test_list:
             # Add tests
             self.num_running += 1
             t = self.test_list.pop(0)
             port_seed = ["--portseed={}".format(
                 len(self.test_list) + self.portseed_offset)]
             log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16)
             log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16)
             self.jobs.append((t,
                               time.time(),
                               subprocess.Popen(
                                   (self.tests_dir + t).split() +
                                   self.flags + port_seed,
                                   universal_newlines=True,
                                   stdout=log_stdout,
                                   stderr=log_stderr),
                               log_stdout,
                               log_stderr))
         if not self.jobs:
             raise IndexError('pop from empty list')
         while True:
             # Return first proc that finishes
             time.sleep(.5)
             for j in self.jobs:
                 (name, time0, proc, log_out, log_err) = j
                 if os.getenv('TRAVIS') == 'true' and int(time.time() - time0) > 20 * 60:
                     # In travis, timeout individual tests after 20 minutes (to stop tests hanging and not
                     # providing useful output.
                     proc.send_signal(signal.SIGINT)
                 if proc.poll() is not None:
                     log_out.seek(0), log_err.seek(0)
                     [stdout, stderr] = [l.read().decode('utf-8')
                                         for l in (log_out, log_err)]
                     log_out.close(), log_err.close()
                     if proc.returncode == TEST_EXIT_PASSED and stderr == "":
                         status = "Passed"
                     elif proc.returncode == TEST_EXIT_SKIPPED:
                         status = "Skipped"
                     else:
                         status = "Failed"
                     self.num_running -= 1
                     self.jobs.remove(j)
                     return name, stdout, stderr, status, int(
                         time.time() - time0)
             print('.', end='', flush=True)
 
 
 def check_script_list(src_dir):
     """Check scripts directory.
 
     Check that there are no scripts in the functional tests directory which are
     not being run by pull-tester.py."""
     script_dir = src_dir + '/test/functional/'
     python_files = set([t for t in os.listdir(script_dir) if t[-3:] == ".py"])
     missed_tests = list(
         python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS)))
     if len(missed_tests) != 0:
         print("The following scripts are not being run:" + str(missed_tests))
         print("Check the test lists in test_runner.py")
         sys.exit(1)
 
 
 class RPCCoverage(object):
 
     """
     Coverage reporting utilities for test_runner.
 
     Coverage calculation works by having each test script subprocess write
     coverage files into a particular directory. These files contain the RPC
     commands invoked during testing, as well as a complete listing of RPC
     commands per `bitcoin-cli help` (`rpc_interface.txt`).
 
     After all tests complete, the commands run are combined and diff'd against
     the complete list to calculate uncovered RPC commands.
 
     See also: test/functional/test_framework/coverage.py
 
     """
 
     def __init__(self):
         self.dir = tempfile.mkdtemp(prefix="coverage")
         self.flag = '--coveragedir={}'.format(self.dir)
 
     def report_rpc_coverage(self):
         """
         Print out RPC commands that were unexercised by tests.
 
         """
         uncovered = self._get_uncovered_rpc_commands()
 
         if uncovered:
             print("Uncovered RPC commands:")
             print("".join(("  - {}\n".format(i)) for i in sorted(uncovered)))
         else:
             print("All RPC commands covered.")
 
     def cleanup(self):
         return shutil.rmtree(self.dir)
 
     def _get_uncovered_rpc_commands(self):
         """
         Return a set of currently untested RPC commands.
 
         """
         # This is shared from `test/functional/test-framework/coverage.py`
         reference_filename = 'rpc_interface.txt'
         coverage_file_prefix = 'coverage.'
 
         coverage_ref_filename = os.path.join(self.dir, reference_filename)
         coverage_filenames = set()
         all_cmds = set()
         covered_cmds = set()
 
         if not os.path.isfile(coverage_ref_filename):
             raise RuntimeError("No coverage reference found")
 
         with open(coverage_ref_filename, 'r') as f:
             all_cmds.update([i.strip() for i in f.readlines()])
 
         for root, dirs, files in os.walk(self.dir):
             for filename in files:
                 if filename.startswith(coverage_file_prefix):
                     coverage_filenames.add(os.path.join(root, filename))
 
         for filename in coverage_filenames:
             with open(filename, 'r') as f:
                 covered_cmds.update([i.strip() for i in f.readlines()])
 
         return all_cmds - covered_cmds
 
 
 if __name__ == '__main__':
     main()