Changeset View
Changeset View
Standalone View
Standalone View
test/util/bitcoin-util-test.py
Show All 16 Lines | |||||
import os | import os | ||||
import pprint | import pprint | ||||
import subprocess | import subprocess | ||||
import sys | import sys | ||||
def main(): | def main(): | ||||
config = configparser.ConfigParser() | config = configparser.ConfigParser() | ||||
config.read_file(open(os.path.join(os.path.dirname( | config.read_file( | ||||
__file__), "../config.ini"), encoding="utf8")) | open(os.path.join(os.path.dirname(__file__), "../config.ini"), encoding="utf8") | ||||
) | |||||
parser = argparse.ArgumentParser(description=__doc__) | parser = argparse.ArgumentParser(description=__doc__) | ||||
parser.add_argument('-v', '--verbose', action='store_true') | parser.add_argument("-v", "--verbose", action="store_true") | ||||
args = parser.parse_args() | args = parser.parse_args() | ||||
verbose = args.verbose | verbose = args.verbose | ||||
if verbose: | if verbose: | ||||
level = logging.DEBUG | level = logging.DEBUG | ||||
else: | else: | ||||
level = logging.ERROR | level = logging.ERROR | ||||
formatter = '%(asctime)s - %(levelname)s - %(message)s' | formatter = "%(asctime)s - %(levelname)s - %(message)s" | ||||
# Add the format/level to the logger | # Add the format/level to the logger | ||||
logging.basicConfig(format=formatter, level=level) | logging.basicConfig(format=formatter, level=level) | ||||
bctester(os.path.join(config["environment"]["SRCDIR"], "test", | bctester( | ||||
"util", "data"), "bitcoin-util-test.json", config["environment"]) | os.path.join(config["environment"]["SRCDIR"], "test", "util", "data"), | ||||
"bitcoin-util-test.json", | |||||
config["environment"], | |||||
) | |||||
def bctester(testDir, input_basename, buildenv): | def bctester(testDir, input_basename, buildenv): | ||||
""" Loads and parses the input file, runs all tests and reports results""" | """Loads and parses the input file, runs all tests and reports results""" | ||||
input_filename = os.path.join(testDir, input_basename) | input_filename = os.path.join(testDir, input_basename) | ||||
raw_data = open(input_filename, encoding="utf8").read() | raw_data = open(input_filename, encoding="utf8").read() | ||||
input_data = json.loads(raw_data) | input_data = json.loads(raw_data) | ||||
failed_testcases = [] | failed_testcases = [] | ||||
for testObj in input_data: | for testObj in input_data: | ||||
try: | try: | ||||
Show All 16 Lines | |||||
def bctest(testDir, testObj, buildenv): | def bctest(testDir, testObj, buildenv): | ||||
"""Runs a single test, comparing output and RC to expected output and RC. | """Runs a single test, comparing output and RC to expected output and RC. | ||||
Raises an error if input can't be read, executable fails, or output/RC | Raises an error if input can't be read, executable fails, or output/RC | ||||
are not as expected. Error is caught by bctester() and reported. | are not as expected. Error is caught by bctester() and reported. | ||||
""" | """ | ||||
# Get the exec names and arguments | # Get the exec names and arguments | ||||
execprog = os.path.join( | execprog = os.path.join( | ||||
buildenv["BUILDDIR"], "src", testObj["exec"] + buildenv["EXEEXT"]) | buildenv["BUILDDIR"], "src", testObj["exec"] + buildenv["EXEEXT"] | ||||
execargs = testObj['args'] | ) | ||||
execargs = testObj["args"] | |||||
execrun = [execprog] + execargs | execrun = [execprog] + execargs | ||||
if buildenv["EMULATOR"]: | if buildenv["EMULATOR"]: | ||||
execrun = [buildenv["EMULATOR"]] + execrun | execrun = [buildenv["EMULATOR"]] + execrun | ||||
# Read the input data (if there is any) | # Read the input data (if there is any) | ||||
stdinCfg = None | stdinCfg = None | ||||
inputData = None | inputData = None | ||||
if "input" in testObj: | if "input" in testObj: | ||||
filename = os.path.join(testDir, testObj["input"]) | filename = os.path.join(testDir, testObj["input"]) | ||||
inputData = open(filename, encoding="utf8").read() | inputData = open(filename, encoding="utf8").read() | ||||
stdinCfg = subprocess.PIPE | stdinCfg = subprocess.PIPE | ||||
# Read the expected output data (if there is any) | # Read the expected output data (if there is any) | ||||
outputFn = None | outputFn = None | ||||
outputData = None | outputData = None | ||||
outputType = None | outputType = None | ||||
if "output_cmp" in testObj: | if "output_cmp" in testObj: | ||||
outputFn = testObj['output_cmp'] | outputFn = testObj["output_cmp"] | ||||
# output type from file extension (determines how to compare) | # output type from file extension (determines how to compare) | ||||
outputType = os.path.splitext(outputFn)[1][1:] | outputType = os.path.splitext(outputFn)[1][1:] | ||||
try: | try: | ||||
outputData = open(os.path.join(testDir, outputFn), | outputData = open(os.path.join(testDir, outputFn), encoding="utf8").read() | ||||
encoding="utf8").read() | |||||
except OSError: | except OSError: | ||||
logging.error(f"Output file {outputFn} can not be opened") | logging.error(f"Output file {outputFn} can not be opened") | ||||
raise | raise | ||||
if not outputData: | if not outputData: | ||||
logging.error(f"Output data missing for {outputFn}") | logging.error(f"Output data missing for {outputFn}") | ||||
raise Exception | raise Exception | ||||
if not outputType: | if not outputType: | ||||
logging.error( | logging.error(f"Output file {outputFn} does not have a file extension") | ||||
f"Output file {outputFn} does not have a file extension") | |||||
raise Exception | raise Exception | ||||
# Run the test | # Run the test | ||||
proc = subprocess.Popen(execrun, stdin=stdinCfg, stdout=subprocess.PIPE, | proc = subprocess.Popen( | ||||
stderr=subprocess.PIPE, universal_newlines=True) | execrun, | ||||
stdin=stdinCfg, | |||||
stdout=subprocess.PIPE, | |||||
stderr=subprocess.PIPE, | |||||
universal_newlines=True, | |||||
) | |||||
try: | try: | ||||
outs = proc.communicate(input=inputData) | outs = proc.communicate(input=inputData) | ||||
except OSError: | except OSError: | ||||
logging.error(f"OSError, Failed to execute {execprog}") | logging.error(f"OSError, Failed to execute {execprog}") | ||||
raise | raise | ||||
if outputData: | if outputData: | ||||
data_mismatch, formatting_mismatch = False, False | data_mismatch, formatting_mismatch = False, False | ||||
# Parse command output and expected output | # Parse command output and expected output | ||||
try: | try: | ||||
a_parsed = parse_output(outs[0], outputType) | a_parsed = parse_output(outs[0], outputType) | ||||
except Exception as e: | except Exception as e: | ||||
logging.error( | logging.error(f"Error parsing command output as {outputType}: {e}") | ||||
f'Error parsing command output as {outputType}: {e}') | |||||
raise | raise | ||||
try: | try: | ||||
b_parsed = parse_output(outputData, outputType) | b_parsed = parse_output(outputData, outputType) | ||||
except Exception as e: | except Exception as e: | ||||
logging.error('Error parsing expected output {} as {}: {}'.format( | logging.error( | ||||
outputFn, outputType, e)) | f"Error parsing expected output {outputFn} as {outputType}: {e}" | ||||
) | |||||
raise | raise | ||||
# Compare data | # Compare data | ||||
if a_parsed != b_parsed: | if a_parsed != b_parsed: | ||||
logging.error(f"Output data mismatch for {outputFn} (format {outputType})") | logging.error(f"Output data mismatch for {outputFn} (format {outputType})") | ||||
data_mismatch = True | data_mismatch = True | ||||
# Compare formatting | # Compare formatting | ||||
if outs[0] != outputData: | if outs[0] != outputData: | ||||
error_message = f"Output formatting mismatch for {outputFn}:\n" | error_message = f"Output formatting mismatch for {outputFn}:\n" | ||||
error_message += "".join(difflib.context_diff(outputData.splitlines(True), | error_message += "".join( | ||||
outs[0].splitlines( | difflib.context_diff( | ||||
True), | outputData.splitlines(True), | ||||
outs[0].splitlines(True), | |||||
fromfile=outputFn, | fromfile=outputFn, | ||||
tofile="returned")) | tofile="returned", | ||||
) | |||||
) | |||||
logging.error(error_message) | logging.error(error_message) | ||||
formatting_mismatch = True | formatting_mismatch = True | ||||
assert not data_mismatch and not formatting_mismatch | assert not data_mismatch and not formatting_mismatch | ||||
# Compare the return code to the expected return code | # Compare the return code to the expected return code | ||||
wantRC = 0 | wantRC = 0 | ||||
if "return_code" in testObj: | if "return_code" in testObj: | ||||
wantRC = testObj['return_code'] | wantRC = testObj["return_code"] | ||||
if proc.returncode != wantRC: | if proc.returncode != wantRC: | ||||
logging.error(f"Return code mismatch for {outputFn}") | logging.error(f"Return code mismatch for {outputFn}") | ||||
raise Exception | raise Exception | ||||
if "error_txt" in testObj: | if "error_txt" in testObj: | ||||
want_error = testObj["error_txt"] | want_error = testObj["error_txt"] | ||||
# Compare error text | # Compare error text | ||||
# TODO: ideally, we'd compare the strings exactly and also assert | # TODO: ideally, we'd compare the strings exactly and also assert | ||||
# That stderr is empty if no errors are expected. However, bitcoin-tx | # That stderr is empty if no errors are expected. However, bitcoin-tx | ||||
# emits DISPLAY errors when running as a windows application on | # emits DISPLAY errors when running as a windows application on | ||||
# linux through wine. Just assert that the expected error text appears | # linux through wine. Just assert that the expected error text appears | ||||
# somewhere in stderr. | # somewhere in stderr. | ||||
if want_error not in outs[1]: | if want_error not in outs[1]: | ||||
logging.error("Error mismatch:\n" + "Expected: " + | logging.error( | ||||
want_error + "\nReceived: " + outs[1].rstrip()) | f"Error mismatch:\nExpected: {want_error}\nReceived: {outs[1].rstrip()}" | ||||
) | |||||
raise Exception | raise Exception | ||||
def parse_output(a, fmt): | def parse_output(a, fmt): | ||||
"""Parse the output according to specified format. | """Parse the output according to specified format. | ||||
Raise an error if the output can't be parsed.""" | Raise an error if the output can't be parsed.""" | ||||
if fmt == 'json': # json: compare parsed data | if fmt == "json": # json: compare parsed data | ||||
return json.loads(a) | return json.loads(a) | ||||
elif fmt == 'hex': # hex: parse and compare binary data | elif fmt == "hex": # hex: parse and compare binary data | ||||
return bytes.fromhex(a.strip()) | return bytes.fromhex(a.strip()) | ||||
else: | else: | ||||
raise NotImplementedError(f"Don't know how to compare {fmt}") | raise NotImplementedError(f"Don't know how to compare {fmt}") | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
main() | main() |