let basic_test (module Test_db : TEST_DATABASE) uri_string () =
  let open Test_db in
  let open Trakeva.Action in
  let local_assert name c =
    Test.check (name :: test_name :: uri_string :: []) c in
  DB.load uri_string
  >>= fun db ->
  let test_get ?(handle=db) ?collection k f =
    DB.get handle ?collection ~key:k
    >>= fun opt ->
    local_assert (sprintf "get %s/%s" (Option.value collection ~default:"") k)
      (f opt);
    return () in
  let is_none = ((=) Nonein
  test_get "k" is_none >>= fun () ->
  test_get ~collection:"c" "k" is_none >>= fun () ->
  DB.get_all db ~collection:"c"
  >>= fun list ->
  local_assert "all c" (list = []);
  let test_actions res actions =
    let action = seq actions in
    DB.act db ~action
    >>= function
    | r when r = res -> return ()
    | `Done ->
      ksprintf Test.fail "Action %s should be Not_done" (to_string action);
      return ()
    | `Not_done ->
      ksprintf Test.fail "Action %s should be Done" (to_string action);
      return ()
  in
  test_actions `Done [set ~key:"k" "v"] >>= fun () ->
  test_get  "k" ((=) (Some "v")) >>= fun () ->
  test_actions `Done [
    contains ~key:"k" "v";
    unset "k";
    set ~key:"k1" ~collection:"c" "V";
  ] >>= fun () ->
  test_actions `Done [
    set ~key:"thekey" ~collection:"c1" "v1";
    set ~key:"thekey" ~collection:"c2" "v2";
  ]
  >>= fun () ->
  test_get ?collection:None "thekey" ((=) None) >>= fun () ->
  test_get ~collection:"c1" "thekey" ((=) (Some "v1")) >>= fun () ->
  test_get ~collection:"c2" "thekey" ((=) (Some "v2")) >>= fun () ->
  test_actions `Not_done [
    contains ~key:"k" "v";
    set ~key:"k1" ~collection:"c" "V";
  ] >>= fun () ->
  test_actions `Done [
    is_not_set "k";
    set ~key:"k2" ~collection:"c" "V2";
    set ~key:"k3" ~collection:"c" "V3";
    set ~key:"k4" ~collection:"c" "V4";
    set ~key:"k5" ~collection:"c" "V5";
  ] >>= fun () ->
  DB.get_all db ~collection:"c"
  >>= fun list ->
  local_assert "full collection 'c'"
    (List.sort ~cmp:String.compare list = ["k1""k2""k3""k4""k5"]);
  let key = "K" in
  let insane =
    let buf = Bytes.make 256 '\000' in
    for i = 0 to 255 do Bytes.set buf i (char_of_int i) done;
    Bytes.to_string buf in
  test_actions `Done [
    set ~key "\"";
    set ~key "\\\"";
    set ~key "\"'\"";
    set ~key:insane insane;
  ]
  >>= fun () ->
  test_get ?collection:None insane ((=) (Some insane))
  >>= fun () ->
  let to_list res_stream =
    let rec go acc =
      res_stream ()
      >>= function
      | None -> return acc
      | Some v -> go (v :: acc) in
    go []
  in
  let list_equal l1 l2 =
    let prep = List.sort ~cmp:Pervasives.compare in
    match prep l1 = prep l2 with
    | true -> true
    | false ->
      say "[%s] ≠ [%s]"
        (String.concat  ~sep:", " l1)
        (String.concat  ~sep:", " l2);
      false
  in
  let check_iterator_like_get_all ~collection =
    let stream = DB.iterator db ~collection in
    to_list stream
    >>= fun all ->
    DB.get_all db ~collection
    >>= fun all_too ->
    local_assert (sprintf "iter %s" collection) (list_equal all all_too);
    return ()
  in
  check_iterator_like_get_all "c" >>= fun () ->
  check_iterator_like_get_all "cc" >>= fun () ->
  (*
     We now test going through a collection with `iterator` while
     modifying the values of the collection.
  *)

  let make_self_collection ~collection =
    let keyvalues = List.init 10 ~f:(sprintf "kv%d"in
    let actions = List.map keyvalues ~f:(fun kv -> set ~collection ~key:kv kv) in
    test_actions `Done actions
    >>= fun () ->
    return keyvalues
  in
  let iterate_and_set ~collection =
    let stream = DB.iterator db ~collection in
    let rec go_through () =
      stream () >>= function
      | Some kv ->
        (* say "%s got some %S in the stream" Test_db.test_name kv; *)
        test_actions `Done [set ~collection ~key:kv ("SET" ^ kv)]
        >>= fun () ->
        go_through ()
      | None -> return ()
    in
    go_through ()
  in
  let test_rw_interleave collection =
    make_self_collection ~collection
    >>= fun keyvalues ->
    iterate_and_set ~collection
    >>= fun () ->
    check_iterator_like_get_all collection
    >>= fun () ->
    DB.get_all db ~collection
    >>= fun allnew ->
    (* let modified = List.map keyvalues (fun v -> "SET" ^ v) in *)
    local_assert (sprintf "test_rw_interleave %s" collection)
      (list_equal keyvalues allnew);
    return ()
  in
  (* Test_db.debug_mode true; *)
  test_rw_interleave "ccc"
  >>= fun () ->
  (*
     We now try to delete all values in a collection while iterating on it.
  *)

  let iterate_and_delete ~collection ~at ~all =
    let stream = DB.iterator db ~collection in
    let rec go_through remaining =
      stream () >>= function
      | Some kv when remaining = 0 ->
        say "%s got some %S in the stream" Test_db.test_name kv;
        test_actions `Done (List.map all ~f:(fun key -> unset ~collection key))
        >>= fun () ->
        go_through (-1)
      | Some kv ->
        say "%s got some %S in the stream : remaining: %d" Test_db.test_name kv remaining;
        go_through (remaining - 1)
      | None -> return ()
    in
    go_through at
  in
  let test_delete_iterleave collection =
    make_self_collection ~collection
    >>= fun keyvalues ->
    iterate_and_delete
      ~collection ~all:keyvalues ~at:(List.length keyvalues / 2)
    >>= fun () ->
    DB.get_all db ~collection
    >>= fun allnew ->
    local_assert ("test_delete_iterleave") (allnew = []);
    return ()
  in
  test_delete_iterleave "aaa"
  >>= fun () ->
  (* Read and write concurrently: *)
  let bunch_of_ints = List.init 100 ~f:(fun i -> i) in
  Deferred_list.for_concurrent bunch_of_ints ~f:(function
    | n when n mod 2 = 0 ->
      let v = sprintf "%d" n in
      test_actions `Done [set ~key:v v]
    | n ->
      DB.get db ~key
      >>= fun _ ->
      return ()
    )
  >>= fun ((_ : unit list), errors) ->
  begin match errors with
  | [] -> return ()
  | more ->
    let msg =
      sprintf "concurrent errors: %s"
        (List.map more ~f:(function
           | `Database (`Get _, m)
           | `Database (`Act _, m) -> m)
         |> String.concat ~sep:"; "in
    local_assert msg false;
    return ()
  end
  >>= fun () ->
  (* Test_db.debug_mode false; *)
  DB.close db