Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor of BitcoinTestFramework main function. #26

Conversation

jachiang
Copy link

@jachiang jachiang commented Sep 2, 2019

In order to wrap the BitcoinTestFramework class in a user-defined class, which can be started and shutdown, the code in BitcoinTestFramework main has been encapsulated in respective functions. Additionally, test-run specific state in temp-directory and NetworkThread is now reset after shutdown.

Behavioural changes:
Potential exceptions raised from any code in setup section of the code are now handled. Previously, only exceptions raised by code beginning here was handled.

TODO's:

  • Refactor NetworkThread class in mininode.py, so each BitcoinTestFramework object initialises a different network thread.
  • Documentation (markdown file) describing how to run an interactive test with TestWrapper.

@@ -480,7 +480,7 @@ def close(self, timeout=10):
wait_until(lambda: not self.network_event_loop.is_running(), timeout=timeout)
self.network_event_loop.close()
self.join(timeout)

NetworkThread.network_event_loop = None # Safe to remove event loop.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the convention seems to be that comments usually go the line above in the python test code

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b1f803d.

self.run_test()
except BaseException as exception:
e = exception
self.shutdown(e = e, exit = True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second parameter (exit) isn't used in this repo. Can you remove it by having self.shutdown return the exit_code and then main() calls sys.exit().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e163c98.

def shutdown(self, e=None, exit=False):
"""Call this method to shutdown the test object and optionally handle an exception."""

if e != None:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes more logical sense to have the exception handling code in the main() function, and then just start shutdown() at the if self.success == TestStatus.FAILED and ... line

Copy link
Author

@jachiang jachiang Oct 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I fully understand your suggestion. If we move error handling into main, this is code that needs to be replicated in TestWrapper.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not:

        try:
            self.setup()
            self.run_test()
        except BaseException as e:
            handle_exception(e)
        finally:
            exit_code = self.shutdown()
            sys.exit(exit_code)

if exit:
sys.exit(exit_code)

def handle_exception(self, e):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my preference would be to not have this as a separate function from main()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e163c98.

@@ -440,7 +466,7 @@ def sync_all(self, nodes=None, **kwargs):

def _start_logging(self):
# Add logger and logging handlers
self.log = logging.getLogger('TestFramework')
self.log = logging.getLogger('TestFramework.'+ self.options.tmpdir) # Assign new logger name to prevent temp path reuse.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right. Since logging.shutdown() is called in TestFramework.shutdown(), no more logging should take place (see https://docs.python.org/3/library/logging.html#logging.shutdown).

A better way to do this would be to replace the logging.shutdown() call with:

handlers = self.log.handlers
for h in handlers:
    h.flush()
    h.flush()
    self.log.removeHandler(h)

That way, when setup is called again, the old handlers will have been removed and the new handlers will be added to the Logging object.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this suggestion. Added to e163c98.

@jnewbery
Copy link

jnewbery commented Sep 3, 2019

You should also reword the second commit log to remove reference to TestWrapper if you want to get this merged to Bitcoin Core.

@jnewbery
Copy link

jnewbery commented Sep 6, 2019

You should also reword the second commit log to remove reference to TestWrapper if you want to get this merged to Bitcoin Core.

Thinking about this some more, perhaps a good way to get this merged to Bitcoin Core would be to include the TestWrapper and add some documentation about how to run an interactive test. That's quite a compelling rationale for making these changes.

I'd hold off on that while we're still iterating on TestWrapper, but after the workshops let's open a PR against https://github.com/bitcoin/bitcoin.

@jachiang
Copy link
Author

jachiang commented Sep 13, 2019

I am adding another TODO for this PR.

  • Refactor NetworkThread class in mininode.py, so each BitcoinTestFramework object initialises a different network thread.

@jnewbery
Copy link

Refactor NetworkThread class in mininode.py, so each BitcoinTestFramework object initialises a different network thread.

I don't think we want more than one instance of BitcoinTestFramework to exist at a time. @jachiang - do you have a reason for wanting this?

@jachiang jachiang force-pushed the master-testframework-main-refactor branch 2 times, most recently from 3f5baf0 to 774ecb1 Compare October 5, 2019 16:57
Copy link
Author

@jachiang jachiang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor NetworkThread class in mininode.py, so each BitcoinTestFramework object initialises a different network thread.

I don't think we want more than one instance of BitcoinTestFramework to exist at a time. @jachiang - do you have a reason for wanting this?

I will omit this from this PR, because it requires limiting one NetworkThread per BitcoinTestFramework, which I am unsure of how to best implement with minimal code changes.

@jachiang jachiang requested a review from jnewbery October 6, 2019 02:57
sys.exit(exit_code)

for h in list(self.log.handlers):
self.log.removeHandler(h)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need to be flushed before being removed?

def shutdown(self, e=None, exit=False):
"""Call this method to shutdown the test object and optionally handle an exception."""

if e != None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not:

        try:
            self.setup()
            self.run_test()
        except BaseException as e:
            handle_exception(e)
        finally:
            exit_code = self.shutdown()
            sys.exit(exit_code)

Copy link

@jnewbery jnewbery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments inline. I also think the first commit comment ("Removal of residual NetworkThread state.") could be expanded a bit to explain why you're making that change.

Once the documentation is added, I think this is ready to open as a PR against bitcoin/bitcoin


def handle_exception(self, e):
# Restore traceback from exception object.
tb = traceback.format_exception(type(e), e, e.__traceback__)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new traceback functionality necessary? Can it be separated into a new commit so you're not moving and changing code in the same commit?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not Ideal, I agree. It seems the logger will only capture the traceback if error logging is called directly inside except (see https://stackoverflow.com/a/5191885 and https://docs.python.org/3/library/logging.html#logging.error). Since we are invoking error/exception logging elsewhere, we reconstruct the traceback string from the exception object.

# Copyright (c) 2014-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.test_framework import BitcoinTestFramework

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pep8 import ordering please.

More generally, can you run flake8 over this new file and resolve any style issues?


def set_test_params(self):
# This can be overriden in setup() parameter.
self.num_nodes=3

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this required? num_nodes defaults to 3 in the setup() method.

bitcoind=abspath(join(__file__ ,"../../../..") + "/src/bitcoind"),
bitcoincli=None,
setup_clean_chain=True,
num_nodes=3,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest you default this to 1

pdbonfailure=False,
usecli = False,
perf = False,
randomseed = None):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried changing the method signature to catch *kwargs and then set the defaults below, to avoid this huge function signature?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is much cleaner as you suggested. Thank you!

@jachiang jachiang force-pushed the master-testframework-main-refactor branch 4 times, most recently from 25ebea1 to 5f01275 Compare October 25, 2019 13:15
The asyncio.new_event_loop() instance is now removed from the NetworkThread class during shutdown. This enables a NetworkThread instance to be restarted after being closed. The current NetworkThread class guards against an existing new_event_loop during initialization.
Setup and shutdown code now moved into dedicated methods. Test "success" is
added as a BitcoinTestFramework member, which can be accessed outside of
main.
In order for BitcoinTestFramework to correctly restart after shutdown,
the previous logging handlers need to be removed, or else logging will
continue in the previous temp directory. "Flush" ensures buffers are
emptied, and "close" ensures file handler close logging file.
TestNode objects need to be removed during shutdown, as setup_nodes
does not remove previous TestNode objects from previous test runs during
setup.
This allows a BitcoinTestFramework child class to set test parameters
in an overridden setup() rather than in an overridden set_test_params().
A BitcoinTestFramework child class which can be imported by an
external user or project. TestWrapper.setup() initiates an underlying
BitcoinTestFramework object with bitcoind subprocesses, rpc interfaces
and test logging. TestWrapper.shutdown() safely tears down the BitcoinTestFramework object.
@jachiang jachiang force-pushed the master-testframework-main-refactor branch from 5f01275 to bf4fc41 Compare October 26, 2019 18:49
@jachiang
Copy link
Author

@jnewbery Sorry this took a while, but I've addressed all feedback, reworked commits for easier review and added a documentation file for the TestWrapper class.

I will review again tomorrow and then open a PR against master.
A very big thank you for your time spent reviewing and improving this!

@jnewbery
Copy link

PR opened against bitcoin/bitcoin here: bitcoin#17288

@jnewbery jnewbery closed this Oct 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants