Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/test_framework.py
Show First 20 Lines • Show All 102 Lines • ▼ Show 20 Lines | def __init__(self): | ||||
self.network_thread = None | self.network_thread = None | ||||
# Wait for up to 60 seconds for the RPC server to respond | # Wait for up to 60 seconds for the RPC server to respond | ||||
self.rpc_timeout = 60 | self.rpc_timeout = 60 | ||||
self.supports_cli = False | self.supports_cli = False | ||||
self.bind_to_localhost_only = True | self.bind_to_localhost_only = True | ||||
def main(self): | def main(self): | ||||
"""Main function. This should not be overridden by the subclass test scripts.""" | """Main function. This should not be overridden by the subclass test scripts.""" | ||||
self.parse_args() | |||||
self.set_test_params() | |||||
assert hasattr( | |||||
self, "num_nodes"), "Test must set self.num_nodes in set_test_params()" | |||||
try: | |||||
self.setup() | |||||
self.run_test() | |||||
except JSONRPCException: | |||||
self.log.exception("JSONRPC error") | |||||
self.success = TestStatus.FAILED | |||||
except SkipTest as e: | |||||
self.log.warning("Test Skipped: {}".format(e.message)) | |||||
self.success = TestStatus.SKIPPED | |||||
except AssertionError: | |||||
self.log.exception("Assertion failed") | |||||
self.success = TestStatus.FAILED | |||||
except KeyError: | |||||
self.log.exception("Key error") | |||||
self.success = TestStatus.FAILED | |||||
except Exception: | |||||
self.log.exception("Unexpected exception caught during testing") | |||||
self.success = TestStatus.FAILED | |||||
except KeyboardInterrupt: | |||||
self.log.warning("Exiting after keyboard interrupt") | |||||
self.success = TestStatus.FAILED | |||||
finally: | |||||
exit_code = self.shutdown() | |||||
sys.exit(exit_code) | |||||
def parse_args(self): | |||||
parser = argparse.ArgumentParser(usage="%(prog)s [options]") | parser = argparse.ArgumentParser(usage="%(prog)s [options]") | ||||
parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true", | parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true", | ||||
help="Leave bitcoinds and test.* datadir on exit or error") | help="Leave bitcoinds and test.* datadir on exit or error") | ||||
parser.add_argument("--noshutdown", dest="noshutdown", default=False, action="store_true", | parser.add_argument("--noshutdown", dest="noshutdown", default=False, action="store_true", | ||||
help="Don't stop bitcoinds after the test execution") | help="Don't stop bitcoinds after the test execution") | ||||
parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), | parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), | ||||
help="Directory for caching pregenerated datadirs (default: %(default)s)") | help="Directory for caching pregenerated datadirs (default: %(default)s)") | ||||
parser.add_argument("--tmpdir", dest="tmpdir", | parser.add_argument("--tmpdir", dest="tmpdir", | ||||
Show All 19 Lines | def parse_args(self): | ||||
parser.add_argument("--randomseed", type=int, | parser.add_argument("--randomseed", type=int, | ||||
help="set a random seed for deterministically reproducing a previous test run") | help="set a random seed for deterministically reproducing a previous test run") | ||||
parser.add_argument("--with-axionactivation", dest="axionactivation", default=False, action="store_true", | parser.add_argument("--with-axionactivation", dest="axionactivation", default=False, action="store_true", | ||||
help="Activate axion update on timestamp {}".format(TIMESTAMP_IN_THE_PAST)) | help="Activate axion update on timestamp {}".format(TIMESTAMP_IN_THE_PAST)) | ||||
self.add_options(parser) | self.add_options(parser) | ||||
self.options = parser.parse_args() | self.options = parser.parse_args() | ||||
self.set_test_params() | def setup(self): | ||||
assert hasattr( | """Call this method to start up the test framework object with options set.""" | ||||
self, "num_nodes"), "Test must set self.num_nodes in set_test_params()" | |||||
PortSeed.n = self.options.port_seed | PortSeed.n = self.options.port_seed | ||||
check_json_precision() | check_json_precision() | ||||
self.options.cachedir = os.path.abspath(self.options.cachedir) | self.options.cachedir = os.path.abspath(self.options.cachedir) | ||||
config = configparser.ConfigParser() | config = configparser.ConfigParser() | ||||
config.read_file(open(self.options.configfile, encoding='utf-8')) | config.read_file(open(self.options.configfile, encoding='utf-8')) | ||||
Show All 31 Lines | def setup(self): | ||||
random.seed(seed) | random.seed(seed) | ||||
self.log.debug("PRNG seed is: {}".format(seed)) | self.log.debug("PRNG seed is: {}".format(seed)) | ||||
self.log.debug('Setting up network thread') | self.log.debug('Setting up network thread') | ||||
self.network_thread = NetworkThread() | self.network_thread = NetworkThread() | ||||
self.network_thread.start() | self.network_thread.start() | ||||
success = TestStatus.FAILED | |||||
try: | |||||
if self.options.usecli: | if self.options.usecli: | ||||
if not self.supports_cli: | if not self.supports_cli: | ||||
raise SkipTest( | raise SkipTest( | ||||
"--usecli specified but test does not support using CLI") | "--usecli specified but test does not support using CLI") | ||||
self.skip_if_no_cli() | self.skip_if_no_cli() | ||||
self.skip_test_if_missing_module() | self.skip_test_if_missing_module() | ||||
self.setup_chain() | self.setup_chain() | ||||
self.setup_network() | self.setup_network() | ||||
self.run_test() | |||||
success = TestStatus.PASSED | |||||
except JSONRPCException: | |||||
self.log.exception("JSONRPC error") | |||||
except SkipTest as e: | |||||
self.log.warning("Test Skipped: {}".format(e.message)) | |||||
success = TestStatus.SKIPPED | |||||
except AssertionError: | |||||
self.log.exception("Assertion failed") | |||||
except KeyError: | |||||
self.log.exception("Key error") | |||||
except Exception: | |||||
self.log.exception("Unexpected exception caught during testing") | |||||
except KeyboardInterrupt: | |||||
self.log.warning("Exiting after keyboard interrupt") | |||||
if success == TestStatus.FAILED and self.options.pdbonfailure: | self.success = TestStatus.PASSED | ||||
def shutdown(self): | |||||
"""Call this method to shut down the test framework object.""" | |||||
if self.success == TestStatus.FAILED and self.options.pdbonfailure: | |||||
print("Testcase failed. Attaching python debugger. Enter ? for help") | print("Testcase failed. Attaching python debugger. Enter ? for help") | ||||
pdb.set_trace() | pdb.set_trace() | ||||
self.log.debug('Closing down network thread') | self.log.debug('Closing down network thread') | ||||
self.network_thread.close() | self.network_thread.close() | ||||
if not self.options.noshutdown: | if not self.options.noshutdown: | ||||
self.log.info("Stopping nodes") | self.log.info("Stopping nodes") | ||||
if self.nodes: | if self.nodes: | ||||
self.stop_nodes() | self.stop_nodes() | ||||
else: | else: | ||||
for node in self.nodes: | for node in self.nodes: | ||||
node.cleanup_on_exit = False | node.cleanup_on_exit = False | ||||
self.log.info( | self.log.info( | ||||
"Note: bitcoinds were not stopped and may still be running") | "Note: bitcoinds were not stopped and may still be running") | ||||
should_clean_up = ( | should_clean_up = ( | ||||
not self.options.nocleanup and | not self.options.nocleanup and | ||||
not self.options.noshutdown and | not self.options.noshutdown and | ||||
success != TestStatus.FAILED and | self.success != TestStatus.FAILED and | ||||
not self.options.perf | not self.options.perf | ||||
) | ) | ||||
if should_clean_up: | if should_clean_up: | ||||
self.log.info("Cleaning up {} on exit".format(self.options.tmpdir)) | self.log.info("Cleaning up {} on exit".format(self.options.tmpdir)) | ||||
cleanup_tree_on_exit = True | cleanup_tree_on_exit = True | ||||
elif self.options.perf: | elif self.options.perf: | ||||
self.log.warning( | self.log.warning( | ||||
"Not cleaning up dir {} due to perf data".format( | "Not cleaning up dir {} due to perf data".format( | ||||
self.options.tmpdir)) | self.options.tmpdir)) | ||||
cleanup_tree_on_exit = False | cleanup_tree_on_exit = False | ||||
else: | else: | ||||
self.log.warning( | self.log.warning( | ||||
"Not cleaning up dir {}".format(self.options.tmpdir)) | "Not cleaning up dir {}".format(self.options.tmpdir)) | ||||
cleanup_tree_on_exit = False | cleanup_tree_on_exit = False | ||||
if success == TestStatus.PASSED: | if self.success == TestStatus.PASSED: | ||||
self.log.info("Tests successful") | self.log.info("Tests successful") | ||||
exit_code = TEST_EXIT_PASSED | exit_code = TEST_EXIT_PASSED | ||||
elif success == TestStatus.SKIPPED: | elif self.success == TestStatus.SKIPPED: | ||||
self.log.info("Test skipped") | self.log.info("Test skipped") | ||||
exit_code = TEST_EXIT_SKIPPED | exit_code = TEST_EXIT_SKIPPED | ||||
else: | else: | ||||
self.log.error( | self.log.error( | ||||
"Test failed. Test logging available at {}/test_framework.log".format(self.options.tmpdir)) | "Test failed. Test logging available at {}/test_framework.log".format(self.options.tmpdir)) | ||||
self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath( | self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath( | ||||
os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir)) | os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir)) | ||||
exit_code = TEST_EXIT_FAILED | exit_code = TEST_EXIT_FAILED | ||||
logging.shutdown() | logging.shutdown() | ||||
if cleanup_tree_on_exit: | if cleanup_tree_on_exit: | ||||
shutil.rmtree(self.options.tmpdir) | shutil.rmtree(self.options.tmpdir) | ||||
sys.exit(exit_code) | return exit_code | ||||
# Methods to override in subclass test scripts. | # Methods to override in subclass test scripts. | ||||
def set_test_params(self): | def set_test_params(self): | ||||
"""Tests must this method to change default values for number of nodes, topology, etc""" | """Tests must this method to change default values for number of nodes, topology, etc""" | ||||
raise NotImplementedError | raise NotImplementedError | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
"""Override this method to add command-line options to the test""" | """Override this method to add command-line options to the test""" | ||||
▲ Show 20 Lines • Show All 374 Lines • Show Last 20 Lines |