Skip to content

Commit 8ad5bde

Browse files
committed
Merge bctest.py into bitcoin-util-test.py
bctest.py is only used as an import by bitcoin-util-test.py. There's no value in keeping it as a separate module, so let's merge them into a single module to keep building and packaging simpler. bitcoin-test-util is importable as a module, so if any future modules really want to import the code from bctest.py, they can import bitcoin-test-util and call the bctest functions by name.
1 parent 95836c5 commit 8ad5bde

File tree

4 files changed

+140
-154
lines changed

4 files changed

+140
-154
lines changed

Makefile.am

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ dist_noinst_SCRIPTS = autogen.sh
223223
EXTRA_DIST = $(top_srcdir)/share/genbuild.sh test/functional/test_runner.py test/functional $(DIST_CONTRIB) $(DIST_DOCS) $(WINDOWS_PACKAGING) $(OSX_PACKAGING) $(BIN_CHECKS)
224224

225225
EXTRA_DIST += \
226-
test/util/bctest.py \
227226
test/util/bitcoin-util-test.py \
228227
test/util/data/bitcoin-util-test.json \
229228
test/util/data/blanktxv1.hex \

configure.ac

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,6 @@ AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/spl
11651165
AC_CONFIG_FILES([doc/Doxyfile])
11661166
AC_CONFIG_LINKS([test/functional/test_runner.py:test/functional/test_runner.py])
11671167
AC_CONFIG_LINKS([test/util/bitcoin-util-test.py:test/util/bitcoin-util-test.py])
1168-
AC_CONFIG_LINKS([test/util/bctest.py:test/util/bctest.py])
11691168

11701169
dnl boost's m4 checks do something really nasty: they export these vars. As a
11711170
dnl result, they leak into secp256k1's configure and crazy things happen.

test/util/bctest.py

Lines changed: 0 additions & 139 deletions
This file was deleted.

test/util/bitcoin-util-test.py

Lines changed: 140 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22
# Copyright 2014 BitPay Inc.
3-
# Copyright 2016 The Bitcoin Core developers
3+
# Copyright 2016-2017 The Bitcoin Core developers
44
# Distributed under the MIT software license, see the accompanying
55
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
66
"""Test framework for bitcoin utils.
@@ -9,23 +9,21 @@
99
1010
Can also be run manually."""
1111

12+
import argparse
13+
import binascii
1214
import configparser
15+
import difflib
16+
import json
17+
import logging
1318
import os
19+
import pprint
20+
import subprocess
1421
import sys
15-
import argparse
16-
import logging
17-
18-
if __name__ == '__main__':
19-
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
20-
import bctest
2122

23+
def main():
2224
config = configparser.ConfigParser()
2325
config.read_file(open(os.path.dirname(__file__) + "/../config.ini"))
2426

25-
buildenv = argparse.Namespace(exeext=config["environment"]["EXEEXT"],
26-
SRCDIR=config["environment"]["SRCDIR"],
27-
BUILDDIR=config["environment"]["BUILDDIR"])
28-
2927
parser = argparse.ArgumentParser(description=__doc__)
3028
parser.add_argument('-v', '--verbose', action='store_true')
3129
args = parser.parse_args()
@@ -37,6 +35,135 @@
3735
level = logging.ERROR
3836
formatter = '%(asctime)s - %(levelname)s - %(message)s'
3937
# Add the format/level to the logger
40-
logging.basicConfig(format = formatter, level=level)
38+
logging.basicConfig(format=formatter, level=level)
39+
40+
bctester(config["environment"]["SRCDIR"] + "/test/util/data", "bitcoin-util-test.json", config["environment"])
41+
42+
def bctester(testDir, input_basename, buildenv):
43+
""" Loads and parses the input file, runs all tests and reports results"""
44+
input_filename = testDir + "/" + input_basename
45+
raw_data = open(input_filename).read()
46+
input_data = json.loads(raw_data)
47+
48+
failed_testcases = []
49+
50+
for testObj in input_data:
51+
try:
52+
bctest(testDir, testObj, buildenv)
53+
logging.info("PASSED: " + testObj["description"])
54+
except:
55+
logging.info("FAILED: " + testObj["description"])
56+
failed_testcases.append(testObj["description"])
57+
58+
if failed_testcases:
59+
error_message = "FAILED_TESTCASES:\n"
60+
error_message += pprint.pformat(failed_testcases, width=400)
61+
logging.error(error_message)
62+
sys.exit(1)
63+
else:
64+
sys.exit(0)
4165

42-
bctest.bctester(buildenv.SRCDIR + "/test/util/data", "bitcoin-util-test.json", buildenv)
66+
def bctest(testDir, testObj, buildenv):
67+
"""Runs a single test, comparing output and RC to expected output and RC.
68+
69+
Raises an error if input can't be read, executable fails, or output/RC
70+
are not as expected. Error is caught by bctester() and reported.
71+
"""
72+
# Get the exec names and arguments
73+
execprog = buildenv["BUILDDIR"] + "/src/" + testObj['exec'] + buildenv["EXEEXT"]
74+
execargs = testObj['args']
75+
execrun = [execprog] + execargs
76+
77+
# Read the input data (if there is any)
78+
stdinCfg = None
79+
inputData = None
80+
if "input" in testObj:
81+
filename = testDir + "/" + testObj['input']
82+
inputData = open(filename).read()
83+
stdinCfg = subprocess.PIPE
84+
85+
# Read the expected output data (if there is any)
86+
outputFn = None
87+
outputData = None
88+
if "output_cmp" in testObj:
89+
outputFn = testObj['output_cmp']
90+
outputType = os.path.splitext(outputFn)[1][1:] # output type from file extension (determines how to compare)
91+
try:
92+
outputData = open(testDir + "/" + outputFn).read()
93+
except:
94+
logging.error("Output file " + outputFn + " can not be opened")
95+
raise
96+
if not outputData:
97+
logging.error("Output data missing for " + outputFn)
98+
raise Exception
99+
100+
# Run the test
101+
proc = subprocess.Popen(execrun, stdin=stdinCfg, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
102+
try:
103+
outs = proc.communicate(input=inputData)
104+
except OSError:
105+
logging.error("OSError, Failed to execute " + execprog)
106+
raise
107+
108+
if outputData:
109+
data_mismatch, formatting_mismatch = False, False
110+
# Parse command output and expected output
111+
try:
112+
a_parsed = parse_output(outs[0], outputType)
113+
except Exception as e:
114+
logging.error('Error parsing command output as %s: %s' % (outputType, e))
115+
raise
116+
try:
117+
b_parsed = parse_output(outputData, outputType)
118+
except Exception as e:
119+
logging.error('Error parsing expected output %s as %s: %s' % (outputFn, outputType, e))
120+
raise
121+
# Compare data
122+
if a_parsed != b_parsed:
123+
logging.error("Output data mismatch for " + outputFn + " (format " + outputType + ")")
124+
data_mismatch = True
125+
# Compare formatting
126+
if outs[0] != outputData:
127+
error_message = "Output formatting mismatch for " + outputFn + ":\n"
128+
error_message += "".join(difflib.context_diff(outputData.splitlines(True),
129+
outs[0].splitlines(True),
130+
fromfile=outputFn,
131+
tofile="returned"))
132+
logging.error(error_message)
133+
formatting_mismatch = True
134+
135+
assert not data_mismatch and not formatting_mismatch
136+
137+
# Compare the return code to the expected return code
138+
wantRC = 0
139+
if "return_code" in testObj:
140+
wantRC = testObj['return_code']
141+
if proc.returncode != wantRC:
142+
logging.error("Return code mismatch for " + outputFn)
143+
raise Exception
144+
145+
if "error_txt" in testObj:
146+
want_error = testObj["error_txt"]
147+
# Compare error text
148+
# TODO: ideally, we'd compare the strings exactly and also assert
149+
# That stderr is empty if no errors are expected. However, bitcoin-tx
150+
# emits DISPLAY errors when running as a windows application on
151+
# linux through wine. Just assert that the expected error text appears
152+
# somewhere in stderr.
153+
if want_error not in outs[1]:
154+
logging.error("Error mismatch:\n" + "Expected: " + want_error + "\nReceived: " + outs[1].rstrip())
155+
raise Exception
156+
157+
def parse_output(a, fmt):
158+
"""Parse the output according to specified format.
159+
160+
Raise an error if the output can't be parsed."""
161+
if fmt == 'json': # json: compare parsed data
162+
return json.loads(a)
163+
elif fmt == 'hex': # hex: parse and compare binary data
164+
return binascii.a2b_hex(a.strip())
165+
else:
166+
raise NotImplementedError("Don't know how to compare %s" % fmt)
167+
168+
if __name__ == '__main__':
169+
main()

0 commit comments

Comments
 (0)