allow tests to state that certain fields' values change at certain

blocks (i.e. value hash) in ways that are inconsistent with prior
releases, but still correct
This commit is contained in:
Jude Nelson
2016-09-29 12:25:09 -04:00
parent 1a629e382c
commit dcdb9a8336

View File

@@ -74,9 +74,12 @@ def find_serialization_outputs( test_output_path ):
Find the SERIALIZE outputs from the given test log output.
Parse them and return the parsed list.
Add '__block_height__' to each item (the height at which the serialization was found)
Add '__ignore_fields__' list to each item for fields we can ignore at that height (as well as the versions for which it applies)
"""
ret = []
curr_height = 0
ignore_fields = {}
with open(test_output_path, "r") as f:
while True:
line = f.readline()
@@ -99,19 +102,55 @@ def find_serialization_outputs( test_output_path ):
pass
line = line.strip()
if " SERIALIZE: " not in line:
continue
payload_bin = extract_serialize_payload( line )
payload = parse_serialize_payload( payload_bin )
if line.startswith("BLOCKSTACK_SERIALIZATION_CHECK_IGNORE"):
# format: BLOCKSTACK_SERIALIZATION_CHECK_IGNORE field
# NOTE: since this gets printed *before* the "Snapshotting block", we'll have to use curr_height+1
parts = line.split()
assert len(parts) == 2
field = parts[1]
payload['__block_height__'] = curr_height
ret.append( payload )
if not ignore_fields.has_key(curr_height+1):
ignore_fields[curr_height+1] = []
ignore_fields[curr_height+1].append(field)
if " SERIALIZE: " in line:
# finished this block's data
payload_bin = extract_serialize_payload( line )
payload = parse_serialize_payload( payload_bin )
payload['__block_height__'] = curr_height
payload['__ignore_fields__'] = ignore_fields.get(curr_height, [])[:]
ret.append( payload )
return ret
def serialization_eq( height, serialization_1, serialization_2, ignore=['txid','consensus_hash','name_consensus_hash','name_hash', 'vtxindex'] ):
def find_ignore_serialization_fields( test_output_path ):
"""
Find any occurrences of "SERIALIZATION_FIELD_IGNORE ..."
and return them as a CSV
"""
ret = []
with open(test_output_path, "r") as f:
while True:
line = f.readline()
if len(line) == 0:
break
line = line.strip()
if line.startswith("SERIALIZATION_CHECK_IGNORE"):
parts = line.split(" ")
assert len(parts) == 2
ignore = parts[1]
ret.append(ignore)
return ret
def serialization_eq( height, serialization_1, serialization_2, ignore=[] ):
"""
Given two parsed serialization payloads, verify that they represent the same data
(ignoring fields given in @ignore)
@@ -164,58 +203,66 @@ def group_by_block_height( serializations ):
return ret
def compare_serializations( test_output_1, test_output_2 ):
def compare_serializations( test_output_old, test_output_new ):
"""
Given the paths to two different test outputs, verify
that their sequence of SERIALIZEs match (up to txid).
"""
serializations_1 = find_serialization_outputs( test_output_1 )
serializations_2 = find_serialization_outputs( test_output_2 )
serializations_old = find_serialization_outputs( test_output_old )
serializations_new = find_serialization_outputs( test_output_new )
serialization_ignore = ['txid','consensus_hash','name_consensus_hash','name_hash','vtxindex']
rc = True
if len(serializations_1) != len(serializations_2):
print >> sys.stderr, " Mismatched number of serializations (%s != %s)" % (len(serializations_1), len(serializations_2))
if len(serializations_old) != len(serializations_new):
print >> sys.stderr, " Mismatched number of serializations (%s != %s)" % (len(serializations_old), len(serializations_new))
return False
# group by block height (since bitcoind can re-order transactions)
block_serializations_1 = group_by_block_height( serializations_1 )
block_serializations_2 = group_by_block_height( serializations_2 )
block_serializations_old = group_by_block_height( serializations_old )
block_serializations_new = group_by_block_height( serializations_new )
for height in sorted(block_serializations_1.keys()):
if not block_serializations_2.has_key(height):
for height in sorted(block_serializations_old.keys()):
if not block_serializations_new.has_key(height):
print >> sys.stderr, " Missing block height %s in second log" % height
return False
for height in sorted(block_serializations_2.keys()):
if not block_serializations_1.has_key(height):
for height in sorted(block_serializations_new.keys()):
if not block_serializations_old.has_key(height):
print >> sys.stderr, " Missing block height %s in first log" % height
return False
for height in sorted(block_serializations_1.keys()):
sh1 = block_serializations_1[height]
sh2 = block_serializations_2[height]
for height in sorted(block_serializations_old.keys()):
s_old = block_serializations_old[height]
s_new = block_serializations_new[height]
if len(sh1) != len(sh2):
print >> sys.stderr, " Mismatched number of serializations at block %s (%s != %s)" % (height, len(sh1), len(sh2))
if len(s_old) != len(s_new):
print >> sys.stderr, " Mismatched number of serializations at block %s (%s != %s)" % (height, len(s_old), len(s_new))
return False
matched = False
err = None
for s1 in sh1:
for s1 in s_old:
# has to match one serialization in the second listing
# order doesn't matter, since bitcoind reorders them anyway
for s2 in sh2:
res, err = serialization_eq( height, s1, s2 )
for s2 in s_new:
# serializations to ignore (use the fields from the new log to ignore fields in the old log)
ignore = serialization_ignore[:]
ignore += s2['__ignore_fields__']
res, err = serialization_eq( height, s1, s2, ignore=ignore )
if res:
matched = True
sh2.remove(s2)
s_new.remove(s2)
break
if not matched:
# soldier on here so we can print all mismatches
print >> sys.stderr, " Mismatched serializations in block %s" % height
print >> sys.stderr, err['error']
return False
rc = False
return True
return rc
def is_test_successful( test_output ):
@@ -235,23 +282,46 @@ def is_test_successful( test_output ):
return False
def skip_check( test_output ):
"""
Should we skip the serialization test?
i.e. is it expected to fail?
"""
with open(test_output, "r") as f:
while True:
line = f.readline()
if len(line) == 0:
break
line = line.strip()
if line == "BLOCKSTACK_SERIALIZATION_CHANGE_BEHAVIOR":
# this is a known breaking change
return True
return False
if __name__ == "__main__":
try:
test_output_1 = sys.argv[1]
test_output_2 = sys.argv[2]
test_output_old = sys.argv[1]
test_output_new = sys.argv[2]
except:
print >> sys.stderr, "Usage: %s TEST_OUTPUT_1 TEST_OUTPUT_2" % sys.argv[0]
print >> sys.stderr, "Usage: %s TEST_OUTPUT_OLD TEST_OUTPUT_NEW" % sys.argv[0]
sys.exit(1)
if not is_test_successful( test_output_1 ):
print >> sys.stderr, " %s is a failed test" % test_output_1
if not is_test_successful( test_output_old ):
print >> sys.stderr, " %s is a failed old test" % test_output_old
sys.exit(2)
if not is_test_successful( test_output_new ):
print >> sys.stderr, " %s is a failed new test" % test_output_new
sys.exit(1)
if not is_test_successful( test_output_2 ):
print >> sys.stderr, " %s is a failed test" % test_output_2
sys.exit(1)
if skip_check( test_output_new ):
print >> sys.stderr, " %s is a breaking chnage" % test_output_new
sys.exit(2)
res = compare_serializations( test_output_1, test_output_2 )
res = compare_serializations( test_output_old, test_output_new )
if res:
sys.exit(0)
else: