NimYAML/test/tserialization.nim
Vindaar ebe4201cbc replace assertStringEqual by version checking for content and len
Now check for content *and* length of the strings in
`tserialization`. Also adds a test case for the case with which the
old version would have failed (one string longer than the other, thus
being different).

This version tries to keep the error reporting mechanism the same as
the old version.
2018-10-12 16:00:39 +02:00

668 lines
20 KiB
Nim

# NimYAML - YAML implementation in Nim
# (c) Copyright 2015 Felix Krause
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
import "../yaml"
import unittest, strutils, tables, times, math, os
type
MyTuple = tuple
str: string
i: int32
b: bool
TrafficLight = enum
tlGreen, tlYellow, tlRed
Person = object
firstnamechar: char
surname: string
age: int32
Node = object
value: string
next: ref Node
BetterInt = distinct int
AnimalKind = enum
akCat, akDog
Animal = object
name: string
case kind: AnimalKind
of akCat:
purringIntensity: int
of akDog: barkometer: int
DumbEnum = enum
deA, deB, deC, deD
NonVariantWithTransient = object
a, b, c, d: string
VariantWithTransient = object
gStorable, gTemporary: string
case kind: DumbEnum
of deA, deB:
cStorable, cTemporary: string
of deC:
alwaysThere: int
of deD:
neverThere: int
WithDefault = object
a, b, c, d: string
WithIgnoredField = object
x, y: int
markAsTransient(NonVariantWithTransient, a)
markAsTransient(NonVariantWithTransient, c)
markAsTransient(VariantWithTransient, gTemporary)
markAsTransient(VariantWithTransient, cTemporary)
markAsTransient(VariantWithTransient, neverThere)
setDefaultValue(WithDefault, b, "b")
setDefaultValue(WithDefault, d, "d")
ignoreInputKey(WithIgnoredField, "z")
proc `$`(v: BetterInt): string {.borrow.}
proc `==`(left, right: BetterInt): bool {.borrow.}
setTagUri(TrafficLight, "!tl")
setTagUri(Node, "!example.net:Node")
setTagUri(BetterInt, "!test:BetterInt")
const yamlDirs = "%YAML 1.2\n%TAG !n! tag:nimyaml.org,2016:\n--- "
proc representObject*(value: BetterInt, ts: TagStyle = tsNone,
c: SerializationContext, tag: TagId) {.raises: [].} =
var
val = $value
i = val.len - 3
while i > 0:
val.insert("_", i)
i -= 3
c.put(scalarEvent(val, tag, yAnchorNone))
proc constructObject*(s: var YamlStream, c: ConstructionContext,
result: var BetterInt)
{.raises: [YamlConstructionError, YamlStreamError].} =
constructScalarItem(s, item, BetterInt):
result = BetterInt(parseBiggestInt(item.scalarContent) + 1)
template assertStringEqual(expected, actual: string, file = stdout) =
if expected != actual:
# if they are unequal, walk through the strings and check each
# character for a better error message
if expected.len != actual.len:
file.write "Expected and actual string's length differs.\n"
file.write "Expected length: ", expected.len, "\n"
file.write "Actual length: ", actual.len, "\n"
# check length up to smaller of the two strings
for i in countup(0, min(expected.high, actual.high)):
if expected[i] != actual[i]:
file.write "string mismatch at character #", i, "(expected:\'",
expected[i], "\', was \'", actual[i], "\'):\n"
file.write "expected:\n", expected, "\nactual:\n", actual, "\n"
assert(false)
# if we haven't raised an assertion error here, the problem is that
# one string is longer than the other
let minInd = min(expected.len, actual.len) # len instead of high to continue
# after shorter string
if expected.high > actual.high:
file.write "Expected continues with: '", expected[minInd .. ^1], "'"
assert false
else:
file.write "Actual continues with: '", actual[minInd .. ^1], "'"
assert false
template expectConstructionError(li, co: int, message: string, body: typed) =
try:
body
echo "Expected YamlConstructionError, but none was raised!"
fail()
except YamlConstructionError:
let e = (ref YamlConstructionError)(getCurrentException())
doAssert li == e.line, "Expected error line " & $li & ", was " & $e.line
doAssert co == e.column, "Expected error column " & $co & ", was " & $e.column
doAssert message == e.msg, "Expected error message \n" & escape(message) &
", got \n" & escape(e.msg)
proc newNode(v: string): ref Node =
new(result)
result.value = v
result.next = nil
let blockOnly = defineOptions(style=psBlockOnly)
suite "Serialization":
test "Internal string assert":
let s1 = "foo"
let s2 = "foobar"
let fname = "dump.txt"
let f: File = open(fname, fmWrite)
try:
assertStringEqual(s1, s2, file=f)
except AssertionError:
discard
finally:
f.close()
removeFile(fname)
test "Load integer without fixed length":
var input = "-4247"
var result: int
load(input, result)
assert result == -4247, "result is " & $result
input = $(int64(int32.high) + 1'i64)
var gotException = false
try: load(input, result)
except: gotException = true
assert gotException, "Expected exception, got none."
test "Dump integer without fixed length":
var input = -4247
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n\"-4247\"", output
when sizeof(int) == sizeof(int64):
input = int(int32.high) + 1
var gotException = false
try: output = dump(input, tsNone, asTidy, blockOnly)
except: gotException = true
assert gotException, "Expected exception, got none."
test "Load Hex byte (0xFF)":
let input = "0xFF"
var result: byte
load(input, result)
assert(result == 255)
test "Load Hex byte (0xC)":
let input = "0xC"
var result: byte
load(input, result)
assert(result == 12)
test "Load Octal byte (0o14)":
let input = "0o14"
var result: byte
load(input, result)
assert(result == 12)
test "Load byte (14)":
let input = "14"
var result: byte
load(input, result)
assert(result == 14)
test "Load Hex int (0xFF)":
let input = "0xFF"
var result: int
load(input, result)
assert(result == 255)
test "Load Hex int (0xC)":
let input = "0xC"
var result: int
load(input, result)
assert(result == 12)
test "Load Octal int (0o14)":
let input = "0o14"
var result: int
load(input, result)
assert(result == 12)
test "Load int (14)":
let input = "14"
var result: int
load(input, result)
assert(result == 14)
test "Load floats":
let input = "[6.8523015e+5, 685.230_15e+03, 685_230.15, -.inf, .NaN]"
var result: seq[float]
load(input, result)
for i in 0..2:
assert result[i] == 6.8523015e+5
assert result[3] == NegInf
assert classify(result[4]) == fcNan
test "Load timestamps":
let input = "[2001-12-15T02:59:43.1Z, 2001-12-14t21:59:43.10-05:00, 2001-12-14 21:59:43.10-5]"
var result: seq[Time]
load(input, result)
assert result.len() == 3
# currently, there is no good way of checking the result content, because
# the parsed Time may have any timezone offset.
test "Load string sequence":
let input = " - a\n - b"
var result: seq[string]
load(input, result)
assert result.len == 2
assert result[0] == "a"
assert result[1] == "b"
test "Dump string sequence":
var input = @["a", "b"]
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- a\n- b", output
test "Load char set":
let input = "- a\n- b"
var result: set[char]
load(input, result)
assert result.card == 2
assert 'a' in result
assert 'b' in result
test "Dump char set":
var input = {'a', 'b'}
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- a\n- b", output
test "Load array":
let input = "- 23\n- 42\n- 47"
var result: array[0..2, int32]
load(input, result)
assert result[0] == 23
assert result[1] == 42
assert result[2] == 47
test "Dump array":
let input = [23'i32, 42'i32, 47'i32]
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- 23\n- 42\n- 47", output
test "Load Table[int, string]":
let input = "23: dreiundzwanzig\n42: zweiundvierzig"
var result: Table[int32, string]
load(input, result)
assert result.len == 2
assert result[23] == "dreiundzwanzig"
assert result[42] == "zweiundvierzig"
test "Dump Table[int, string]":
var input = initTable[int32, string]()
input[23] = "dreiundzwanzig"
input[42] = "zweiundvierzig"
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual(yamlDirs & "\n23: dreiundzwanzig\n42: zweiundvierzig",
output)
test "Load OrderedTable[tuple[int32, int32], string]":
let input = "- {a: 23, b: 42}: drzw\n- {a: 13, b: 47}: drsi"
var result: OrderedTable[tuple[a, b: int32], string]
load(input, result)
var i = 0
for key, value in result.pairs:
case i
of 0:
assert key == (a: 23'i32, b: 42'i32)
assert value == "drzw"
of 1:
assert key == (a: 13'i32, b: 47'i32)
assert value == "drsi"
else: assert false
i.inc()
test "Dump OrderedTable[tuple[int32, int32], string]":
var input = initOrderedTable[tuple[a, b: int32], string]()
input.add((a: 23'i32, b: 42'i32), "dreiundzwanzigzweiundvierzig")
input.add((a: 13'i32, b: 47'i32), "dreizehnsiebenundvierzig")
var output = dump(input, tsRootOnly, asTidy, blockOnly)
assertStringEqual(yamlDirs &
"""!n!tables:OrderedTable(tag:nimyaml.org;2016:tuple(tag:nimyaml.org;2016:system:int32;tag:nimyaml.org;2016:system:int32);tag:yaml.org;2002:str)
-
?
a: 23
b: 42
: dreiundzwanzigzweiundvierzig
-
?
a: 13
b: 47
: dreizehnsiebenundvierzig""", output)
test "Load Sequences in Sequence":
let input = " - [1, 2, 3]\n - [4, 5]\n - [6]"
var result: seq[seq[int32]]
load(input, result)
assert result.len == 3
assert result[0] == @[1.int32, 2.int32, 3.int32]
assert result[1] == @[4.int32, 5.int32]
assert result[2] == @[6.int32]
test "Dump Sequences in Sequence":
let input = @[@[1.int32, 2.int32, 3.int32], @[4.int32, 5.int32], @[6.int32]]
var output = dump(input, tsNone)
assertStringEqual yamlDirs & "\n- [1, 2, 3]\n- [4, 5]\n- [6]", output
test "Load Enum":
let input =
"!<tag:nimyaml.org,2016:system:seq(tl)>\n- !tl tlRed\n- tlGreen\n- tlYellow"
var result: seq[TrafficLight]
load(input, result)
assert result.len == 3
assert result[0] == tlRed
assert result[1] == tlGreen
assert result[2] == tlYellow
test "Dump Enum":
let input = @[tlRed, tlGreen, tlYellow]
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\n- tlRed\n- tlGreen\n- tlYellow", output
test "Load Tuple":
let input = "str: value\ni: 42\nb: true"
var result: MyTuple
load(input, result)
assert result.str == "value"
assert result.i == 42
assert result.b == true
test "Dump Tuple":
let input = (str: "value", i: 42.int32, b: true)
var output = dump(input, tsNone)
assertStringEqual yamlDirs & "\nstr: value\ni: 42\nb: y", output
test "Load Tuple - unknown field":
let input = "str: value\nfoo: bar\ni: 42\nb: true"
var result: MyTuple
expectConstructionError(2, 1, "While constructing MyTuple: Unknown field: \"foo\""):
load(input, result)
test "Load Tuple - missing field":
let input = "str: value\nb: true"
var result: MyTuple
expectConstructionError(2, 8, "While constructing MyTuple: Missing field: \"i\""):
load(input, result)
test "Load Tuple - duplicate field":
let input = "str: value\ni: 42\nb: true\nb: true"
var result: MyTuple
expectConstructionError(4, 1, "While constructing MyTuple: Duplicate field: \"b\""):
load(input, result)
test "Load Multiple Documents":
let input = "1\n---\n2"
var result: seq[int]
loadMultiDoc(input, result)
assert(result.len == 2)
assert result[0] == 1
assert result[1] == 2
test "Load Multiple Documents (Single Doc)":
let input = "1"
var result: seq[int]
loadMultiDoc(input, result)
assert(result.len == 1)
assert result[0] == 1
test "Load custom object":
let input = "firstnamechar: P\nsurname: Pan\nage: 12"
var result: Person
load(input, result)
assert result.firstnamechar == 'P'
assert result.surname == "Pan"
assert result.age == 12
test "Dump custom object":
let input = Person(firstnamechar: 'P', surname: "Pan", age: 12)
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual(yamlDirs &
"\nfirstnamechar: P\nsurname: Pan\nage: 12", output)
test "Load custom object - unknown field":
let input = " firstnamechar: P\n surname: Pan\n age: 12\n occupation: free"
var result: Person
expectConstructionError(4, 3, "While constructing Person: Unknown field: \"occupation\""):
load(input, result)
test "Load custom object - missing field":
let input = "surname: Pan\nage: 12\n "
var result: Person
expectConstructionError(3, 3, "While constructing Person: Missing field: \"firstnamechar\""):
load(input, result)
test "Load custom object - duplicate field":
let input = "firstnamechar: P\nsurname: Pan\nage: 12\nsurname: Pan"
var result: Person
expectConstructionError(4, 1, "While constructing Person: Duplicate field: \"surname\""):
load(input, result)
test "Load sequence with explicit tags":
let input = yamlDirs & "!n!system:seq(" &
"tag:yaml.org;2002:str)\n- !!str one\n- !!str two"
var result: seq[string]
load(input, result)
assert result[0] == "one"
assert result[1] == "two"
test "Dump sequence with explicit tags":
let input = @["one", "two"]
var output = dump(input, tsAll, asTidy, blockOnly)
assertStringEqual(yamlDirs & "!n!system:seq(" &
"tag:yaml.org;2002:str) \n- !!str one\n- !!str two", output)
test "Load custom object with explicit root tag":
let input =
"--- !<tag:nimyaml.org,2016:custom:Person>\nfirstnamechar: P\nsurname: Pan\nage: 12"
var result: Person
load(input, result)
assert result.firstnamechar == 'P'
assert result.surname == "Pan"
assert result.age == 12
test "Dump custom object with explicit root tag":
let input = Person(firstnamechar: 'P', surname: "Pan", age: 12)
var output = dump(input, tsRootOnly, asTidy, blockOnly)
assertStringEqual(yamlDirs &
"!n!custom:Person \nfirstnamechar: P\nsurname: Pan\nage: 12", output)
test "Load custom variant object":
let input =
"---\n- - name: Bastet\n - kind: akCat\n - purringIntensity: 7\n" &
"- - name: Anubis\n - kind: akDog\n - barkometer: 13"
var result: seq[Animal]
load(input, result)
assert result.len == 2
assert result[0].name == "Bastet"
assert result[0].kind == akCat
assert result[0].purringIntensity == 7
assert result[1].name == "Anubis"
assert result[1].kind == akDog
assert result[1].barkometer == 13
test "Dump custom variant object":
let input = @[Animal(name: "Bastet", kind: akCat, purringIntensity: 7),
Animal(name: "Anubis", kind: akDog, barkometer: 13)]
var output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & """
-
-
name: Bastet
-
kind: akCat
-
purringIntensity: 7
-
-
name: Anubis
-
kind: akDog
-
barkometer: 13""", output
test "Load custom variant object - missing field":
let input = "[{name: Bastet}, {kind: akCat}]"
var result: Animal
expectConstructionError(1, 32, "While constructing Animal: Missing field: \"purringIntensity\""):
load(input, result)
test "Load non-variant object with transient fields":
let input = "{b: b, d: d}"
var result: NonVariantWithTransient
load(input, result)
assert result.a.len == 0
assert result.b == "b"
assert result.c.len == 0
assert result.d == "d"
test "Load non-variant object with transient fields - unknown field":
let input = "{b: b, c: c, d: d}"
var result: NonVariantWithTransient
expectConstructionError(1, 9, "While constructing NonVariantWithTransient: Field \"c\" is transient and may not occur in input"):
load(input, result)
test "Dump non-variant object with transient fields":
let input = NonVariantWithTransient(a: "a", b: "b", c: "c", d: "d")
let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & "\nb: b\nd: d", output
test "Load variant object with transient fields":
let input = "[[gStorable: gs, kind: deB, cStorable: cs], [gStorable: a, kind: deD]]"
var result: seq[VariantWithTransient]
load(input, result)
assert result.len == 2
assert result[0].kind == deB
assert result[0].gStorable == "gs"
assert result[0].cStorable == "cs"
assert result[1].kind == deD
assert result[1].gStorable == "a"
test "Load variant object with transient fields":
let input = "[gStorable: gc, kind: deD, neverThere: foo]"
var result: VariantWithTransient
expectConstructionError(1, 38, "While constructing VariantWithTransient: Field \"neverThere\" is transient and may not occur in input"):
load(input, result)
test "Dump variant object with transient fields":
let input = @[VariantWithTransient(kind: deB, gStorable: "gs",
gTemporary: "gt", cStorable: "cs", cTemporary: "ct"),
VariantWithTransient(kind: deD, gStorable: "a", gTemporary: "b",
neverThere: 42)]
let output = dump(input, tsNone, asTidy, blockOnly)
assertStringEqual yamlDirs & """
-
-
gStorable: gs
-
kind: deB
-
cStorable: cs
-
-
gStorable: a
-
kind: deD""", output
test "Load object with ignored key":
let input = "[{x: 1, y: 2}, {x: 3, z: 4, y: 5}, {z: [1, 2, 3], x: 4, y: 5}]"
var result: seq[WithIgnoredField]
load(input, result)
assert result.len == 3
assert result[0].x == 1
assert result[0].y == 2
assert result[1].x == 3
assert result[1].y == 5
assert result[2].x == 4
assert result[2].y == 5
test "Load object with ignored key - unknown field":
let input = "{x: 1, y: 2, zz: 3}"
var result: WithIgnoredField
expectConstructionError(1, 16, "While constructing WithIgnoredField: Unknown field: \"zz\""):
load(input, result)
when not defined(JS):
test "Dump cyclic data structure":
var
a = newNode("a")
b = newNode("b")
c = newNode("c")
a.next = b
b.next = c
c.next = a
var output = dump(a, tsRootOnly, asTidy, blockOnly)
assertStringEqual yamlDirs & """!example.net:Node &a
value: a
next:
value: b
next:
value: c
next: *a""", output
test "Load cyclic data structure":
let input = yamlDirs & """!n!system:seq(example.net:Node)
- &a
value: a
next: &b
value: b
next: &c
value: c
next: *a
- *b
- *c
"""
var result: seq[ref Node]
try: load(input, result)
except YamlConstructionError:
let ex = (ref YamlConstructionError)(getCurrentException())
echo "line ", ex.line, ", column ", ex.column, ": ", ex.msg
echo ex.lineContent
raise ex
assert(result.len == 3)
assert(result[0].value == "a")
assert(result[1].value == "b")
assert(result[2].value == "c")
assert(result[0].next == result[1])
assert(result[1].next == result[2])
assert(result[2].next == result[0])
test "Load object with default values":
let input = "a: abc\nc: dce"
var result: WithDefault
load(input, result)
assert result.a == "abc"
assert result.b == "b"
assert result.c == "dce"
assert result.d == "d"
test "Load object with partly default values":
let input = "a: abc\nb: bcd\nc: cde"
var result: WithDefault
load(input, result)
assert result.a == "abc"
assert result.b == "bcd"
assert result.c == "cde"
assert result.d == "d"
test "Custom constructObject":
let input = "- 1\n- !test:BetterInt 2"
var result: seq[BetterInt]
load(input, result)
assert(result.len == 2)
assert(result[0] == 2.BetterInt)
assert(result[1] == 3.BetterInt)
test "Custom representObject":
let input = @[1.BetterInt, 9998887.BetterInt, 98312.BetterInt]
var output = dump(input, tsAll, asTidy, blockOnly)
assertStringEqual yamlDirs & """!n!system:seq(test:BetterInt)
- !test:BetterInt 1
- !test:BetterInt 9_998_887
- !test:BetterInt 98_312""", output