-
Notifications
You must be signed in to change notification settings - Fork 0
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
Refactor of BitcoinTestFramework main function. #26
Conversation
@@ -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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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()
.
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
You should also reword the second commit log to remove reference to |
Thinking about this some more, perhaps a good way to get this merged to Bitcoin Core would be to include the I'd hold off on that while we're still iterating on |
I am adding another TODO for this PR.
|
I don't think we want more than one instance of |
3f5baf0
to
774ecb1
Compare
There was a problem hiding this 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.
sys.exit(exit_code) | ||
|
||
for h in list(self.log.handlers): | ||
self.log.removeHandler(h) |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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)
There was a problem hiding this 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__) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
25ebea1
to
5f01275
Compare
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.
5f01275
to
bf4fc41
Compare
@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. |
PR opened against bitcoin/bitcoin here: bitcoin#17288 |
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.