1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
// Author: cirakg & claude ai
// Summary: Tests for the streaming JSON tokenizer: tokens, escapes, numbers, errors, incomplete input, limits.
import Toybox.Lang;
import Toybox.Test;
(:test)
module JsonFixtures {
// All tokens of text, as "kind:value" strings (values for keys, strings
// and numbers), ending with the final token.
function tokens(text as String) as Array<String> {
var tok = new Json.Tokenizer(Bytes.fromUtf8(text));
var out = [] as Array<String>;
for (var i = 0; i < 200; i++) {
var t = tok.next();
if (t == Json.T_KEY) {
out.add("K:" + tok.text);
} else if (t == Json.T_STRING) {
out.add("S:" + tok.text);
} else if (t == Json.T_NUMBER) {
out.add(tok.numberIsInteger ? "N:" + tok.number : "N:float");
} else {
out.add("T" + t);
}
if (t == Json.T_END || t == Json.T_INCOMPLETE || t == Json.T_ERROR) {
return out;
}
}
return out;
}
function last(text as String) as Number {
var tok = new Json.Tokenizer(Bytes.fromUtf8(text));
for (var i = 0; i < 500; i++) {
var t = tok.next();
if (t == Json.T_END || t == Json.T_INCOMPLETE || t == Json.T_ERROR) {
return t;
}
}
return -1;
}
}
(:test)
function jsonTokenizesAllValueKinds(logger as Test.Logger) as Boolean {
var got = JsonFixtures.tokens("{\"a\": [1, -20, true, false, null, \"x\\\"y\\n\\u00e9\\ud83d\\ude00\"], \"b\": {}}");
var expected = ["T1", "K:a", "T3", "N:1", "N:-20", "T8", "T9", "T10", "S:x\"y\né😀", "T4", "K:b", "T1", "T2", "T2", "T0"];
if (!got.toString().equals(expected.toString())) {
logger.error("got " + got.toString());
return false;
}
return true;
}
(:test)
function jsonNumbers(logger as Test.Logger) as Boolean {
var got = JsonFixtures.tokens("[0, 1.5, 1e3, -0, 123456789012345678901, 999999999999999999]");
var expected = ["T3", "N:0", "N:float", "N:float", "N:0", "N:float", "N:999999999999999999", "T4", "T0"];
if (!got.toString().equals(expected.toString())) {
logger.error("got " + got.toString());
return false;
}
return true;
}
(:test)
function jsonRejectsInvalidDocuments(logger as Test.Logger) as Boolean {
var bad = [
"[1,]", "{\"a\" 1}", "[01]", "[\"\\x\"]", "[\"\\ud800\"]", "[\"\\udc00\"]", "{a: 1}", "[1] [2]",
"[\"\\u0000\"]", "[1.]", "[-]", "[tru]", "{\"a\":1,}", "[\"a\tb\"]", "[1 2]", "]"
];
for (var i = 0; i < bad.size(); i++) {
if (JsonFixtures.last(bad[i] as String) != Json.T_ERROR) {
logger.error("accepted invalid JSON " + bad[i]);
return false;
}
}
return true;
}
// Input that stops inside the document is "incomplete", not an error, so
// a multi-part import can wait for the rest.
(:test)
function jsonReportsIncompleteInput(logger as Test.Logger) as Boolean {
var partial = ["{", "{\"a\"", "{\"a\":", "{\"a\": \"abc", "{\"a\": 12", "[tr", "[1, {\"b\": [", "{\"a\": \"x\\"];
for (var i = 0; i < partial.size(); i++) {
if (JsonFixtures.last(partial[i] as String) != Json.T_INCOMPLETE) {
logger.error("not incomplete: " + partial[i]);
return false;
}
}
return JsonFixtures.last("") == Json.T_INCOMPLETE && JsonFixtures.last(" 7 ") == Json.T_END;
}
(:test)
function jsonEnforcesTheDepthLimit(logger as Test.Logger) as Boolean {
var ok = "";
var tooDeep = "";
for (var i = 0; i < Json.MAX_DEPTH; i++) {
ok += "[";
}
tooDeep = ok + "[";
var close = "";
for (var i = 0; i < Json.MAX_DEPTH; i++) {
close += "]";
}
return JsonFixtures.last(ok + close) == Json.T_END && JsonFixtures.last(tooDeep + close + "]") == Json.T_ERROR;
}
// Long strings (icons) are skipped without decoding, including escaped
// quotes inside them; long keys are errors.
(:test)
function jsonSkipsLongStrings(logger as Test.Logger) as Boolean {
var big = "";
for (var i = 0; i < 300; i++) {
big += "ABCDEFGHIJ";
}
var withQuote = big + "\\\"" + big;
var got = JsonFixtures.tokens("{\"icon\": \"" + big + "\", \"q\": \"" + withQuote + "\", \"n\": \"ok\"}");
var expected = ["T1", "K:icon", "T11", "K:q", "T11", "K:n", "S:ok", "T2", "T0"];
if (!got.toString().equals(expected.toString())) {
logger.error("got " + got.toString());
return false;
}
return JsonFixtures.last("{\"" + big + "\": 1}") == Json.T_ERROR
&& JsonFixtures.last("{\"icon\": \"" + big) == Json.T_INCOMPLETE;
}
|