Skip to content

Commit 60d739e

Browse files
committed
Merge #11789: [tests] [travis-ci] Combine logs on failure
ff8a9b0 [tests] Add combinedlogslen argument to test_runner.py (John Newbery) dba94ea [tests] [travis-ci] Move Travis functional test log post processing to test_runner (John Newbery) bba1c54 [tests] Improve logging shutdown and add hint for combine_logs (John Newbery) Pull request description: Replaces #11779 . Notes from that PR: > Currently, when a functional test fails, the debug logs are printed sequentially to the travis log. This makes debugging race conditions based on the travis log hard. Instead, all logs events should be combined and sorted by their timestamp, then appended to the travis log. @MarcoFalke Tree-SHA512: 56c80067d6a2c92f7e6a35e3ae5160637a0de052d9da593c7be6e02233544a93c66d62456f903f85e2edc09e31ab4bdafd1aed1d9897ae48c634f82631f856f7
2 parents dd49862 + ff8a9b0 commit 60d739e

File tree

3 files changed

+26
-29
lines changed

3 files changed

+26
-29
lines changed

.travis.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ env:
1818
- CCACHE_COMPRESS=1
1919
- BASE_OUTDIR=$TRAVIS_BUILD_DIR/out
2020
- SDK_URL=https://bitcoincore.org/depends-sources/sdks
21-
- PYTHON_DEBUG=1
2221
- WINEDEBUG=fixme-all
2322
matrix:
2423
# ARM
@@ -79,7 +78,7 @@ script:
7978
- export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/depends/$HOST/lib
8079
- if [ "$RUN_TESTS" = "true" ]; then travis_wait 30 make $MAKEJOBS check VERBOSE=1; fi
8180
- if [ "$TRAVIS_EVENT_TYPE" = "cron" ]; then extended="--extended --exclude pruning,dbcrash"; fi
82-
- if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --coverage --quiet ${extended}; fi
81+
- if [ "$RUN_TESTS" = "true" ]; then test/functional/test_runner.py --combinedlogslen=4000 --coverage --quiet ${extended}; fi
8382
after_script:
8483
- echo $TRAVIS_COMMIT_RANGE
8584
- echo $TRAVIS_COMMIT_LOG

test/functional/test_framework/test_framework.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Base class for RPC testing."""
66

7-
from collections import deque
87
from enum import Enum
98
import logging
109
import optparse
@@ -149,32 +148,19 @@ def main(self):
149148
shutil.rmtree(self.options.tmpdir)
150149
else:
151150
self.log.warning("Not cleaning up dir %s" % self.options.tmpdir)
152-
if os.getenv("PYTHON_DEBUG", ""):
153-
# Dump the end of the debug logs, to aid in debugging rare
154-
# travis failures.
155-
import glob
156-
filenames = [self.options.tmpdir + "/test_framework.log"]
157-
filenames += glob.glob(self.options.tmpdir + "/node*/regtest/debug.log")
158-
MAX_LINES_TO_PRINT = 1000
159-
for fn in filenames:
160-
try:
161-
with open(fn, 'r') as f:
162-
print("From", fn, ":")
163-
print("".join(deque(f, MAX_LINES_TO_PRINT)))
164-
except OSError:
165-
print("Opening file %s failed." % fn)
166-
traceback.print_exc()
167151

168152
if success == TestStatus.PASSED:
169153
self.log.info("Tests successful")
170-
sys.exit(TEST_EXIT_PASSED)
154+
exit_code = TEST_EXIT_PASSED
171155
elif success == TestStatus.SKIPPED:
172156
self.log.info("Test skipped")
173-
sys.exit(TEST_EXIT_SKIPPED)
157+
exit_code = TEST_EXIT_SKIPPED
174158
else:
175159
self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
176-
logging.shutdown()
177-
sys.exit(TEST_EXIT_FAILED)
160+
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))
161+
exit_code = TEST_EXIT_FAILED
162+
logging.shutdown()
163+
sys.exit(exit_code)
178164

179165
# Methods to override in subclass test scripts.
180166
def set_test_params(self):

test/functional/test_runner.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
import argparse
18+
from collections import deque
1819
import configparser
1920
import datetime
2021
import os
@@ -174,6 +175,7 @@ def main():
174175
epilog='''
175176
Help text and arguments for individual test script:''',
176177
formatter_class=argparse.RawTextHelpFormatter)
178+
parser.add_argument('--combinedlogslen', '-c', type=int, default=0, help='print a combined log (of length n lines) from all test nodes and test framework to the console on failure.')
177179
parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface')
178180
parser.add_argument('--exclude', '-x', help='specify a comma-separated-list of scripts to exclude.')
179181
parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests')
@@ -266,9 +268,9 @@ def main():
266268
if not args.keepcache:
267269
shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True)
268270

269-
run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args)
271+
run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args, args.combinedlogslen)
270272

271-
def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[]):
273+
def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[], combined_logs_len=0):
272274
# Warn if bitcoind is already running (unix only)
273275
try:
274276
if subprocess.check_output(["pidof", "bitcoind"]) is not None:
@@ -314,7 +316,7 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove
314316
max_len_name = len(max(test_list, key=len))
315317

316318
for _ in range(len(test_list)):
317-
test_result, stdout, stderr = job_queue.get_next()
319+
test_result, testdir, stdout, stderr = job_queue.get_next()
318320
test_results.append(test_result)
319321

320322
if test_result.status == "Passed":
@@ -325,6 +327,14 @@ def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_cove
325327
print("\n%s%s%s failed, Duration: %s s\n" % (BOLD[1], test_result.name, BOLD[0], test_result.time))
326328
print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n')
327329
print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n')
330+
if combined_logs_len and os.path.isdir(testdir):
331+
# Print the final `combinedlogslen` lines of the combined logs
332+
print('{}Combine the logs and print the last {} lines ...{}'.format(BOLD[1], combined_logs_len, BOLD[0]))
333+
print('\n============')
334+
print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0]))
335+
print('============\n')
336+
combined_logs, _ = subprocess.Popen([os.path.join(tests_dir, 'combine_logs.py'), '-c', testdir], universal_newlines=True, stdout=subprocess.PIPE).communicate()
337+
print("\n".join(deque(combined_logs.splitlines(), combined_logs_len)))
328338

329339
print_results(test_results, max_len_name, (int(time.time() - time0)))
330340

@@ -389,13 +399,15 @@ def get_next(self):
389399
log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16)
390400
log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16)
391401
test_argv = t.split()
392-
tmpdir = ["--tmpdir=%s/%s_%s" % (self.tmpdir, re.sub(".py$", "", test_argv[0]), portseed)]
402+
testdir = "{}/{}_{}".format(self.tmpdir, re.sub(".py$", "", test_argv[0]), portseed)
403+
tmpdir_arg = ["--tmpdir={}".format(testdir)]
393404
self.jobs.append((t,
394405
time.time(),
395-
subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir,
406+
subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir_arg,
396407
universal_newlines=True,
397408
stdout=log_stdout,
398409
stderr=log_stderr),
410+
testdir,
399411
log_stdout,
400412
log_stderr))
401413
if not self.jobs:
@@ -404,7 +416,7 @@ def get_next(self):
404416
# Return first proc that finishes
405417
time.sleep(.5)
406418
for j in self.jobs:
407-
(name, time0, proc, log_out, log_err) = j
419+
(name, time0, proc, testdir, log_out, log_err) = j
408420
if os.getenv('TRAVIS') == 'true' and int(time.time() - time0) > 20 * 60:
409421
# In travis, timeout individual tests after 20 minutes (to stop tests hanging and not
410422
# providing useful output.
@@ -422,7 +434,7 @@ def get_next(self):
422434
self.num_running -= 1
423435
self.jobs.remove(j)
424436

425-
return TestResult(name, status, int(time.time() - time0)), stdout, stderr
437+
return TestResult(name, status, int(time.time() - time0)), testdir, stdout, stderr
426438
print('.', end='', flush=True)
427439

428440
class TestResult():

0 commit comments

Comments
 (0)