mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 12:32:48 +01:00
e197f976e1
* Merge #16509: test: Adapt test framework for chains other than "regtest" faf36838bdba7393960fce6ad0c56dc1f93f5870 test: Avoid hardcoding the chain name in combine_logs (MarcoFalke) fa8a1d7ba30040f8c74f93fc41a61276c255a6a6 test: Adapt test framework for chains other than "regtest" (MarcoFalke) 68f546635d5de2ccfedadeabc7bc79e12e5eca6a test: Fix “local variable 'e' is assigned to but never used” (Ben Woosley) Pull request description: This is required for various work in progress: * testchains #8994 * signet #16411 * some of my locally written tests While it will be unused in the master branch as of now, it will make all of those pull requests shorter. Thus review for non-regtest tests can focus on the actual changes and not some test framework changes. ACKs for top commit: jonatack: ACK faf36838bdba7393960fce6ad0c56dc1f93f5870, ran tests and reviewed the code. Tree-SHA512: 35add66c12cab68f2fac8f7c7d47c604d3f24eae9336ff78f83e2c92b3dc08a25e7f4217199bac5393dd3fb72f945bba9c001d6fbb8efd298c88858075fcb3d6 * Add devnet support for tests * test: make sure devnet can connect to each other and start * Partial merge bitcoin/bitcoin#16681: Tests: Use self.chain instead of 'regtest' in almost all current tests, revert one TODO while at it Co-authored-by: MarcoFalke <falke.marco@gmail.com> Co-authored-by: Jorge Timón <jtimon@jtimon.cc>
124 lines
5.0 KiB
Python
Executable File
124 lines
5.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Combine logs from multiple bitcoin nodes as well as the test_framework log.
|
|
|
|
This streams the combined log output to stdout. Use combine_logs.py > outputfile
|
|
to write to an outputfile."""
|
|
|
|
import argparse
|
|
from collections import defaultdict, namedtuple
|
|
import glob
|
|
import heapq
|
|
import itertools
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# Matches on the date format at the start of the log event
|
|
TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z")
|
|
|
|
LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event'])
|
|
|
|
def main():
|
|
"""Main function. Parses args, reads the log files and renders them as text or html."""
|
|
|
|
parser = argparse.ArgumentParser(usage='%(prog)s [options] <test temporary directory>', description=__doc__)
|
|
parser.add_argument('-c', '--color', dest='color', action='store_true', help='outputs the combined log with events colored by source (requires posix terminal colors. Use less -r for viewing)')
|
|
parser.add_argument('--html', dest='html', action='store_true', help='outputs the combined log as html. Requires jinja2. pip install jinja2')
|
|
args, unknown_args = parser.parse_known_args()
|
|
|
|
if args.color and os.name != 'posix':
|
|
print("Color output requires posix terminal colors.")
|
|
sys.exit(1)
|
|
|
|
if args.html and args.color:
|
|
print("Only one out of --color or --html should be specified")
|
|
sys.exit(1)
|
|
|
|
# There should only be one unknown argument - the path of the temporary test directory
|
|
if len(unknown_args) != 1:
|
|
print("Unexpected arguments" + str(unknown_args))
|
|
sys.exit(1)
|
|
|
|
log_events = read_logs(unknown_args[0])
|
|
|
|
print_logs(log_events, color=args.color, html=args.html)
|
|
|
|
def read_logs(tmp_dir):
|
|
"""Reads log files.
|
|
|
|
Delegates to generator function get_log_events() to provide individual log events
|
|
for each of the input log files."""
|
|
|
|
# Find out what the folder is called that holds the debug.log file
|
|
chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir))
|
|
if chain:
|
|
chain = chain[0] # pick the first one if more than one chain was found (should never happen)
|
|
chain = re.search('node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name
|
|
else:
|
|
chain = 'regtest' # fallback to regtest (should only happen when none exists)
|
|
|
|
files = [("test", "%s/test_framework.log" % tmp_dir)]
|
|
for i in itertools.count():
|
|
logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain)
|
|
if not os.path.isfile(logfile):
|
|
break
|
|
files.append(("node%d" % i, logfile))
|
|
|
|
return heapq.merge(*[get_log_events(source, f) for source, f in files])
|
|
|
|
def get_log_events(source, logfile):
|
|
"""Generator function that returns individual log events.
|
|
|
|
Log events may be split over multiple lines. We use the timestamp
|
|
regex match as the marker for a new log event."""
|
|
try:
|
|
with open(logfile, 'r', encoding='utf-8') as infile:
|
|
event = ''
|
|
timestamp = ''
|
|
for line in infile:
|
|
# skip blank lines
|
|
if line == '\n':
|
|
continue
|
|
# if this line has a timestamp, it's the start of a new log event.
|
|
time_match = TIMESTAMP_PATTERN.match(line)
|
|
if time_match:
|
|
if event:
|
|
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
|
|
event = line
|
|
timestamp = time_match.group()
|
|
# if it doesn't have a timestamp, it's a continuation line of the previous log.
|
|
else:
|
|
event += "\n" + line
|
|
# Flush the final event
|
|
yield LogEvent(timestamp=timestamp, source=source, event=event.rstrip())
|
|
except FileNotFoundError:
|
|
print("File %s could not be opened. Continuing without it." % logfile, file=sys.stderr)
|
|
|
|
def print_logs(log_events, color=False, html=False):
|
|
"""Renders the iterator of log events into text or html."""
|
|
if not html:
|
|
colors = defaultdict(lambda: '')
|
|
if color:
|
|
colors["test"] = "\033[0;36m" # CYAN
|
|
colors["node0"] = "\033[0;34m" # BLUE
|
|
colors["node1"] = "\033[0;32m" # GREEN
|
|
colors["node2"] = "\033[0;31m" # RED
|
|
colors["node3"] = "\033[0;33m" # YELLOW
|
|
colors["reset"] = "\033[0m" # Reset font color
|
|
|
|
for event in log_events:
|
|
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, event.event, colors["reset"]))
|
|
|
|
else:
|
|
try:
|
|
import jinja2
|
|
except ImportError:
|
|
print("jinja2 not found. Try `pip install jinja2`")
|
|
sys.exit(1)
|
|
print(jinja2.Environment(loader=jinja2.FileSystemLoader(os.path.dirname(os.path.abspath(__file__))))
|
|
.get_template('combined_log_template.html')
|
|
.render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events]))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|