mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2026-03-07 21:27:55 -05:00
430 lines
11 KiB
OCaml
430 lines
11 KiB
OCaml
include (val Containers_testlib.make ~__FILE__ ())
|
|
module Cbor = Containers_cbor
|
|
|
|
let gen_c : Cbor.t Q.Gen.t =
|
|
let open Q.Gen in
|
|
sized @@ fix
|
|
@@ fun self size ->
|
|
let recurse = self (size - 1) in
|
|
let base =
|
|
[
|
|
1, return `Null;
|
|
1, return `Undefined;
|
|
( 3,
|
|
let+ x = int >|= Int64.of_int in
|
|
`Int x );
|
|
( 1,
|
|
let+ b = bool in
|
|
`Bool b );
|
|
( 1,
|
|
let+ x = 0 -- 19 in
|
|
`Simple x );
|
|
( 1,
|
|
let+ x = 26 -- 127 in
|
|
`Simple x );
|
|
( 1,
|
|
let+ f = float in
|
|
`Float f );
|
|
( 2,
|
|
let* n = oneof_weighted [ 20, 0 -- 150; 1, 151 -- 100_000 ] in
|
|
let+ s = string_size ~gen:printable (return n) in
|
|
`Text s );
|
|
( 2,
|
|
let* n = oneof_weighted [ 20, 0 -- 150; 1, 151 -- 100_000 ] in
|
|
let+ s = string_size ~gen:char (return n) in
|
|
`Bytes s );
|
|
]
|
|
in
|
|
let g_base = oneof_weighted base in
|
|
let rec_ =
|
|
[
|
|
( 2,
|
|
let+ l =
|
|
if size > 10 then
|
|
list_size (0 -- 1024) g_base
|
|
else
|
|
list_size (0 -- 10) recurse
|
|
in
|
|
`Array l );
|
|
( 2,
|
|
let+ l =
|
|
if size > 10 then
|
|
list_size (0 -- 1024) (pair g_base g_base)
|
|
else
|
|
list_size (0 -- 5) (pair g_base recurse)
|
|
in
|
|
`Map l );
|
|
( 1,
|
|
let+ i = 0 -- 1024 and+ sub = self (size - 1) in
|
|
`Tag (i, sub) );
|
|
]
|
|
in
|
|
oneof_weighted
|
|
(if size > 0 then
|
|
base @ rec_
|
|
else
|
|
base)
|
|
|
|
let rec shrink (c : Cbor.t) : Cbor.t Q.Iter.t =
|
|
let open Q.Iter in
|
|
match c with
|
|
| `Null | `Undefined | `Bool false -> empty
|
|
| `Bool true -> return (`Bool false)
|
|
| `Simple i ->
|
|
let+ i = Q.Shrink.int i in
|
|
`Simple i
|
|
| `Int i ->
|
|
let+ i = Q.Shrink.int (Int64.to_int i) in
|
|
`Int (Int64.of_int i)
|
|
| `Tag (t, i) ->
|
|
let+ i = shrink i in
|
|
`Tag (t, i)
|
|
| `Float _ -> empty
|
|
| `Array l ->
|
|
let+ l = Q.Shrink.list ~shrink l in
|
|
`Array l
|
|
| `Map l ->
|
|
let shrink_pair (a, b) =
|
|
(let+ a = shrink a in
|
|
a, b)
|
|
<+> let+ b = shrink b in
|
|
a, b
|
|
in
|
|
let+ l = Q.Shrink.list ~shrink:shrink_pair l in
|
|
`Map l
|
|
| `Text s ->
|
|
let+ s = Q.Shrink.string s in
|
|
`Text s
|
|
| `Bytes s ->
|
|
let+ s = Q.Shrink.string s in
|
|
`Bytes s
|
|
|
|
let arb = Q.make ~shrink ~print:Cbor.to_string_diagnostic gen_c
|
|
|
|
let rec eq_c c c' =
|
|
match c, c' with
|
|
| `Null, `Null | `Undefined, `Undefined -> true
|
|
| `Simple i, `Simple i' -> Int.equal i i'
|
|
| `Bool b, `Bool b' -> Bool.equal b b'
|
|
| `Int i, `Int i' -> Int64.equal i i'
|
|
| `Float f, `Float f' -> Float.equal f f'
|
|
| `Bytes s, `Bytes s' -> String.equal s s'
|
|
| `Text t, `Text t' -> String.equal t t'
|
|
| `Array a, `Array a' -> CCList.equal eq_c a a'
|
|
| `Map m, `Map m' ->
|
|
CCList.equal (fun (t0, t1) (t0', t1') -> eq_c t0 t0' && eq_c t1 t1') m m'
|
|
| `Tag (i, t), `Tag (i', t') -> Int.equal i i' && eq_c t t'
|
|
| _ -> false
|
|
;;
|
|
|
|
q ~count:1_000 ~long_factor:10 arb @@ fun c ->
|
|
let s = Cbor.encode c in
|
|
let c' = Cbor.decode_exn s in
|
|
if not (eq_c c c') then
|
|
Q.Test.fail_reportf "@[<hv2>roundtrip failed:@ from %a@ to %a@]"
|
|
Cbor.pp_diagnostic c Cbor.pp_diagnostic c';
|
|
true
|
|
;;
|
|
|
|
(* Additional edge case and error handling tests *)
|
|
|
|
(* Test basic encoding/decoding *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode `Null) = `Null;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode `Undefined) = `Undefined;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Bool true)) = `Bool true;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Bool false)) = `Bool false;;
|
|
|
|
(* Test integer edge cases *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 0L)) = `Int 0L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 23L)) = `Int 23L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 24L)) = `Int 24L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 255L)) = `Int 255L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 256L)) = `Int 256L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 65535L)) = `Int 65535L;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int 65536L)) = `Int 65536L;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Int Int64.max_int)) = `Int Int64.max_int
|
|
;;
|
|
|
|
(* Test negative integers *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int (-1L))) = `Int (-1L);;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int (-23L))) = `Int (-23L);;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int (-24L))) = `Int (-24L);;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Int (-256L))) = `Int (-256L);;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Int Int64.min_int)) = `Int Int64.min_int
|
|
;;
|
|
|
|
(* Test floats *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Float 0.0)) = `Float 0.0;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Float 1.5)) = `Float 1.5;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Float (-1.5))) = `Float (-1.5);;
|
|
|
|
t @@ fun () ->
|
|
let result = Cbor.decode_exn (Cbor.encode (`Float infinity)) in
|
|
match result with
|
|
| `Float f -> classify_float f = FP_infinite && f > 0.0
|
|
| _ -> false
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
let result = Cbor.decode_exn (Cbor.encode (`Float neg_infinity)) in
|
|
match result with
|
|
| `Float f -> classify_float f = FP_infinite && f < 0.0
|
|
| _ -> false
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
let result = Cbor.decode_exn (Cbor.encode (`Float nan)) in
|
|
match result with
|
|
| `Float f -> classify_float f = FP_nan
|
|
| _ -> false
|
|
;;
|
|
|
|
(* Test strings *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Text "")) = `Text "";;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Text "hello")) = `Text "hello";;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Text "a")) = `Text "a";;
|
|
|
|
t @@ fun () ->
|
|
let long = String.make 1000 'x' in
|
|
Cbor.decode_exn (Cbor.encode (`Text long)) = `Text long
|
|
;;
|
|
|
|
(* Test UTF-8 strings *)
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Text "hello 世界")) = `Text "hello 世界"
|
|
;;
|
|
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Text "émoji 🎉")) = `Text "émoji 🎉"
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Text "Здравствуй")) = `Text "Здравствуй"
|
|
;;
|
|
|
|
(* Test bytes *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Bytes "")) = `Bytes "";;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Bytes "\x00\x01\x02")) = `Bytes "\x00\x01\x02"
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
let bytes = String.init 256 char_of_int in
|
|
Cbor.decode_exn (Cbor.encode (`Bytes bytes)) = `Bytes bytes
|
|
;;
|
|
|
|
(* Test arrays *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Array [])) = `Array [];;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Array [ `Int 1L ])) = `Array [ `Int 1L ]
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Array [ `Int 1L; `Int 2L; `Int 3L ]))
|
|
= `Array [ `Int 1L; `Int 2L; `Int 3L ]
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Array [ `Bool true; `Text "a"; `Int 42L ]))
|
|
= `Array [ `Bool true; `Text "a"; `Int 42L ]
|
|
;;
|
|
|
|
(* Test nested arrays *)
|
|
t @@ fun () ->
|
|
let nested = `Array [ `Array [ `Int 1L; `Int 2L ]; `Array [ `Int 3L ] ] in
|
|
Cbor.decode_exn (Cbor.encode nested) = nested
|
|
;;
|
|
|
|
(* Test maps *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Map [])) = `Map [];;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Map [ `Text "key", `Int 42L ]))
|
|
= `Map [ `Text "key", `Int 42L ]
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
let map = `Map [ `Text "a", `Int 1L; `Text "b", `Int 2L; `Text "c", `Int 3L ] in
|
|
Cbor.decode_exn (Cbor.encode map) = map
|
|
;;
|
|
|
|
(* Test maps with various key types *)
|
|
t @@ fun () ->
|
|
let map = `Map [ `Int 0L, `Text "zero"; `Int 1L, `Text "one" ] in
|
|
Cbor.decode_exn (Cbor.encode map) = map
|
|
;;
|
|
|
|
(* Test tags *)
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Tag (0, `Text "2013-03-21")))
|
|
= `Tag (0, `Text "2013-03-21")
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Tag (1, `Int 1363896240L)))
|
|
= `Tag (1, `Int 1363896240L)
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.decode_exn (Cbor.encode (`Tag (32, `Text "http://example.com")))
|
|
= `Tag (32, `Text "http://example.com")
|
|
;;
|
|
|
|
(* Test simple values *)
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Simple 0)) = `Simple 0;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Simple 19)) = `Simple 19;;
|
|
t @@ fun () -> Cbor.decode_exn (Cbor.encode (`Simple 255)) = `Simple 255;;
|
|
|
|
(* Test error cases *)
|
|
t @@ fun () ->
|
|
match Cbor.decode "" with
|
|
| Error _ -> true
|
|
| Ok _ -> false
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
match Cbor.decode "\x1f" with
|
|
(* invalid additional info *)
|
|
| Error _ -> true
|
|
| Ok _ -> false
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
match Cbor.decode "\x1c" with
|
|
(* reserved additional info *)
|
|
| Error _ -> true
|
|
| Ok _ -> false
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
match Cbor.decode "\x5f\x42\x01\x02\x43\x03\x04\x05" with
|
|
(* incomplete indefinite *)
|
|
| Error _ -> true
|
|
| Ok _ -> false
|
|
;;
|
|
|
|
(* Test that decode_exn raises on invalid input *)
|
|
t @@ fun () ->
|
|
try
|
|
ignore (Cbor.decode_exn "");
|
|
false
|
|
with Failure _ -> true
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
try
|
|
ignore (Cbor.decode_exn "\x1c");
|
|
false
|
|
with Failure _ -> true
|
|
;;
|
|
|
|
(* Test diagnostic string output *)
|
|
t @@ fun () -> Cbor.to_string_diagnostic `Null = "null";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic `Undefined = "undefined";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Bool true) = "true";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Bool false) = "false";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Int 42L) = "42";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Int (-42L)) = "-42";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Float 1.5) = "1.5";;
|
|
t @@ fun () -> Cbor.to_string_diagnostic (`Text "hello") = "\"hello\"";;
|
|
|
|
t @@ fun () ->
|
|
Cbor.to_string_diagnostic (`Array [ `Int 1L; `Int 2L ]) = "[1, 2]"
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
Cbor.to_string_diagnostic (`Map [ `Text "a", `Int 1L ])
|
|
|> CCString.mem ~sub:"\"a\""
|
|
;;
|
|
|
|
(* Test deeply nested structures *)
|
|
t @@ fun () ->
|
|
let rec make_nested n =
|
|
if n = 0 then
|
|
`Int 0L
|
|
else
|
|
`Array [ make_nested (n - 1) ]
|
|
in
|
|
let nested = make_nested 100 in
|
|
Cbor.decode_exn (Cbor.encode nested) = nested
|
|
;;
|
|
|
|
(* Test large collections *)
|
|
t @@ fun () ->
|
|
let large_array = `Array (List.init 1000 (fun i -> `Int (Int64.of_int i))) in
|
|
Cbor.decode_exn (Cbor.encode large_array) = large_array
|
|
;;
|
|
|
|
t @@ fun () ->
|
|
let large_map =
|
|
`Map (List.init 500 (fun i -> `Int (Int64.of_int i), `Text (string_of_int i)))
|
|
in
|
|
Cbor.decode_exn (Cbor.encode large_map) = large_map
|
|
;;
|
|
|
|
(* Test mixed nested structures *)
|
|
t @@ fun () ->
|
|
let complex =
|
|
`Map
|
|
[
|
|
`Text "array", `Array [ `Int 1L; `Int 2L; `Int 3L ];
|
|
`Text "map", `Map [ `Text "nested", `Bool true ];
|
|
`Text "tagged", `Tag (42, `Text "value");
|
|
`Text "null", `Null;
|
|
]
|
|
in
|
|
Cbor.decode_exn (Cbor.encode complex) = complex
|
|
;;
|
|
|
|
(* Test that encoding is consistent *)
|
|
t @@ fun () ->
|
|
let c = `Map [ `Text "a", `Int 1L; `Text "b", `Int 2L ] in
|
|
let e1 = Cbor.encode c in
|
|
let e2 = Cbor.encode c in
|
|
e1 = e2
|
|
;;
|
|
|
|
(* Test buffer reuse *)
|
|
t @@ fun () ->
|
|
let buf = Buffer.create 16 in
|
|
let _ = Cbor.encode ~buf (`Int 1L) in
|
|
let s1 = Buffer.contents buf in
|
|
Buffer.clear buf;
|
|
let _ = Cbor.encode ~buf (`Int 1L) in
|
|
let s2 = Buffer.contents buf in
|
|
s1 = s2
|
|
;;
|
|
|
|
(* Property: encoding then decoding gives original value *)
|
|
q ~count:5000 arb @@ fun c ->
|
|
match Cbor.decode (Cbor.encode c) with
|
|
| Ok c' -> eq_c c c'
|
|
| Error e -> Q.Test.fail_reportf "decode failed: %s" e
|
|
;;
|
|
|
|
(* Property: decode result equality *)
|
|
q ~count:2000 arb @@ fun c ->
|
|
let s = Cbor.encode c in
|
|
match Cbor.decode s with
|
|
| Error e -> Q.Test.fail_reportf "decode failed on encoded value: %s" e
|
|
| Ok c1 ->
|
|
(match Cbor.decode s with
|
|
| Error _ -> false
|
|
| Ok c2 -> eq_c c1 c2)
|
|
;;
|
|
|
|
(* Property: diagnostic string doesn't crash *)
|
|
q ~count:1000 arb @@ fun c ->
|
|
let _ = Cbor.to_string_diagnostic c in
|
|
true
|
|
;;
|
|
|
|
(* Property: encoding size is reasonable *)
|
|
q ~count:1000 arb @@ fun c ->
|
|
let s = Cbor.encode c in
|
|
String.length s < 1_000_000 (* Sanity check *)
|