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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
module M = Yojson_five.Safe
let yojson = Alcotest.testable M.pp M.equal
(* any error message will match the string. *)
let any_string = Alcotest.testable Fmt.string (fun _ _ -> true)
let parsing_test_case name error_msg expected input =
Alcotest.test_case name `Quick (fun () ->
Alcotest.(check (result yojson error_msg))
name expected (M.from_string input))
let parsing_should_succeed name input expected =
parsing_test_case name Alcotest.string (Ok expected) input
let parsing_should_fail name input =
let failure = Error "<anything>" in
parsing_test_case name any_string failure input
let parsing_should_fail_with_error name input expected =
parsing_test_case name Alcotest.string (Error expected) input
let parsing_tests =
[
parsing_should_fail "Unexpected line break" {|"foo
bar"|};
parsing_should_succeed "true" "true" (`Bool true);
parsing_should_succeed "false" "false" (`Bool false);
parsing_should_succeed "null" "null" `Null;
parsing_should_succeed "double quotes string" {|"hello world"|}
(`String "hello world");
parsing_should_succeed "single quotes string" {|'hello world'|}
(`String "hello world");
parsing_should_succeed "float" "12345.67890" (`Float 12345.67890);
parsing_should_succeed "hex" "0x1" (`Int 0x1);
parsing_should_succeed "hex escape sequence" {|"\x61"|} (`String "a");
parsing_should_succeed "unicode escape sequence" {|"\u03bb"|} (`String "λ");
parsing_should_succeed "more string escaping"
"\"Hello \\u03bb \\x77\\x6F\\x72\\x6C\\x64\"" (`String "Hello λ world");
parsing_should_succeed "escaping quotes" {|"\"\'"|} (`String {|"'|});
parsing_should_succeed "more escaping quotes" {|'\"\''|} (`String {|"'|});
parsing_should_succeed "escaping other chars" {|'\A\C\/\D\C'|}
(`String "AC/DC");
parsing_should_succeed "built in escapes" {|'\b\n\r\t\f\v'|}
(`String "\b\n\r\t\012\011");
parsing_should_succeed "null byte string" {|"\0"|} (`String "\x00");
parsing_should_succeed "octal string" {|"\077"|} (`String "?");
parsing_should_succeed "null and octal string" {|"\07"|} (`String "\x007");
parsing_should_succeed "int" "1" (`Int 1);
parsing_should_succeed "backslash escape" {|"foo\\bar"|}
(`String {|foo\bar|});
parsing_should_succeed "line break" "\"foo\\\nbar\"" (`String "foobar");
parsing_should_succeed "string and comment" "\"bar\" //foo" (`String "bar");
(* objects *)
parsing_should_succeed "empty object" "{}" (`Assoc []);
parsing_should_succeed "object with double quote string" {|{"foo": "bar"}|}
(`Assoc [ ("foo", `String "bar") ]);
parsing_should_succeed "object with single quote string" {|{'foo': 'bar'}|}
(`Assoc [ ("foo", `String "bar") ]);
parsing_should_succeed "object with unquoted string" {|{foo: 'bar'}|}
(`Assoc [ ("foo", `String "bar") ]);
parsing_should_succeed "trailing comma in object" {|{"one": 1,}|}
(`Assoc [ ("one", `Int 1) ]);
parsing_should_succeed "colon in key" {|{"colon:": 1}|}
(`Assoc [ ("colon:", `Int 1) ]);
parsing_should_fail "multiple trailing commas in object" {|{"one": 1,,}|};
parsing_should_fail "just trailing comma in object" "{,}";
parsing_should_fail "just trailing commas in object" "{,,,}";
parsing_should_fail "multiple colons in object" {|{one :: 1}|};
parsing_should_fail "newline in key" {|{new\nline: 1}|};
(* lists *)
parsing_should_succeed "empty list" "[]" (`List []);
parsing_should_succeed "heterogenous list" {|[1, "2", 3.0]|}
(`List [ `Int 1; `String "2"; `Float 3. ]);
parsing_should_succeed "trailing comma in list" "[1, 2, 3,]"
(`List [ `Int 1; `Int 2; `Int 3 ]);
parsing_should_succeed "trailing comma with space list" "[1, 2, 3, ]"
(`List [ `Int 1; `Int 2; `Int 3 ]);
parsing_should_succeed "newlines in list" "[1, 2\n, 3]"
(`List [ `Int 1; `Int 2; `Int 3 ]);
parsing_should_fail "multiple trailing commas in list" "[1, 2, 3,,]";
parsing_should_fail "just trailing comma in list" "[,]";
parsing_should_fail "multiple trailing commas in list" "[,,,]";
(* all together *)
(let expected =
`Assoc
[
("unquoted", `String "and you can quote me on that");
("singleQuotes", `String "I can use \"double quotes\" here");
("lineBreaks", `String {|Look, Mom! No \n's!|});
("hexadecimal", `Int 0xdecaf);
("leadingDecimalPoint", `Float 0.8675309);
("andTrailing", `Float 8675309.0);
("positiveSign", `Int 1);
("trailingComma", `String "in objects");
("andIn", `List [ `String "arrays" ]);
("backwardsCompatible", `String "with JSON");
]
in
parsing_should_succeed "More elaborated"
{|{
// comments
unquoted: 'and you can quote me on that',
singleQuotes: 'I can use "double quotes" here',
lineBreaks: "Look, Mom! \
No \\n's!",
hexadecimal: 0xdecaf,
leadingDecimalPoint: .8675309, andTrailing: 8675309.,
positiveSign: +1,
trailingComma: 'in objects', andIn: ['arrays',],
"backwardsCompatible": "with JSON",
}|}
expected);
parsing_should_fail_with_error "unexpected EOF in list" "[1, 2,"
"Line 1: Unexpected end of input";
parsing_should_fail_with_error "unexpected EOF on different line" "\n[1, 2,"
"Line 2: Unexpected end of input";
parsing_should_fail_with_error "unexpected EOF in assoc" {|{"foo": 1,|}
"Line 1: Unexpected end of input";
parsing_should_fail_with_error "missing colon in assoc" {|{"foo"}|}
"Line 1: Expected ':' but found '}'";
parsing_should_fail_with_error "bad identifier in assoc" {|{[0]}|}
"Line 1: Expected string or identifier but found '['";
]
let writing_test_case name input expected =
Alcotest.test_case name `Quick (fun () ->
Alcotest.(check string) name expected (M.to_string input))
let writing_tests =
[
writing_test_case "Empty object" (`Assoc []) "{}";
writing_test_case "Empty list" (`List []) "[]";
writing_test_case "true" (`Bool true) "true";
writing_test_case "false" (`Bool false) "false";
writing_test_case "null" `Null "null";
writing_test_case "string" (`String "hello world") "\"hello world\"";
writing_test_case "float" (`Float 12345.6789) "12345.6789";
writing_test_case "hex" (`Int 0x1) "1";
writing_test_case "int" (`Int 1) "1";
]
let () =
Alcotest.run "JSON5"
[ ("parsing", parsing_tests); ("writing", writing_tests) ]
|