ocaml-containers/tests/core/t_result.ml
Simon Cruanes 83e03d2a94
wip
2026-02-10 21:29:02 -05:00

391 lines
8.8 KiB
OCaml

open CCResult
module T = (val Containers_testlib.make ~__FILE__ ())
include T;;
t @@ fun () -> Error "ohno 42" = fail_printf "ohno %d" 42;;
t @@ fun () -> Error "ohno 42" = fail_fprintf "ohno %d" 42;;
eq (Error "error\ncontext:message(number 42, foo: true)")
(add_ctxf "message(number %d, foo: %B)" 42 true (Error "error"))
;;
t @@ fun () ->
let called_with = ref None in
let f e = called_with := Some e in
iter_err f (Ok 1);
assert (!called_with = None);
iter_err f (Error 1);
assert (!called_with = Some 1);
true
;;
t @@ fun () -> get_or_failwith (Ok 1) = 1;;
t @@ fun () ->
try
ignore @@ get_or_failwith (Error "e");
false
with Failure msg -> msg = "e"
;;
eq (get_lazy (fun _ -> 2) (Ok 1)) 1;;
eq (get_lazy (fun _ -> 2) (Error "error")) 2;;
eq 42 (fold_ok ( + ) 2 (Ok 40));;
eq 40 (fold_ok ( + ) 40 (Error "foo"));;
eq (Ok []) (flatten_l []);;
eq (Ok [ 1; 2; 3 ]) (flatten_l [ Ok 1; Ok 2; Ok 3 ]);;
eq (Error "ohno") (flatten_l [ Ok 1; Error "ohno"; Ok 2; Ok 3; Error "wut" ]);;
(* Additional comprehensive tests for CCResult *)
(* Test return and fail *)
eq (Ok 42) (return 42);;
eq (Error "failed") (fail "failed");;
(* Test of_exn and of_exn_trace *)
t @@ fun () ->
match of_exn (Failure "test") with
| Error msg -> String.length msg > 0
| Ok _ -> false
;;
t @@ fun () ->
match of_exn_trace (Failure "test") with
| Error msg -> String.length msg > 0
| Ok _ -> false
;;
(* Test opt_map *)
eq (Ok (Some 6)) (opt_map (fun x -> Ok (x * 2)) (Some 3));;
eq (Ok None) (opt_map (fun x -> Ok (x * 2)) None);;
eq (Error "err") (opt_map (fun _ -> Error "err") (Some 3));;
(* Test map *)
eq (Ok 3) (map (( + ) 1) (Ok 2));;
eq (Error "e") (map (( + ) 1) (Error "e"));;
t @@ fun () -> map String.uppercase_ascii (Ok "hello") = Ok "HELLO";;
(* Test map_err *)
eq (Ok 5) (map_err String.uppercase_ascii (Ok 5));;
eq (Error "ERROR") (map_err String.uppercase_ascii (Error "error"));;
(* Test map2 *)
eq (Ok "HELLO")
(map2 String.uppercase_ascii String.uppercase_ascii (Ok "hello"))
;;
eq (Error "ERROR")
(map2 String.uppercase_ascii String.uppercase_ascii (Error "error"))
;;
(* Test iter *)
t @@ fun () ->
let r = ref 0 in
iter (fun x -> r := x) (Ok 42);
!r = 42
;;
t @@ fun () ->
let r = ref 0 in
iter (fun x -> r := x) (Error "e");
!r = 0
;;
(* Test get_exn *)
eq 42 (get_exn (Ok 42));;
t @@ fun () ->
try
ignore (get_exn (Error "error"));
false
with Invalid_argument _ -> true
;;
(* Test get_or *)
eq 5 (get_or (Ok 5) ~default:0);;
eq 0 (get_or (Error "e") ~default:0);;
(* Test apply_or *)
eq 10 (apply_or (fun x -> Ok (x * 2)) 5);;
t @@ fun () ->
apply_or
(fun x ->
if x > 0 then
Ok (x * 2)
else
Error "neg")
5
= 10
;;
t @@ fun () ->
apply_or
(fun x ->
if x > 0 then
Ok (x * 2)
else
Error "neg")
(-5)
= -5
;;
(* Test map_or *)
eq 10 (map_or (fun x -> x * 2) (Ok 5) ~default:0);;
eq 0 (map_or (fun x -> x * 2) (Error "e") ~default:0);;
(* Test catch *)
eq 5 (catch (Ok 5) ~ok:CCFun.id ~err:(fun _ -> 0));;
eq 0 (catch (Error "e") ~ok:CCFun.id ~err:(fun _ -> 0));;
eq "ERROR: e"
(catch (Error "e") ~ok:Int.to_string ~err:(fun e -> "ERROR: " ^ e))
;;
(* Test flat_map *)
eq (Ok 3) (flat_map (fun x -> Ok (x + 1)) (Ok 2));;
eq (Error "e") (flat_map (fun x -> Ok (x + 1)) (Error "e"));;
eq (Error "e2") (flat_map (fun _ -> Error "e2") (Ok 2));;
(* Test fold *)
eq 10 (fold ~ok:CCFun.id ~error:(fun _ -> 0) (Ok 10));;
eq 0 (fold ~ok:CCFun.id ~error:(fun _ -> 0) (Error "e"));;
(* Test is_ok and is_error *)
t @@ fun () -> is_ok (Ok 1);;
t @@ fun () -> not (is_ok (Error "e"));;
t @@ fun () -> is_error (Error "e");;
t @@ fun () -> not (is_error (Ok 1));;
(* Test guard and guard_str *)
t @@ fun () ->
match guard (fun () -> 42) with
| Ok 42 -> true
| _ -> false
;;
t @@ fun () ->
match guard (fun () -> failwith "error") with
| Error _ -> true
| _ -> false
;;
t @@ fun () ->
match guard_str (fun () -> 42) with
| Ok 42 -> true
| _ -> false
;;
t @@ fun () ->
match guard_str (fun () -> failwith "test error") with
| Error msg -> String.length msg > 0
| _ -> false
;;
(* Test guard_str_trace *)
t @@ fun () ->
match guard_str_trace (fun () -> 42) with
| Ok 42 -> true
| _ -> false
;;
t @@ fun () ->
match guard_str_trace (fun () -> failwith "test error") with
| Error msg -> String.length msg > 0
| _ -> false
;;
(* Test wrap functions *)
eq (Ok 6) (wrap1 (( + ) 1) 5);;
t @@ fun () ->
match wrap1 (fun _ -> failwith "error") () with
| Error _ -> true
| _ -> false
;;
eq (Ok 7) (wrap2 ( + ) 3 4);;
t @@ fun () ->
match wrap2 (fun _ _ -> failwith "error") 1 2 with
| Error _ -> true
| _ -> false
;;
eq (Ok 10) (wrap3 (fun a b c -> a + b + c) 2 3 5);;
t @@ fun () ->
match wrap3 (fun _ _ _ -> failwith "error") 1 2 3 with
| Error _ -> true
| _ -> false
;;
(* Test pure *)
eq (Ok 42) (pure 42);;
(* Test join *)
eq (Ok 5) (join (Ok (Ok 5)));;
eq (Error "e") (join (Ok (Error "e")));;
eq (Error "e") (join (Error "e"));;
(* Test both *)
eq (Ok (3, 5)) (both (Ok 3) (Ok 5));;
eq (Error "e1") (both (Error "e1") (Ok 5));;
eq (Error "e2") (both (Ok 3) (Error "e2"));;
eq (Error "e1") (both (Error "e1") (Error "e2"));;
(* Test map_l *)
eq (Ok [ 2; 3; 4 ]) (map_l (fun x -> Ok (x + 1)) [ 1; 2; 3 ]);;
eq (Error "e")
(map_l
(fun x ->
if x > 0 then
Ok x
else
Error "e")
[ 1; -1; 2 ])
;;
eq (Ok []) (map_l (fun x -> Ok x) []);;
(* Test fold_l *)
eq (Ok 6) (fold_l (fun acc x -> Ok (acc + x)) 0 [ 1; 2; 3 ]);;
eq (Error "e")
(fold_l
(fun _ x ->
if x > 0 then
Ok x
else
Error "e")
0 [ 1; -1; 2 ])
;;
(* Test choose *)
eq (Ok 1) (choose [ Ok 1; Ok 2; Ok 3 ]);;
eq (Ok 2) (choose [ Error "e1"; Ok 2; Ok 3 ]);;
eq (Ok 3) (choose [ Error "e1"; Error "e2"; Ok 3 ]);;
eq (Error [ "e1"; "e2"; "e3" ]) (choose [ Error "e1"; Error "e2"; Error "e3" ])
;;
eq (Error []) (choose []);;
(* Test retry *)
t @@ fun () ->
let attempts = ref 0 in
let f () =
incr attempts;
if !attempts < 3 then
Error "fail"
else
Ok "success"
in
match retry 5 f with
| Ok "success" -> !attempts = 3
| _ -> false
;;
t @@ fun () ->
let attempts = ref 0 in
let f () =
incr attempts;
Error "always fails"
in
match retry 3 f with
| Error errs -> !attempts = 3 && List.length errs = 3
| _ -> false
;;
(* Test to_opt *)
eq (Some 5) (to_opt (Ok 5));;
eq None (to_opt (Error "e"));;
(* Test of_opt *)
eq (Ok 5) (of_opt (Some 5));;
eq (Error "option is None") (of_opt None);;
(* Test equal *)
t @@ fun () -> equal ~err:String.equal Int.equal (Ok 5) (Ok 5);;
t @@ fun () -> not (equal ~err:String.equal Int.equal (Ok 5) (Ok 6));;
t @@ fun () -> equal ~err:String.equal Int.equal (Error "e") (Error "e");;
t @@ fun () -> not (equal ~err:String.equal Int.equal (Error "e1") (Error "e2"))
;;
t @@ fun () -> not (equal ~err:String.equal Int.equal (Ok 5) (Error "e"));;
(* Test compare *)
t @@ fun () -> compare ~err:String.compare Int.compare (Ok 5) (Ok 5) = 0;;
t @@ fun () -> compare ~err:String.compare Int.compare (Ok 5) (Ok 6) < 0;;
t @@ fun () -> compare ~err:String.compare Int.compare (Ok 6) (Ok 5) > 0;;
t @@ fun () ->
compare ~err:String.compare Int.compare (Error "a") (Error "a") = 0
;;
t @@ fun () ->
compare ~err:String.compare Int.compare (Error "a") (Error "b") < 0
;;
t @@ fun () -> compare ~err:String.compare Int.compare (Error "a") (Ok 5) < 0;;
t @@ fun () -> compare ~err:String.compare Int.compare (Ok 5) (Error "a") > 0;;
(* Property-based tests *)
q Q.int (fun x -> return x = Ok x);;
q Q.(result int string) (fun r -> is_ok r = not (is_error r));;
q Q.(result int string) (fun r -> map CCFun.id r = r);;
q Q.(result int string) (fun r -> map_err CCFun.id r = r);;
q Q.(result int string) (fun r -> flat_map return r = r);;
q Q.(result int string) (fun r -> equal ~err:String.equal Int.equal r r);;
q
Q.(result int string)
(fun r -> compare ~err:String.compare Int.compare r r = 0)
;;
q
Q.(result int string)
(fun r ->
of_opt (to_opt r)
=
match r with
| Ok x -> Ok x
| Error _ -> Error "option is None")
;;
q Q.int (fun x -> to_opt (Ok x) = Some x);;
q Q.string (fun e -> to_opt (Error e) = None);;
q
Q.(pair (result int string) int)
(fun (r, default) ->
let v = get_or r ~default in
match r with
| Ok x -> v = x
| Error _ -> v = default)
;;
q
Q.(list (result int string))
(fun l ->
match flatten_l l with
| Ok values ->
List.for_all
(function
| Ok _ -> true
| Error _ -> false)
l
&& List.length values <= List.length l
| Error _ ->
List.exists
(function
| Error _ -> true
| Ok _ -> false)
l)
;;
(* Additional focused tests for high-value functions *)
t @@ fun () -> map (( + ) 1) (Ok 2) = Ok 3;;
t @@ fun () -> is_ok (Ok 1) && not (is_ok (Error "e"));;
t @@ fun () -> to_opt (Ok 5) = Some 5 && to_opt (Error "e") = None;;
t @@ fun () -> both (Ok 3) (Ok 5) = Ok (3, 5);;
q Q.int (fun x -> return x = Ok x);;
q Q.int (fun x -> to_opt (Ok x) = Some x)