Repository: bitcoin/bitcoin
File: test/functional/test_framework/test_framework.py (Python)

Code before:
        self.log.debug('Setting up network thread')
        self.network_thread = NetworkThread()
        self.network_thread.start()

        if self.options.usecli:
            if not self.supports_cli:
                raise SkipTest("--usecli specified but test does not support using CLI")
            self.skip_if_no_cli()
        self.skip_test_if_missing_module()
        self.setup_chain()
        self.setup_network()

        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")
            pdb.set_trace()

        self.log.debug('Closing down network thread')
        self.network_thread.close()
        self.log.info("Stopping nodes")
        if self.nodes:
            self.stop_nodes()

        should_clean_up = (
            not self.options.nocleanup and
            self.success != TestStatus.FAILED and
            not self.options.perf
        )
        if should_clean_up:
            self.log.info("Cleaning up {} on exit".format(self.options.tmpdir))
            cleanup_tree_on_exit = True
        elif self.options.perf:
            self.log.warning("Not cleaning up dir {} due to perf data".format(self.options.tmpdir))
            cleanup_tree_on_exit = False
        else:
            self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
            cleanup_tree_on_exit = False

        if self.success == TestStatus.PASSED:
            self.log.info("Tests successful")
            exit_code = TEST_EXIT_PASSED
        elif self.success == TestStatus.SKIPPED:
            self.log.info("Test skipped")
            exit_code = TEST_EXIT_SKIPPED
        else:
            self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
            self.log.error("")

Review comment (bug):
Used repurposed test script (556b4646985d4313925bf060692e7a690d396629) from #30660 to see how things behave.

Cleaner shutdowns when failing can be achieved by also avoiding stop-attempts here:
```suggestion
        if self.success == TestStatus.FAILED:
            self.log.info("Not stopping nodes as test failed")
        else:
            self.log.info("Stopping nodes")
            if self.nodes:
                self.stop_nodes()
```

### Output without suggestion
```
₿ build/test/functional/feature_framework_rpc_failure.py 
2025-01-23T10:07:35.733000Z TestFramework (INFO): PRNG seed is: 1128325736874135373
2025-01-23T10:07:35.733000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_qgky8ew8
2025-01-23T10:07:35.744000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 131, in main
    self.setup()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 313, in setup
    self.setup_network()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 401, in setup_network
    self.setup_nodes()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 423, in setup_nodes
    self.start_nodes()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 573, in start_nodes
    node.wait_for_rpc_connection()
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 334, in wait_for_rpc_connection
    self._raise_assertion_error("Unable to connect to bitcoind after {}s".format(self.rpc_timeout))
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 198, in _raise_assertion_error
    raise AssertionError(self._node_msg(msg))
AssertionError: [node 0] Unable to connect to bitcoind after 0s
2025-01-23T10:07:35.795000Z TestFramework (INFO): Stopping nodes
Traceback (most recent call last):
  File "/home/hodlinator/b2/build/test/functional/feature_framework_rpc_failure.py", line 13, in <module>
    FeatureFrameworkRPCFailure(__file__).main()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 159, in main
    exit_code = self.shutdown()
                ^^^^^^^^^^^^^^^
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 328, in shutdown
    self.stop_nodes()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 587, in stop_nodes
    node.stop_node(wait=wait, wait_until_stopped=False)
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 395, in stop_node
    self.stop(wait=wait)
    ^^^^^^^^^
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 215, in __getattr__
    assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: [node 0] Error: no RPC connection
[node 0] Cleaning up leftover process
```
### Output with suggestion
```
₿ build/test/functional/feature_framework_rpc_failure.py 
2025-01-23T10:07:43.326000Z TestFramework (INFO): PRNG seed is: 2921237778351099427
2025-01-23T10:07:43.326000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_7ywaw2wa
2025-01-23T10:07:43.336000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 131, in main
    self.setup()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 313, in setup
    self.setup_network()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 404, in setup_network
    self.setup_nodes()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 426, in setup_nodes
    self.start_nodes()
  File "/home/hodlinator/b2/test/functional/test_framework/test_framework.py", line 576, in start_nodes
    node.wait_for_rpc_connection()
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 334, in wait_for_rpc_connection
    self._raise_assertion_error("Unable to connect to bitcoind after {}s".format(self.rpc_timeout))
  File "/home/hodlinator/b2/test/functional/test_framework/test_node.py", line 198, in _raise_assertion_error
    raise AssertionError(self._node_msg(msg))
AssertionError: [node 0] Unable to connect to bitcoind after 0s
2025-01-23T10:07:43.387000Z TestFramework (INFO): Not stopping nodes as test failed
2025-01-23T10:07:43.387000Z TestFramework (WARNING): Not cleaning up dir /tmp/bitcoin_func_test_7ywaw2wa
2025-01-23T10:07:43.387000Z TestFramework (ERROR): Test failed. Test logging available at /tmp/bitcoin_func_test_7ywaw2wa/test_framework.log
2025-01-23T10:07:43.388000Z TestFramework (ERROR): 
2025-01-23T10:07:43.388000Z TestFramework (ERROR): Hint: Call /home/hodlinator/b2/test/functional/combine_logs.py '/tmp/bitcoin_func_test_7ywaw2wa' to consolidate all logs
2025-01-23T10:07:43.388000Z TestFramework (ERROR): 
2025-01-23T10:07:43.388000Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
2025-01-23T10:07:43.388000Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
2025-01-23T10:07:43.388000Z TestFramework (ERROR): 
[node 0] Cleaning up leftover process
```

Similar results can be observed by using *wallet_migration.py* as described in https://github.com/bitcoin/bitcoin/pull/31620#issuecomment-2577352102.

Code after:
        self.log.debug('Setting up network thread')
        self.network_thread = NetworkThread()
        self.network_thread.start()

        if self.options.usecli:
            if not self.supports_cli:
                raise SkipTest("--usecli specified but test does not support using CLI")
            self.skip_if_no_cli()
        self.skip_test_if_missing_module()
        self.setup_chain()
        self.setup_network()

        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")
            pdb.set_trace()

        self.log.debug('Closing down network thread')
        self.network_thread.close()
        if self.success == TestStatus.FAILED:
            self.log.info("Not stopping nodes as test failed. The dangling processes will be cleaned up later.")
        else:
            self.log.info("Stopping nodes")
            if self.nodes:
                self.stop_nodes()

        should_clean_up = (
            not self.options.nocleanup and
            self.success != TestStatus.FAILED and
            not self.options.perf
        )
        if should_clean_up:
            self.log.info("Cleaning up {} on exit".format(self.options.tmpdir))
            cleanup_tree_on_exit = True
        elif self.options.perf:
            self.log.warning("Not cleaning up dir {} due to perf data".format(self.options.tmpdir))
            cleanup_tree_on_exit = False
        else:
            self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
            cleanup_tree_on_exit = False

        if self.success == TestStatus.PASSED:
            self.log.info("Tests successful")
            exit_code = TEST_EXIT_PASSED
        elif self.success == TestStatus.SKIPPED:
            self.log.info("Test skipped")
            exit_code = TEST_EXIT_SKIPPED