Extract common test cases and patterns used by both SSZ and JSON

This commit is contained in:
Zahary Karadjov 2019-08-02 11:52:28 +03:00
parent 8e907c46fc
commit 897a6638cf
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
2 changed files with 233 additions and 51 deletions

View File

@ -1,10 +1,19 @@
import
unittest, times, typetraits,
unittest, times, typetraits, random, strutils, options, sets,
faststreams/input_stream,
../object_serialization
../../serialization, ../object_serialization
type
Transaction = object
Meter* = distinct int
Mile* = distinct int
Simple* = object
x*: int
y*: string
distance*: Meter
ignored*: int
Transaction* = object
amount: int
time: DateTime
sender: string
@ -19,6 +28,9 @@ type
b: string
f: Foo
ListOfLists = object
lists: seq[ListOfLists]
# Baz should use custom serialization
# The `i` field should be multiplied by two while deserializing and
# `ignored` field should be set to 10
@ -29,21 +41,69 @@ type
NoExpectedResult = distinct int
ObjectKind* = enum
A
B
CaseObject* = object
case kind: ObjectKind:
of A:
a*: int
other*: CaseObjectRef
else:
b*: int
CaseObjectRef* = ref CaseObject
HoldsSet* = object
a*: int
s*: HashSet[string]
HoldsOption* = object
r*: ref Simple
o*: Option[Simple]
HoldsArray* = object
data*: seq[int]
Meter.borrowSerialization int
Simple.setSerializedFields distance, x, y
proc default(T: typedesc): T = discard
template roundtripTest*(Format: type, value: auto, expectedResult: auto) =
mixin `==`
func caseObjectEquals(a, b: CaseObject): bool
test Format.name & " " & value.type.name & " roundtrip":
func `==`*(a, b: CaseObjectRef): bool =
let nils = ord(a.isNil) + ord(b.isNil)
if nils == 0:
caseObjectEquals(a[], b[])
else:
nils == 2
func caseObjectEquals(a, b: CaseObject): bool =
# TODO This is needed to work-around a Nim overload selection issue
if a.kind != b.kind: return false
case a.kind
of A:
if a.a != b.a: return false
a.other == b.other
of B:
a.b == b.b
func `==`*(a, b: CaseObject): bool =
caseObjectEquals(a, b)
template roundtripChecks*(Format: type, value: auto, expectedResult: auto) =
let v = value
let serialized = Format.encode(v)
let serialized = encode(Format, v)
checkpoint "(encoded value): " & $serialized
when not (expectedResult is NoExpectedResult):
check serialized == expectedResult
try:
let decoded = Format.decode(serialized, v.type)
let decoded = Format.decode(serialized, type(v))
checkpoint "(decoded value): " & repr(decoded)
let decodedValueMatchesOriginal = decoded == v
check decodedValueMatchesOriginal
@ -51,9 +111,128 @@ template roundtripTest*(Format: type, value: auto, expectedResult: auto) =
checkpoint "(serialization error): " & err.formatMsg("(encoded value)")
fail()
template roundtripTest*(Format: type, value: auto, expectedResult: auto) =
mixin `==`
# TODO can't use the dot operator on the next line.
test name(Format) & " " & name(type(value)) & " roundtrip":
roundtripChecks Format, value, expectedResult
template roundtripTest*(Format: type, value: auto) =
roundtripTest(Format, value, NoExpectedResult(0))
template roundtripChecks*(Format: type, value: auto) =
roundtripChecks(Format, value, NoExpectedResult(0))
proc executeRoundtripTests*(Format: type) =
mixin init, ReaderType, WriterType
type
Reader = ReaderType Format
Writer = WriterType Format
template roundtrip(val: untyped) =
mixin supports
# TODO:
# If this doesn't work reliably, it will fail too silently.
# We need to report the number of checks passed.
when supports(Format, type(val)):
roundtripChecks(Format, val)
suite(name(Format) & " generic roundtrip tests"):
test "simple values":
template intTests(T: untyped) =
roundtrip low(T)
roundtrip high(T)
for i in 0..1000:
roundtrip rand(low(T)..high(T))
when false:
intTests int8
intTests int16
intTests int32
intTests int64
intTests uint8
intTests uint16
intTests uint32
intTests uint64
roundtrip ""
roundtrip "a"
roundtrip repeat("a",1000)
roundtrip repeat("a",100000)
roundtrip @[1, 2, 3, 4]
roundtrip newSeq[string]()
roundtrip @["a", "", "b", "cd"]
roundtrip @["", ""]
roundtrip true
roundtrip false
roundtrip ObjectKind.A
roundtrip ObjectKind.B
test "objects":
var b = Bar(b: "abracadabra",
f: Foo(x: 5'u64, y: "hocus pocus", z: @[100, 200, 300]))
roundtrip b
when false:
# TODO: This requires the test suite of each format to implement
# support for the DateTime type.
var t = Transaction(time: now(), amount: 1000, sender: "Alice", receiver: "Bob")
roundtrip t
when false and supports(Format, Baz):
# TODO: Specify the custom serialization required for the `Baz` type
# and give it a more proper name. The custom serialization demands
# that the `ignored` field is populated with a value depending on
# the `i` value. `i` itself is doubled on deserialization.
var origVal = Baz(f: Foo(x: 10'u64, y: "y", z: @[]), ignored: 5)
bytes = Format.encode(origVal)
var restored = Format.decode(bytes, Baz)
check:
origVal.f.x == restored.f.x
origVal.f.i == restored.f.i div 2
origVal.f.y.len == restored.f.y.len
restored.ignored == 10
test "case objects":
var
c1 = CaseObjectRef(kind: B, b: 100)
c2 = CaseObjectRef(kind: A, a: 80, other: CaseObjectRef(kind: B))
c3 = CaseObject(kind: A, a: 60, other: nil)
roundtrip c1
roundtrip c2
roundtrip c3
test "lists":
var
l1 = ListOfLists()
l2 = ListOfLists(lists: @[])
l3 = ListOfLists(lists: @[
ListOfLists(lists: @[
ListOfLists(),
ListOfLists(),
]),
ListOfLists(lists: @[
ListOfLists(lists: @[ListOfLists()]),
ListOfLists(lists: @[ListOfLists(lists: @[])])])
])
roundtrip l1
roundtrip l2
roundtrip l3
test "sets":
var s1 = toSet([1, 2, 3, 1, 4, 2])
var s2 = HoldsSet(a: 100, s: toSet(["a", "b", "c"]))
roundtrip s1
roundtrip s2
proc executeReaderWriterTests*(Format: type) =
mixin init, ReaderType, WriterType
@ -95,36 +274,6 @@ proc executeReaderWriterTests*(Format: type) =
findFieldReader(bazFields[], "ignored", pos) == nil
findFieldReader(bazFields[], "some_other_name", pos) == nil
test "Encoding and decoding an object":
var originalBar = Bar(b: "abracadabra",
f: Foo(x: 5'u64, y: "hocus pocus", z: @[100, 200, 300]))
executeRoundtripTests(Format)
var bytes = Format.encode(originalBar)
var s = memoryStream(bytes)
var reader = Reader.init(s)
var restoredBar = reader.readValue(Bar)
check:
originalBar == restoredBar
when false:
var t1 = Transaction(time: now(), amount: 1000, sender: "Alice", receiver: "Bob")
bytes = Format.encode(t1)
var t2 = Format.decode(bytes, Transaction)
check:
t2.time == default(DateTime)
t2.sender == "Alice"
t2.receiver == "Bob"
t2.amount == 1000
var origVal = Baz(f: Foo(x: 10'u64, y: "y", z: @[]), ignored: 5)
bytes = Format.encode(origVal)
var restored = Format.decode(bytes, Baz)
check:
origVal.f.x == restored.f.x
origVal.f.i == restored.f.i div 2
origVal.f.y.len == restored.f.y.len
restored.ignored == 10

View File

@ -0,0 +1,33 @@
when defined(serialization_tracing):
var tracingEnabled* = false
## TODO
## Implement a tracing context object that will be
## able to track down which object fields are currently
## entered. It will print the debug output in a form
## that can be easily collapsed with indentation-based
## outlining in most text editors.
func isTracingEnabled: bool =
# TODO this is a work-around for the lack of working
# `{.noSideEffect.}:` override in Nim 0.19.6.
{.emit: "`result` = `tracingEnabled`;".}
template traceSerialization*(args: varargs[untyped]) =
## `traceSerialization` can be used to capture precise
## traces of the serialization and deserialization of
## complex formats.
if isTracingEnabled():
debugEcho args
template trs*(args: varargs[untyped]) =
## `trs` is shorter form for "trace serialization"
## that's easy to write during active development
## and easy to replace with `traceSerialization`
## once your library is complete :)
traceSerialization(args)
else:
template traceSerialization*(args: varargs[untyped]) = discard
template trs*(args: varargs[untyped]) = discard