Persistent store with Git semantics: lazy reads, delayed writes, content-addressing
at inline-small-objects 614 lines 22 kB view raw
1open Irmin 2 3(* Hash tests *) 4let test_sha1_hash () = 5 let h = Hash.sha1 "hello" in 6 let hex = Hash.to_hex h in 7 Alcotest.(check string) 8 "sha1 hex length" (String.make 40 '0') 9 (String.make (String.length hex) '0'); 10 Alcotest.(check int) "sha1 bytes length" 20 (String.length (Hash.to_bytes h)) 11 12let test_sha256_hash () = 13 let h = Hash.sha256 "hello" in 14 let hex = Hash.to_hex h in 15 Alcotest.(check string) 16 "sha256 hex length" (String.make 64 '0') 17 (String.make (String.length hex) '0'); 18 Alcotest.(check int) 19 "sha256 bytes length" 32 20 (String.length (Hash.to_bytes h)) 21 22let test_hash_roundtrip () = 23 let h1 = Hash.sha1 "test data" in 24 let hex = Hash.to_hex h1 in 25 match Hash.sha1_of_hex hex with 26 | Ok h2 -> Alcotest.(check bool) "roundtrip" true (Hash.equal h1 h2) 27 | Error (`Msg msg) -> Alcotest.fail msg 28 29let test_mst_depth () = 30 (* Test MST depth calculation *) 31 let h = Hash.sha256 "test" in 32 let depth = Hash.mst_depth h in 33 Alcotest.(check bool) "depth >= 0" true (depth >= 0) 34 35(* Tree tests *) 36let test_empty_tree () = 37 let tree = Tree.Git.empty () in 38 Alcotest.(check (option string)) 39 "find empty" None 40 (Tree.Git.find tree [ "foo" ]) 41 42let test_tree_add_find () = 43 let tree = Tree.Git.empty () in 44 let tree = Tree.Git.add tree [ "foo"; "bar" ] "content" in 45 Alcotest.(check (option string)) 46 "find added" (Some "content") 47 (Tree.Git.find tree [ "foo"; "bar" ]) 48 49let test_tree_remove () = 50 let tree = Tree.Git.empty () in 51 let tree = Tree.Git.add tree [ "foo" ] "content" in 52 let tree = Tree.Git.remove tree [ "foo" ] in 53 Alcotest.(check (option string)) 54 "find removed" None 55 (Tree.Git.find tree [ "foo" ]) 56 57let test_tree_overwrite () = 58 let tree = Tree.Git.empty () in 59 let tree = Tree.Git.add tree [ "key" ] "value1" in 60 let tree = Tree.Git.add tree [ "key" ] "value2" in 61 Alcotest.(check (option string)) 62 "find overwritten" (Some "value2") 63 (Tree.Git.find tree [ "key" ]) 64 65let test_tree_nested () = 66 let tree = Tree.Git.empty () in 67 let tree = Tree.Git.add tree [ "a"; "b"; "c" ] "deep" in 68 let tree = Tree.Git.add tree [ "a"; "x" ] "shallow" in 69 Alcotest.(check (option string)) 70 "find deep" (Some "deep") 71 (Tree.Git.find tree [ "a"; "b"; "c" ]); 72 Alcotest.(check (option string)) 73 "find shallow" (Some "shallow") 74 (Tree.Git.find tree [ "a"; "x" ]) 75 76(* Backend tests *) 77let test_memory_backend () = 78 let backend = Backend.Memory.create_sha1 () in 79 let data = "test content" in 80 let hash = Hash.sha1 data in 81 backend.write hash data; 82 Alcotest.(check (option string)) "read back" (Some data) (backend.read hash) 83 84let test_backend_refs () = 85 let backend = Backend.Memory.create_sha1 () in 86 let data = "content" in 87 let hash = Hash.sha1 data in 88 backend.write hash data; 89 backend.set_ref "refs/heads/main" hash; 90 Alcotest.(check bool) 91 "ref exists" true 92 (Option.is_some (backend.get_ref "refs/heads/main")); 93 match backend.get_ref "refs/heads/main" with 94 | Some h -> Alcotest.(check bool) "ref matches" true (Hash.equal hash h) 95 | None -> Alcotest.fail "ref not found" 96 97let test_backend_test_and_set () = 98 let backend = Backend.Memory.create_sha1 () in 99 let h1 = Hash.sha1 "content1" in 100 let h2 = Hash.sha1 "content2" in 101 backend.write h1 "content1"; 102 backend.write h2 "content2"; 103 backend.set_ref "ref" h1; 104 105 (* Should fail with wrong test value *) 106 let result = backend.test_and_set_ref "ref" ~test:(Some h2) ~set:(Some h2) in 107 Alcotest.(check bool) "wrong test fails" false result; 108 109 (* Should succeed with correct test value *) 110 let result = backend.test_and_set_ref "ref" ~test:(Some h1) ~set:(Some h2) in 111 Alcotest.(check bool) "correct test succeeds" true result 112 113(* Disk backend tests *) 114let with_temp_dir f = 115 Eio_main.run @@ fun env -> 116 let fs = Eio.Stdenv.fs env in 117 let cwd = Eio.Stdenv.cwd env in 118 Eio.Switch.run @@ fun sw -> 119 let tmp_name = Printf.sprintf "irmin-test-%d" (Random.int 100000) in 120 let tmp_path = Eio.Path.(cwd / tmp_name) in 121 Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 tmp_path; 122 Fun.protect 123 ~finally:(fun () -> 124 (* Clean up temp directory *) 125 let rec rm path = 126 if Eio.Path.is_directory path then begin 127 List.iter 128 (fun name -> rm Eio.Path.(path / name)) 129 (Eio.Path.read_dir path); 130 Eio.Path.rmdir path 131 end 132 else if Eio.Path.is_file path then Eio.Path.unlink path 133 in 134 rm tmp_path) 135 (fun () -> f ~sw ~fs tmp_path) 136 137let test_disk_backend () = 138 with_temp_dir @@ fun ~sw ~fs:_ tmp_path -> 139 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 140 let data = "test content" in 141 let hash = Hash.sha1 data in 142 backend.write hash data; 143 Alcotest.(check (option string)) "read back" (Some data) (backend.read hash); 144 backend.close () 145 146let test_disk_backend_persistence () = 147 Eio_main.run @@ fun env -> 148 let cwd = Eio.Stdenv.cwd env in 149 let tmp_name = Printf.sprintf "irmin-test-%d" (Random.int 100000) in 150 let tmp_path = Eio.Path.(cwd / tmp_name) in 151 let data = "persistent content" in 152 let hash = Hash.sha1 data in 153 (* Write and close *) 154 Eio.Switch.run (fun sw -> 155 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 156 backend.write hash data; 157 backend.set_ref "refs/heads/main" hash; 158 backend.flush (); 159 backend.close ()); 160 (* Reopen and read *) 161 Eio.Switch.run (fun sw -> 162 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 163 Alcotest.(check (option string)) 164 "read after reopen" (Some data) (backend.read hash); 165 Alcotest.(check bool) 166 "ref persisted" true 167 (Option.is_some (backend.get_ref "refs/heads/main")); 168 backend.close ()); 169 (* Clean up *) 170 let rec rm path = 171 if Eio.Path.is_directory path then begin 172 List.iter (fun name -> rm Eio.Path.(path / name)) (Eio.Path.read_dir path); 173 Eio.Path.rmdir path 174 end 175 else if Eio.Path.is_file path then Eio.Path.unlink path 176 in 177 rm tmp_path 178 179let test_disk_backend_refs () = 180 with_temp_dir @@ fun ~sw ~fs:_ tmp_path -> 181 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 182 let data = "content" in 183 let hash = Hash.sha1 data in 184 backend.write hash data; 185 backend.set_ref "refs/heads/main" hash; 186 Alcotest.(check bool) 187 "ref exists" true 188 (Option.is_some (backend.get_ref "refs/heads/main")); 189 (match backend.get_ref "refs/heads/main" with 190 | Some h -> Alcotest.(check bool) "ref matches" true (Hash.equal hash h) 191 | None -> Alcotest.fail "ref not found"); 192 backend.close () 193 194let test_disk_backend_write_batch () = 195 with_temp_dir @@ fun ~sw ~fs:_ tmp_path -> 196 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 197 let objects = 198 [ 199 (Hash.sha1 "data1", "data1"); 200 (Hash.sha1 "data2", "data2"); 201 (Hash.sha1 "data3", "data3"); 202 ] 203 in 204 backend.write_batch objects; 205 List.iter 206 (fun (hash, data) -> 207 Alcotest.(check (option string)) 208 "batch item" (Some data) (backend.read hash)) 209 objects; 210 backend.close () 211 212let test_disk_backend_wal_recovery () = 213 (* Test WAL crash recovery: write without flush, reopen, verify data *) 214 Eio_main.run @@ fun env -> 215 let cwd = Eio.Stdenv.cwd env in 216 let tmp_name = Printf.sprintf "irmin-wal-test-%d" (Random.int 100000) in 217 let tmp_path = Eio.Path.(cwd / tmp_name) in 218 let data = "wal recovery content" in 219 let hash = Hash.sha1 data in 220 (* Write but DON'T flush - simulates crash before checkpoint *) 221 Eio.Switch.run (fun sw -> 222 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 223 backend.write hash data; 224 (* Verify it's readable in current session *) 225 Alcotest.(check (option string)) 226 "readable before crash" (Some data) (backend.read hash); 227 (* Close without flush - WAL should still have the entry *) 228 backend.close ()); 229 (* Reopen - should replay WAL and recover the data *) 230 Eio.Switch.run (fun sw -> 231 let backend = Backend.Disk.create_sha1 ~sw tmp_path in 232 Alcotest.(check (option string)) 233 "recovered from WAL" (Some data) (backend.read hash); 234 (* Bloom filter should also have the entry *) 235 Alcotest.(check bool) "exists after recovery" true (backend.exists hash); 236 backend.close ()); 237 (* Clean up *) 238 let rec rm path = 239 if Eio.Path.is_directory path then begin 240 List.iter (fun name -> rm Eio.Path.(path / name)) (Eio.Path.read_dir path); 241 Eio.Path.rmdir path 242 end 243 else if Eio.Path.is_file path then Eio.Path.unlink path 244 in 245 rm tmp_path 246 247(* Store tests *) 248let test_store_commit () = 249 let backend = Backend.Memory.create_sha1 () in 250 let store = Store.Git.create ~backend in 251 let tree = Tree.Git.empty () in 252 let tree = Tree.Git.add tree [ "README.md" ] "# Hello" in 253 let hash = 254 Store.Git.commit store ~tree ~parents:[] ~message:"Initial commit" 255 ~author:"test" 256 in 257 Alcotest.(check bool) "commit hash exists" true (backend.exists hash) 258 259let test_store_branches () = 260 let backend = Backend.Memory.create_sha1 () in 261 let store = Store.Git.create ~backend in 262 let tree = Tree.Git.empty () in 263 let hash = 264 Store.Git.commit store ~tree ~parents:[] ~message:"test" ~author:"test" 265 in 266 Store.Git.set_head store ~branch:"main" hash; 267 let branches = Store.Git.branches store in 268 Alcotest.(check (list string)) "branches" [ "main" ] branches 269 270(* Tree format tests *) 271let test_git_tree_format () = 272 let node = Codec.Git.empty_node in 273 Alcotest.(check bool) "empty is empty" true (Codec.Git.is_empty node); 274 let h = Hash.sha1 "content" in 275 let node = Codec.Git.add node "file.txt" (`Contents h) in 276 Alcotest.(check bool) "not empty after add" false (Codec.Git.is_empty node); 277 match Codec.Git.find node "file.txt" with 278 | Some (`Contents h') -> 279 Alcotest.(check bool) "find matches" true (Hash.equal h h') 280 | _ -> Alcotest.fail "entry not found" 281 282let test_git_tree_serialization () = 283 let h = Hash.sha1 "content" in 284 let node = Codec.Git.empty_node in 285 let node = Codec.Git.add node "file.txt" (`Contents h) in 286 let bytes = Codec.Git.bytes_of_node node in 287 match Codec.Git.node_of_bytes bytes with 288 | Ok node' -> 289 let entries = Codec.Git.list node' in 290 Alcotest.(check int) "one entry" 1 (List.length entries) 291 | Error (`Msg msg) -> Alcotest.fail msg 292 293(* Test suites *) 294let hash_tests = 295 [ 296 Alcotest.test_case "sha1 hash" `Quick test_sha1_hash; 297 Alcotest.test_case "sha256 hash" `Quick test_sha256_hash; 298 Alcotest.test_case "hash roundtrip" `Quick test_hash_roundtrip; 299 Alcotest.test_case "mst depth" `Quick test_mst_depth; 300 ] 301 302let tree_tests = 303 [ 304 Alcotest.test_case "empty tree" `Quick test_empty_tree; 305 Alcotest.test_case "tree add/find" `Quick test_tree_add_find; 306 Alcotest.test_case "tree remove" `Quick test_tree_remove; 307 Alcotest.test_case "tree overwrite" `Quick test_tree_overwrite; 308 Alcotest.test_case "tree nested" `Quick test_tree_nested; 309 ] 310 311let backend_tests = 312 [ 313 Alcotest.test_case "memory backend" `Quick test_memory_backend; 314 Alcotest.test_case "backend refs" `Quick test_backend_refs; 315 Alcotest.test_case "backend test_and_set" `Quick test_backend_test_and_set; 316 Alcotest.test_case "disk backend" `Quick test_disk_backend; 317 Alcotest.test_case "disk backend persistence" `Quick 318 test_disk_backend_persistence; 319 Alcotest.test_case "disk backend refs" `Quick test_disk_backend_refs; 320 Alcotest.test_case "disk backend write_batch" `Quick 321 test_disk_backend_write_batch; 322 Alcotest.test_case "disk backend WAL recovery" `Quick 323 test_disk_backend_wal_recovery; 324 ] 325 326let test_store_diff () = 327 let backend = Backend.Memory.create_sha1 () in 328 let store = Store.Git.create ~backend in 329 (* Create first commit with two files *) 330 let tree1 = Tree.Git.empty () in 331 let tree1 = Tree.Git.add tree1 [ "file1.txt" ] "content1" in 332 let tree1 = Tree.Git.add tree1 [ "file2.txt" ] "content2" in 333 let hash1 = Tree.Git.hash tree1 ~backend in 334 (* Create second tree: modify file1, remove file2, add file3 *) 335 let tree2 = Tree.Git.empty () in 336 let tree2 = Tree.Git.add tree2 [ "file1.txt" ] "modified1" in 337 let tree2 = Tree.Git.add tree2 [ "file3.txt" ] "content3" in 338 let hash2 = Tree.Git.hash tree2 ~backend in 339 (* Compute diff *) 340 let changes = Store.Git.diff store ~old:hash1 ~new_:hash2 |> List.of_seq in 341 (* Check we have the expected changes *) 342 let has_remove_file2 = 343 List.exists 344 (function `Remove [ "file2.txt" ] -> true | _ -> false) 345 changes 346 in 347 let has_add_file3 = 348 List.exists 349 (function `Add ([ "file3.txt" ], _) -> true | _ -> false) 350 changes 351 in 352 let has_change_file1 = 353 List.exists 354 (function `Change ([ "file1.txt" ], _, _) -> true | _ -> false) 355 changes 356 in 357 Alcotest.(check bool) "file2 removed" true has_remove_file2; 358 Alcotest.(check bool) "file3 added" true has_add_file3; 359 Alcotest.(check bool) "file1 changed" true has_change_file1 360 361let store_tests = 362 [ 363 Alcotest.test_case "store commit" `Quick test_store_commit; 364 Alcotest.test_case "store branches" `Quick test_store_branches; 365 Alcotest.test_case "store diff" `Quick test_store_diff; 366 ] 367 368let test_git_inline_roundtrip () = 369 let node = Codec.Git.empty_node in 370 let node = Codec.Git.add node "small" (`Contents_inlined "hello") in 371 let h = Hash.sha1 "some content" in 372 let node = Codec.Git.add node "big" (`Contents h) in 373 let node = Codec.Git.add node "dir" (`Node h) in 374 let bytes = Codec.Git.bytes_of_node node in 375 match Codec.Git.node_of_bytes bytes with 376 | Error (`Msg m) -> Alcotest.fail m 377 | Ok node' -> 378 (match Codec.Git.find node' "small" with 379 | Some (`Contents_inlined s) -> 380 Alcotest.(check string) "inlined content" "hello" s 381 | _ -> Alcotest.fail "inlined entry not found"); 382 (match Codec.Git.find node' "big" with 383 | Some (`Contents h') -> 384 Alcotest.(check bool) "hash content" true (Hash.equal h h') 385 | _ -> Alcotest.fail "hash entry not found"); 386 (match Codec.Git.find node' "dir" with 387 | Some (`Node h') -> 388 Alcotest.(check bool) "node entry" true (Hash.equal h h') 389 | _ -> Alcotest.fail "node entry not found"); 390 let entries = Codec.Git.list node' in 391 Alcotest.(check int) "3 entries" 3 (List.length entries) 392 393let test_tree_inline_write_read () = 394 let backend = Backend.Memory.create_sha1 () in 395 let tree = Tree.Git.empty () in 396 let tree = Tree.Git.add tree [ "small" ] "hi" in 397 let tree = Tree.Git.add tree [ "big" ] (String.make 100 'x') in 398 (* Write with inlining enabled *) 399 let hash = Tree.Git.hash ~inline_threshold:48 tree ~backend in 400 (* Read back *) 401 let tree2 = Tree.Git.of_hash ~backend hash in 402 Alcotest.(check (option string)) 403 "small inlined" (Some "hi") 404 (Tree.Git.find tree2 [ "small" ]); 405 Alcotest.(check (option string)) 406 "big not inlined" (Some (String.make 100 'x')) 407 (Tree.Git.find tree2 [ "big" ]); 408 let entries = Tree.Git.list tree2 [] in 409 Alcotest.(check int) "2 entries" 2 (List.length entries) 410 411let test_tree_inline_nested () = 412 let backend = Backend.Memory.create_sha1 () in 413 let tree = Tree.Git.empty () in 414 let tree = Tree.Git.add tree [ "dir"; "a" ] "small value" in 415 let tree = Tree.Git.add tree [ "dir"; "b" ] (String.make 100 'y') in 416 let tree = Tree.Git.add tree [ "root" ] "top" in 417 let hash = Tree.Git.hash ~inline_threshold:48 tree ~backend in 418 let tree2 = Tree.Git.of_hash ~backend hash in 419 Alcotest.(check (option string)) 420 "nested small" (Some "small value") 421 (Tree.Git.find tree2 [ "dir"; "a" ]); 422 Alcotest.(check (option string)) 423 "nested big" (Some (String.make 100 'y')) 424 (Tree.Git.find tree2 [ "dir"; "b" ]); 425 Alcotest.(check (option string)) 426 "top level" (Some "top") 427 (Tree.Git.find tree2 [ "root" ]) 428 429let tree_format_tests = 430 [ 431 Alcotest.test_case "git tree format" `Quick test_git_tree_format; 432 Alcotest.test_case "git tree serialization" `Quick 433 test_git_tree_serialization; 434 Alcotest.test_case "git inline roundtrip" `Quick test_git_inline_roundtrip; 435 Alcotest.test_case "tree inline write/read" `Quick 436 test_tree_inline_write_read; 437 Alcotest.test_case "tree inline nested" `Quick test_tree_inline_nested; 438 ] 439 440(* Link tests *) 441let test_link_v_get () = 442 let s = Link.Mst.v () in 443 let l = Link.v s 42 in 444 Alcotest.(check int) "get (v s x) = x" 42 (Link.get l) 445 446let test_link_is_val () = 447 let s = Link.Mst.v () in 448 let l = Link.v s "hello" in 449 Alcotest.(check bool) "in-memory is_val" true (Link.is_val l) 450 451let test_link_equal () = 452 let s = Link.Mst.v () in 453 let l0 = Link.v s [ 1; 2; 3 ] in 454 let l1 = Link.v s [ 1; 2; 3 ] in 455 let l2 = Link.v s [ 1; 2; 4 ] in 456 Alcotest.(check bool) "same value equal" true (Link.equal l0 l1); 457 Alcotest.(check bool) "diff value not equal" false (Link.equal l0 l2) 458 459let test_link_address () = 460 let s = Link.Mst.v () in 461 let l0 = Link.v s "test" in 462 let l1 = Link.v s "test" in 463 Alcotest.(check bool) "same address" true (Link.address l0 = Link.address l1) 464 465let test_link_pp () = 466 let s = Link.Mst.v () in 467 let l = Link.v s "test" in 468 let _ = Link.address l in 469 (* force address computation *) 470 let str = Format.asprintf "%a" Link.pp l in 471 Alcotest.(check int) "pp is 7 chars" 7 (String.length str) 472 473let test_link_read_write () = 474 let s : int Link.store = Link.Mst.v () in 475 Link.write s 42; 476 Alcotest.(check int) "after write" 42 (Link.read s); 477 Link.write s 100; 478 Alcotest.(check int) "after second write" 100 (Link.read s) 479 480let test_link_is_open () = 481 let s = Link.Mst.v () in 482 Alcotest.(check bool) "initially open" true (Link.is_open s); 483 Link.close s; 484 Alcotest.(check bool) "closed after close" false (Link.is_open s) 485 486(* Tree types for the tree example test *) 487type test_tree = test_node Link.t 488and test_node = TEmpty | TNode of { l : test_tree; x : int; r : test_tree } 489 490let test_link_tree () = 491 let s = Link.Mst.v () in 492 let empty = Link.v s TEmpty in 493 let leaf x = Link.v s (TNode { l = empty; x; r = empty }) in 494 let node l x r = Link.v s (TNode { l; x; r }) in 495 let t = node (leaf 1) 2 (leaf 3) in 496 match Link.get t with 497 | TEmpty -> Alcotest.fail "expected node" 498 | TNode n -> ( 499 Alcotest.(check int) "root" 2 n.x; 500 match (Link.get n.l, Link.get n.r) with 501 | TNode l, TNode r -> 502 Alcotest.(check int) "left" 1 l.x; 503 Alcotest.(check int) "right" 3 r.x 504 | _ -> Alcotest.fail "expected leaves") 505 506let link_tests = 507 [ 508 Alcotest.test_case "v/get" `Quick test_link_v_get; 509 Alcotest.test_case "is_val" `Quick test_link_is_val; 510 Alcotest.test_case "equal" `Quick test_link_equal; 511 Alcotest.test_case "address" `Quick test_link_address; 512 Alcotest.test_case "pp" `Quick test_link_pp; 513 Alcotest.test_case "read/write" `Quick test_link_read_write; 514 Alcotest.test_case "is_open" `Quick test_link_is_open; 515 Alcotest.test_case "tree" `Quick test_link_tree; 516 ] 517 518(* Proof tests *) 519let test_proof_produce_verify () = 520 let backend = Backend.Memory.create_sha1 () in 521 (* Build a tree: foo/bar = "hello", foo/baz = "world" *) 522 let tree = Tree.Git.empty () in 523 let tree = Tree.Git.add tree [ "foo"; "bar" ] "hello" in 524 let tree = Tree.Git.add tree [ "foo"; "baz" ] "world" in 525 let root_hash = Tree.Git.hash tree ~backend in 526 (* Produce a proof that only accesses foo/bar *) 527 let proof, result = 528 Proof.Git.produce backend root_hash (fun t -> 529 let v = Proof.Git.Tree.find t [ "foo"; "bar" ] in 530 (t, v)) 531 in 532 Alcotest.(check (option string)) "found value" (Some "hello") result; 533 (* Verify the proof *) 534 match 535 Proof.Git.verify proof (fun t -> 536 let v = Proof.Git.Tree.find t [ "foo"; "bar" ] in 537 (t, v)) 538 with 539 | Ok (_, v) -> 540 Alcotest.(check (option string)) "verified value" (Some "hello") v 541 | Error (`Proof_mismatch msg) -> Alcotest.fail ("proof mismatch: " ^ msg) 542 543let test_proof_blinded () = 544 let backend = Backend.Memory.create_sha1 () in 545 let tree = Tree.Git.empty () in 546 let tree = Tree.Git.add tree [ "a" ] "1" in 547 let tree = Tree.Git.add tree [ "b" ] "2" in 548 let root_hash = Tree.Git.hash tree ~backend in 549 (* Only access "a", "b" should be blinded *) 550 let proof, _ = 551 Proof.Git.produce backend root_hash (fun t -> 552 let _ = Proof.Git.Tree.find t [ "a" ] in 553 (t, ())) 554 in 555 (* Check proof state has blinded nodes *) 556 let state = Proof.state proof in 557 match state with 558 | Proof.Node entries -> 559 let has_a = 560 List.exists 561 (fun (k, v) -> 562 k = "a" && match v with Proof.Contents "1" -> true | _ -> false) 563 entries 564 in 565 let has_blinded_b = 566 List.exists 567 (fun (k, v) -> 568 k = "b" 569 && match v with Proof.Blinded_contents _ -> true | _ -> false) 570 entries 571 in 572 Alcotest.(check bool) "has a" true has_a; 573 Alcotest.(check bool) "b is blinded" true has_blinded_b 574 | _ -> Alcotest.fail "expected Node" 575 576let test_proof_mst () = 577 let backend = Backend.Memory.create_sha256 () in 578 let tree = Tree.Mst.empty () in 579 let tree = Tree.Mst.add tree [ "key1" ] "value1" in 580 let tree = Tree.Mst.add tree [ "key2" ] "value2" in 581 let root_hash = Tree.Mst.hash tree ~backend in 582 let proof, result = 583 Proof.Mst.produce backend root_hash (fun t -> 584 let v = Proof.Mst.Tree.find t [ "key1" ] in 585 (t, v)) 586 in 587 Alcotest.(check (option string)) "found value" (Some "value1") result; 588 match 589 Proof.Mst.verify proof (fun t -> 590 let v = Proof.Mst.Tree.find t [ "key1" ] in 591 (t, v)) 592 with 593 | Ok (_, v) -> 594 Alcotest.(check (option string)) "verified value" (Some "value1") v 595 | Error (`Proof_mismatch msg) -> Alcotest.fail ("proof mismatch: " ^ msg) 596 597let proof_tests = 598 [ 599 Alcotest.test_case "produce/verify" `Quick test_proof_produce_verify; 600 Alcotest.test_case "blinded nodes" `Quick test_proof_blinded; 601 Alcotest.test_case "mst proofs" `Quick test_proof_mst; 602 ] 603 604let () = 605 Alcotest.run "Irmin" 606 [ 607 ("Hash", hash_tests); 608 ("Tree", tree_tests); 609 ("Backend", backend_tests); 610 ("Store", store_tests); 611 ("Codec", tree_format_tests); 612 ("Link", link_tests); 613 ("Proof", proof_tests); 614 ]