nimbus-eth2/tests/official/test_fixture_ssz_static.nim

216 lines
6.5 KiB
Nim

# beacon_chain
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
os, strutils, strformat, tables, unittest, sequtils, typetraits,
# Status libs
stew/[byteutils, bitseqs], nimcrypto/hash,
serialization/testing/tracing,
json_serialization, json_serialization/lexer,
# Beacon chain internals
../../beacon_chain/ssz,
../../beacon_chain/spec/[datatypes, validator, digest, crypto],
# Test utilities
../testutil,
./fixtures_utils
const
failFast = defined(debug) and false
traceOnFailure = defined(debug)
type
SpecObject[T] = ref object of RootObj
obj: ref T
SszStaticTest* = object
obj: RootRef
objType, objJsonRepr: string
expectedBytes: seq[byte]
expectedRootHash, expectedSigHash: Eth2Digest
hasSigHash: bool
line: int
ReaderProc = proc(r: var JsonReader): RootRef {.cdecl, gcsafe.}
TestingProc = proc(file: string, test: SszStaticTest) {.cdecl, gcsafe.}
SpecTypeVtable = object
reader: ReaderProc
tester: TestingProc
let testsDir = JsonTestsDir / "ssz_static" / "core"
let minDevTestFile = getTempDir() / "minimal_ssz_test.json"
var specTypesRTTI = initTable[string, SpecTypeVtable]()
proc readerImpl[T](r: var JsonReader): RootRef {.cdecl, gcsafe.} =
var res = SpecObject[T](obj: new T)
res.obj[] = r.readValue(T)
RootRef(res)
# TODO:
# Fun fact: With mainnet settings, the BeaconState object
# is too large to safely exist as a stack variable. The
# `testerImpl` procedure below will trigger a segmentation
# fault on its very first line because of it.
#
# To work-around this issue, this file uses ref objects
# to store the loaded test cases, but we must compare them
# by value:
template valuesAreEqual[T](a, b: ref T): bool =
a[] == b[]
template valuesAreEqual[T](a, b: T): bool =
a == b
template `$`(x: ref auto): string =
$(x[])
proc readSszValueRef*(input: openarray[byte], T: type): ref T =
new result
result[] = readSszValue(input, T)
proc testerImpl[T](path: string, sszTest: SszStaticTest) {.cdecl, gcsafe.} =
doAssert sszTest.obj != nil
var obj = SpecObject[T](sszTest.obj)
test &"test case on line {sszTest.line}":
template execTest(testOpName, testOp, expectedRes) =
let ourRes = testOp
let success = valuesAreEqual(ourRes, expectedRes)
if not success and traceOnFailure:
{.gcsafe.}:
echo "====== ", testOpName, " failed ", path, ":", sszTest.line
echo " our result:"
echo " ", ourRes
echo " expected result:"
echo " ", expectedRes
when defined(serialization_tracing):
tracingEnabled = true
discard testOp
tracingEnabled = false
echo "======================================================"
if failFast: quit 1
# TODO BEWARE: Passing the boolean expression to `check` directly
# will trigger a Nim compilation bomb. This is most likely caused
# by a mis-behaving generics instantiations cache when a function
# is explicitly instantiated to get its address.
# There is a recursive instantiation loop of system's `$` operator.
check success
execTest "serialization",
(let ourBytes = SSZ.encode(obj.obj[]); ourBytes),
sszTest.expectedBytes
execTest "root hash check",
hash_tree_root(obj.obj[]),
sszTest.expectedRootHash
when hasSigningRoot(T):
doAssert sszTest.hasSigHash
execTest "sig hash check",
signingRoot(obj.obj[]),
sszTest.expectedSigHash
execTest "roundtrip",
readSszValueRef(sszTest.expectedBytes, T),
obj.obj
template addSpecTypeRTTI(T: type) =
var reader = readerImpl[T]
var tester = testerImpl[T]
specTypesRTTI.add(T.name, SpecTypeVtable(reader: reader,
tester: tester))
foreachSpecType(addSpecTypeRTTI)
proc runTest(path: string, test: SszStaticTest) =
if test.objType != "Unsupported":
specTypesRTTI[test.objType].tester(path, test)
proc advanceToClosingBrace(lexer: var JsonLexer, openedBraces = 1) =
var closedBraces = 0
while closedBraces < openedBraces:
while lexer.tok notin {tkCurlyLe, tkCurlyRi}:
lexer.next
if lexer.tok == tkCurlyLe:
dec closedBraces
else:
inc closedBraces
lexer.next
proc readValue*(r: var JsonReader, result: var SszStaticTest) {.gcsafe.} =
r.skipToken tkCurlyLe
if r.lexer.tok != tkString:
r.raiseUnexpectedToken(etString)
var reader: ReaderProc
let key = r.lexer.strVal
{.gcsafe.}:
if not specTypesRTTI.hasKey(key):
result.objType = "Unsupported"
r.lexer.advanceToClosingBrace
return
result.objType = key
result.line = r.lexer.line
reader = specTypesRTTI[key].reader
r.lexer.next
r.skipToken tkColon
r.skipToken tkCurlyLe
while r.lexer.tok == tkString:
# TODO: I was hit by a very nasty Nim bug here.
# If you use `let` on the next line, the variable will be
# aliased to `r.lexer.strVar` instead of being copied.
# This will create problems, because the value is modified
# on the next line.
var field = r.lexer.strVal
r.lexer.next
r.skipToken tkColon
case field
of "value":
result.obj = reader(r)
of "serialized":
result.expectedBytes = hexToSeqByte r.readValue(string)
of "root":
result.expectedRootHash = Eth2Digest.fromHex r.readValue(string)
of "signing_root":
result.expectedSigHash = Eth2Digest.fromHex r.readValue(string)
result.hasSigHash = true
else:
r.raiseUnexpectedField(field, type(result).name)
if r.lexer.tok == tkComma:
r.lexer.next()
else:
break
r.skipToken tkCurlyRi
r.skipToken tkCurlyRi
when failFast:
# This will produce faster failures in debug builds
{.gcsafe.}: runTest result
proc executeSuite(path: string) =
let sszSuite = path.parseTests SszStaticTest
suite &"{path}: {sszSuite.title}":
for sszTest in sszSuite.test_cases:
runTest path, sszTest
if fileExists(minDevTestFile):
executeSuite minDevTestFile
for kind, path in walkDir(testsDir):
if kind notin {pcFile, pcLinkToFile}: continue
if const_preset in path:
executeSuite path