char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
78f4748
main
1use std:: path:: Path ; 2 3use super ::{ 4Access , BatchOp , Decision , ListedEntry , MAX_BATCH_BYTES , MAX_BATCH_OPS , Store , StoredEntry , 5 parse_stamp, 6}; 7 8// One-transaction shorthands, so tests read like the adapters' single ops. 9trait Shorthand { 10fn get ( & self , keyspace : & str , key : & [ u8 ]) -> std:: io:: Result < Option < StoredEntry >>; 11fn get_many ( & self , gets : & [( String , Vec < u8 >)]) -> std:: io:: Result < Vec < Option < StoredEntry >>>; 12fn set ( & self , keyspace : & str , key : & [ u8 ], value : & [ u8 ]) -> std:: io:: Result < String >; 13fn clear ( & self , keyspace : & str ) -> std:: io:: Result <()>; 14fn count ( & self , keyspace : & str ) -> std:: io:: Result < u64 >; 15fn list_keyspaces ( & self ) -> std:: io:: Result < Vec < String >>; 16fn list ( 17& self , 18keyspace : & str , 19start : Option < & [ u8 ]>, 20end : Option < & [ u8 ]>, 21prefix : Option < & [ u8 ]>, 22limit : usize , 23reverse : bool , 24) -> std:: io:: Result < Vec < ListedEntry >>; 25} 26 27impl Shorthand for Store { 28fn get ( & self , keyspace : & str , key : & [ u8 ]) -> std:: io:: Result < Option < StoredEntry >> { 29self . read_tx ( |tx| tx. get ( keyspace, key)) 30} 31 32fn get_many ( & self , gets : & [( String , Vec < u8 >)]) -> std:: io:: Result < Vec < Option < StoredEntry >>> { 33self . read_tx ( |tx| gets. iter (). map ( |( ks, key) | tx. get ( ks, key)). collect ()) 34} 35 36fn set ( & self , keyspace : & str , key : & [ u8 ], value : & [ u8 ]) -> std:: io:: Result < String > { 37self . write_tx ( |tx| tx. put ( keyspace, key, value). map ( Decision :: Commit )) 38} 39 40fn clear ( & self , keyspace : & str ) -> std:: io:: Result <()> { 41self . write_tx ( |tx| tx. clear ( keyspace). map ( Decision :: Commit )) 42} 43 44fn count ( & self , keyspace : & str ) -> std:: io:: Result < u64 > { 45self . read_tx ( |tx| tx. count ( keyspace)) 46} 47 48fn list_keyspaces ( & self ) -> std:: io:: Result < Vec < String >> { 49self . read_tx ( |tx| tx. list_keyspaces ()) 50} 51 52fn list ( 53& self , 54keyspace : & str , 55start : Option < & [ u8 ]>, 56end : Option < & [ u8 ]>, 57prefix : Option < & [ u8 ]>, 58limit : usize , 59reverse : bool , 60) -> std:: io:: Result < Vec < ListedEntry >> { 61self . read_tx ( |tx| tx. list ( keyspace, start, end, prefix, limit, reverse)) 62} 63} 64 65fn open_shared ( dir : & Path , name : & str ) ->Store { 66Store :: open ( & dir. join ( name), Access :: Shared , true ). unwrap () 67} 68 69fn check ( keyspace : & str , key : & [ u8 ], expected : Option < & str >) ->BatchOp { 70BatchOp :: Check { 71keyspace : keyspace. into (), 72key : key. to_vec (), 73expected : expected. map ( Into :: into), 74} 75} 76 77fn set ( keyspace : & str , key : & [ u8 ], value : & [ u8 ]) ->BatchOp { 78BatchOp :: Set { 79keyspace : keyspace. into (), 80key : key. to_vec (), 81value : value. to_vec (), 82} 83} 84 85fn delete ( keyspace : & str , key : & [ u8 ]) ->BatchOp { 86BatchOp :: Delete { 87keyspace : keyspace. into (), 88key : key. to_vec (), 89} 90} 91 92fn get ( store : & Store , keyspace : & str , key : & [ u8 ]) ->Option <( Vec < u8 >, String )> { 93 store. get ( keyspace, key). unwrap () 94} 95 96fn assert_send_sync < T : Send +Sync >() {} 97 98# [ test ] 99fn store_is_send_sync_and_cloneable () { 100assert_send_sync ::< Store >(); 101let root = tempfile:: tempdir (). unwrap (); 102let store =open_shared ( root. path (), "kv.redb" ); 103let _clone = store. clone (); 104} 105 106# [ test ] 107fn set_get_roundtrip () { 108let root = tempfile:: tempdir (). unwrap (); 109let store =open_shared ( root. path (), "kv.redb" ); 110let stamp = store. set ( "ks" , b"key" , b"value" ). unwrap (); 111assert_eq! ( 112get ( & store, "ks" , b"key" ). unwrap (), 113( b"value" . to_vec (), stamp) 114); 115assert_eq! ( store. get ( "ks" , b"missing" ). unwrap (), None ); 116assert_eq! ( store. get ( "other" , b"key" ). unwrap (), None ); 117} 118 119# [ test ] 120fn persists_across_reopen_in_shared_mode () { 121let root = tempfile:: tempdir (). unwrap (); 122let path = root. path (). join ( "kv.redb" ); 123let store =Store :: open ( & path, Access :: Shared , true ). unwrap (); 124let stamp = store. set ( "ks" , b"key" , b"value" ). unwrap (); 125drop ( store); 126let store =Store :: open ( & path, Access :: Shared , false ). unwrap (); 127assert_eq! ( 128get ( & store, "ks" , b"key" ). unwrap (), 129( b"value" . to_vec (), stamp) 130); 131} 132 133# [ test ] 134fn exclusive_blocks_second_opener () { 135let root = tempfile:: tempdir (). unwrap (); 136let path = root. path (). join ( "kv.redb" ); 137let _first =Store :: open ( & path, Access :: Exclusive , true ). unwrap (); 138let error =Store :: open ( & path, Access :: Exclusive , true ). unwrap_err (); 139assert_eq! ( error. kind (), std:: io:: ErrorKind :: AlreadyExists ); 140let error =Store :: open ( & path, Access :: Shared , false ). unwrap_err (); 141assert_eq! ( error. kind (), std:: io:: ErrorKind :: AlreadyExists ); 142} 143 144# [ test ] 145fn read_only_does_not_create () { 146let root = tempfile:: tempdir (). unwrap (); 147let path = root. path (). join ( "kv.redb" ); 148let store =Store :: open ( & path, Access :: ReadOnly , false ). unwrap (); 149assert! ( !path. exists ()); 150assert_eq! ( store. get ( "ks" , b"key" ). unwrap (), None ); 151assert_eq! ( store. list_keyspaces (). unwrap (), Vec ::< String >:: new ()); 152assert_eq! ( store. count ( "ks" ). unwrap (), 0 ); 153assert! ( 154 store 155. list ( "ks" , None , None , None , 10 , false ) 156. unwrap () 157. is_empty () 158); 159let gets =[ 160( "ks" . to_owned (), b"a" . to_vec ()), 161( "ks" . to_owned (), b"b" . to_vec ()), 162]; 163assert_eq! ( store. get_many ( & gets). unwrap (), vec! [ None , None ]); 164let error = store. set ( "ks" , b"key" , b"value" ). unwrap_err (); 165assert_eq! ( error. kind (), std:: io:: ErrorKind :: PermissionDenied ); 166} 167 168# [ test ] 169fn keyspace_isolation_clear_and_list () { 170let root = tempfile:: tempdir (). unwrap (); 171let store =open_shared ( root. path (), "kv.redb" ); 172 store. set ( "a" , b"k" , b"1" ). unwrap (); 173 store. set ( "b" , b"k" , b"2" ). unwrap (); 174assert_eq! ( 175 store. list_keyspaces (). unwrap (), 176vec! [ "a" . to_owned (), "b" . to_owned ()] 177); 178 store. clear ( "a" ). unwrap (); 179assert_eq! ( store. get ( "a" , b"k" ). unwrap (), None ); 180assert_eq! ( get ( & store, "b" , b"k" ). unwrap (). 0 , b"2" ); 181assert_eq! ( 182 store. list_keyspaces (). unwrap (), 183vec! [ "a" . to_owned (), "b" . to_owned ()] 184); 185 store. clear ( "c" ). unwrap (); 186assert_eq! ( 187 store. list_keyspaces (). unwrap (), 188vec! [ "a" . to_owned (), "b" . to_owned (), "c" . to_owned ()] 189); 190} 191 192# [ test ] 193fn atomic_commit_success_across_keyspaces () { 194let root = tempfile:: tempdir (). unwrap (); 195let store =open_shared ( root. path (), "kv.redb" ); 196let stamp = store. set ( "a" , b"k1" , b"v1" ). unwrap (); 197let batch =[ 198check ( "a" , b"k1" , Some ( & stamp)), 199set ( "a" , b"k1" , b"v2" ), 200set ( "b" , b"k2" , b"w2" ), 201delete ( "c" , b"ghost" ), 202]; 203let ( ok, commit_stamp) = store. atomic_commit ( & batch). unwrap (); 204assert! ( ok); 205let commit_stamp = commit_stamp. unwrap (); 206assert_eq! ( 207get ( & store, "a" , b"k1" ). unwrap (), 208( b"v2" . to_vec (), commit_stamp. clone ()) 209); 210assert_eq! ( 211get ( & store, "b" , b"k2" ). unwrap (), 212( b"w2" . to_vec (), commit_stamp) 213); 214} 215 216# [ test ] 217fn atomic_check_failure_aborts_everything () { 218let root = tempfile:: tempdir (). unwrap (); 219let store =open_shared ( root. path (), "kv.redb" ); 220let stamp = store. set ( "a" , b"k1" , b"v1" ). unwrap (); 221let batch =[ 222check ( "a" , b"k1" , Some ( "255" )), 223set ( "a" , b"k1" , b"v2" ), 224set ( "b" , b"k2" , b"w2" ), 225]; 226let ( ok, commit_stamp) = store. atomic_commit ( & batch). unwrap (); 227assert! ( !ok); 228assert_eq! ( commit_stamp, None ); 229assert_eq! ( get ( & store, "a" , b"k1" ). unwrap (), ( b"v1" . to_vec (), stamp)); 230assert_eq! ( store. get ( "b" , b"k2" ). unwrap (), None ); 231} 232 233# [ test ] 234fn atomic_null_check_means_must_not_exist () { 235let root = tempfile:: tempdir (). unwrap (); 236let store =open_shared ( root. path (), "kv.redb" ); 237let batch =[ check ( "a" , b"k" , None ), set ( "a" , b"k" , b"v" )]; 238let ( ok, _) = store. atomic_commit ( & batch). unwrap (); 239assert! ( ok); 240let batch =[ check ( "a" , b"k" , None ), set ( "a" , b"k" , b"other" )]; 241let ( ok, _) = store. atomic_commit ( & batch). unwrap (); 242assert! ( !ok); 243assert_eq! ( get ( & store, "a" , b"k" ). unwrap (). 0 , b"v" ); 244} 245 246# [ test ] 247fn versionstamp_changes_on_rewrite_and_is_shared_within_a_commit () { 248let root = tempfile:: tempdir (). unwrap (); 249let store =open_shared ( root. path (), "kv.redb" ); 250let first = store. set ( "ks" , b"k" , b"v1" ). unwrap (); 251let second = store. set ( "ks" , b"k" , b"v2" ). unwrap (); 252assert_ne! ( first, second); 253let batch =[ set ( "a" , b"x" , b"1" ), set ( "b" , b"y" , b"2" )]; 254let ( ok, stamp) = store. atomic_commit ( & batch). unwrap (); 255assert! ( ok); 256let stamp = stamp. unwrap (); 257assert_eq! ( get ( & store, "a" , b"x" ). unwrap (). 1 , stamp); 258assert_eq! ( get ( & store, "b" , b"y" ). unwrap (). 1 , stamp); 259assert_ne! ( stamp, second); 260} 261 262# [ test ] 263fn list_ordering_prefix_limit_and_reverse () { 264let root = tempfile:: tempdir (). unwrap (); 265let store =open_shared ( root. path (), "kv.redb" ); 266for keyin [ b"c" . as_slice (), b"a" , b"ab" , b"aa" , b"b" ] { 267 store. set ( "ks" , key, key). unwrap (); 268} 269let keys = |entries : Vec <( Vec < u8 >, Vec < u8 >, String )> |{ 270 entries 271. into_iter () 272. map ( |( key, _, _) | key) 273. collect ::< Vec < _ >>() 274}; 275assert_eq! ( 276keys ( store. list ( "ks" , None , None , None , 100 , false ). unwrap ()), 277vec! [ 278b"a" . to_vec (), 279b"aa" . to_vec (), 280b"ab" . to_vec (), 281b"b" . to_vec (), 282b"c" . to_vec () 283] 284); 285assert_eq! ( 286keys ( 287 store 288. list ( "ks" , None , None , Some ( b"a" ), 100 , false ) 289. unwrap () 290), 291vec! [ b"a" . to_vec (), b"aa" . to_vec (), b"ab" . to_vec ()] 292); 293assert_eq! ( 294keys ( store. list ( "ks" , None , None , Some ( b"a" ), 2 , false ). unwrap ()), 295vec! [ b"a" . to_vec (), b"aa" . to_vec ()] 296); 297assert_eq! ( 298keys ( store. list ( "ks" , None , None , Some ( b"a" ), 100 , true ). unwrap ()), 299vec! [ b"ab" . to_vec (), b"aa" . to_vec (), b"a" . to_vec ()] 300); 301assert_eq! ( 302keys ( 303 store 304. list ( "ks" , Some ( b"aa" ), Some ( b"c" ), None , 100 , false ) 305. unwrap () 306), 307vec! [ b"aa" . to_vec (), b"ab" . to_vec (), b"b" . to_vec ()] 308); 309assert! ( 310 store 311. list ( "ks" , None , None , Some ( b"zzz" ), 100 , false ) 312. unwrap () 313. is_empty () 314); 315assert! ( 316 store 317. list ( "missing" , None , None , None , 100 , false ) 318. unwrap () 319. is_empty () 320); 321} 322 323# [ test ] 324fn get_many_reads_one_snapshot () { 325let root = tempfile:: tempdir (). unwrap (); 326let store =open_shared ( root. path (), "kv.redb" ); 327let first = store. set ( "a" , b"k1" , b"v1" ). unwrap (); 328let gets =vec! [ 329( "a" . to_owned (), b"k1" . to_vec ()), 330( "a" . to_owned (), b"absent" . to_vec ()), 331( "b" . to_owned (), b"k1" . to_vec ()), 332]; 333assert_eq! ( 334 store. get_many ( & gets). unwrap (), 335vec! [ Some (( b"v1" . to_vec (), first)), None , None ] 336); 337} 338 339# [ test ] 340fn failed_atomic_changes_nothing_including_metadata () { 341let root = tempfile:: tempdir (). unwrap (); 342let store =open_shared ( root. path (), "kv.redb" ); 343let batch =[ 344check ( "a" , b"k1" , Some ( "1" )), 345set ( "a" , b"k1" , b"v1" ), 346set ( "b" , b"k2" , b"v2" ), 347]; 348let ( ok, stamp) = store. atomic_commit ( & batch). unwrap (); 349assert! ( !ok); 350assert_eq! ( stamp, None ); 351assert! ( store. list_keyspaces (). unwrap (). is_empty ()); 352assert_eq! ( store. get ( "a" , b"k1" ). unwrap (), None ); 353assert_eq! ( store. get ( "b" , b"k2" ). unwrap (), None ); 354} 355 356# [ test ] 357fn check_only_batch_burns_no_versionstamp () { 358let root = tempfile:: tempdir (). unwrap (); 359let store =open_shared ( root. path (), "kv.redb" ); 360let first = store. set ( "ks" , b"k" , b"v1" ). unwrap (); 361let batch =[ check ( "ks" , b"k" , Some ( & first))]; 362let ( ok, stamp) = store. atomic_commit ( & batch). unwrap (); 363assert! ( ok); 364assert_eq! ( stamp, None ); 365let second = store. set ( "ks" , b"k" , b"v2" ). unwrap (); 366assert_eq! ( 367parse_stamp ( & second). unwrap (), 368parse_stamp ( & first). unwrap () +1 369); 370} 371 372# [ test ] 373fn malformed_versionstamp_is_invalid_input () { 374let root = tempfile:: tempdir (). unwrap (); 375let store =open_shared ( root. path (), "kv.redb" ); 376 store. set ( "ks" , b"k" , b"v" ). unwrap (); 377for badin [ 378"not-a-stamp" , 379"" , 380"-1" , 381"+1" , 382"0x10" , 383"1.0" , 384"18446744073709551616" , 385] { 386let batch =[ check ( "ks" , b"k" , Some ( bad)), set ( "ks" , b"k" , b"other" )]; 387assert_eq! ( 388 store. atomic_commit ( & batch). unwrap_err (). kind (), 389 std:: io:: ErrorKind :: InvalidInput , 390"stamp {bad:?} must be rejected" , 391); 392} 393assert_eq! ( get ( & store, "ks" , b"k" ). unwrap (). 0 , b"v" ); 394} 395 396# [ test ] 397fn missing_file_without_create_fails () { 398let root = tempfile:: tempdir (). unwrap (); 399for accessin [ Access :: Shared , Access :: Exclusive ] { 400let error =Store :: open ( & root. path (). join ( "missing.redb" ), access, false ). unwrap_err (); 401assert_eq! ( error. kind (), std:: io:: ErrorKind :: NotFound ); 402} 403assert! ( !root. path (). join ( "missing.redb" ). exists ()); 404} 405 406# [ test ] 407fn invalid_inputs_are_rejected () { 408let root = tempfile:: tempdir (). unwrap (); 409let store =open_shared ( root. path (), "kv.redb" ); 410assert_eq! ( 411Access :: parse ( "bogus" ). unwrap_err (). kind (), 412 std:: io:: ErrorKind :: InvalidInput 413); 414assert_eq! ( 415 store. set ( "" , b"k" , b"v" ). unwrap_err (). kind (), 416 std:: io:: ErrorKind :: InvalidInput 417); 418assert_eq! ( 419 store 420. atomic_commit ( & [ set ( "" , b"k" , b"v" )]) 421. unwrap_err () 422. kind (), 423 std:: io:: ErrorKind :: InvalidInput 424); 425assert_eq! ( 426Store :: open ( & root. path (). join ( "ro.redb" ), Access :: ReadOnly , true ) 427. unwrap_err () 428. kind (), 429 std:: io:: ErrorKind :: InvalidInput 430); 431} 432 433# [ test ] 434fn write_tx_commit_applies_and_abort_changes_nothing () { 435let root = tempfile:: tempdir (). unwrap (); 436let store =open_shared ( root. path (), "kv.redb" ); 437let stamp = store 438. write_tx ( |tx|{ 439 tx. put ( "ks" , b"k" , b"v" ) ?; 440Ok ( Decision :: Commit ( tx. stamp (). unwrap ())) 441}) 442. unwrap (); 443assert_eq! ( get ( & store, "ks" , b"k" ). unwrap (), ( b"v" . to_vec (), stamp)); 444let before = store. list_keyspaces (). unwrap (); 445let ( aborted, missing) = store 446. write_tx ( |tx|{ 447 tx. put ( "fresh" , b"k" , b"v" ) ?; 448 tx. put_meta ( "adapter/counter" , 7 ) ?; 449assert_eq! ( tx. get ( "fresh" , b"k" ) ?. unwrap (). 0 , b"v" ); 450assert_eq! ( tx. get_meta ( "adapter/counter" ) ?, Some ( 7 )); 451Ok ( Decision :: Abort (( false , true ))) 452}) 453. unwrap (); 454assert! ( !aborted && missing); 455assert_eq! ( store. get ( "fresh" , b"k" ). unwrap (), None ); 456assert_eq! ( store. list_keyspaces (). unwrap (), before); 457 store 458. read_tx ( |tx|{ 459assert_eq! ( tx. get_meta ( "adapter/counter" ) ?, None ); 460Ok (()) 461}) 462. unwrap (); 463} 464 465# [ test ] 466fn write_tx_sees_its_own_writes () { 467let root = tempfile:: tempdir (). unwrap (); 468let store =open_shared ( root. path (), "kv.redb" ); 469 store 470. write_tx ( |tx|{ 471 tx. put ( "ks" , b"k" , b"one" ) ?; 472let first = tx. get ( "ks" , b"k" ) ?. unwrap (); 473 tx. put ( "ks" , b"k" , b"two" ) ?; 474let second = tx. get ( "ks" , b"k" ) ?. unwrap (); 475assert_eq! ( first. 1 , second. 1 , "puts in one transaction share the stamp" ); 476assert! ( tx. delete ( "ks" , b"k" ) ?); 477assert! ( !tx. delete ( "ks" , b"k" ) ?, "second delete reports absence" ); 478assert_eq! ( tx. get ( "ks" , b"k" ) ?, None ); 479 tx. clear ( "ks" ) ?; 480assert! ( tx. list_keyspaces () ?. contains ( & "ks" . to_owned ())); 481Ok ( Decision :: Commit (())) 482}) 483. unwrap (); 484assert_eq! ( store. get ( "ks" , b"k" ). unwrap (), None ); 485} 486 487# [ test ] 488fn metadata_roundtrips_and_the_counter_slot_is_rejected () { 489let root = tempfile:: tempdir (). unwrap (); 490let store =open_shared ( root. path (), "kv.redb" ); 491 store 492. write_tx ( |tx|{ 493 tx. put_meta ( "webstorage/bytes_used" , 4096 ) ?; 494assert_eq! ( tx. get_meta ( "webstorage/bytes_used" ) ?, Some ( 4096 )); 495 tx. put_meta ( "webstorage/bytes_used" , 8192 ) ?; 496Ok ( Decision :: Commit (())) 497}) 498. unwrap (); 499 store 500. read_tx ( |tx|{ 501assert_eq! ( tx. get_meta ( "webstorage/bytes_used" ) ?, Some ( 8192 )); 502assert_eq! ( tx. get_meta ( "webstorage/other" ) ?, None ); 503Ok (()) 504}) 505. unwrap (); 506// "" is the commit counter's slot. 507assert_eq! ( 508 store 509. write_tx ( |tx|{ 510 tx. put_meta ( "" , 1 ) ?; 511Ok ( Decision :: Commit (())) 512}) 513. unwrap_err () 514. kind (), 515 std:: io:: ErrorKind :: InvalidInput , 516); 517assert_eq! ( 518 store. read_tx ( |tx| tx. get_meta ( "" )). unwrap_err (). kind (), 519 std:: io:: ErrorKind :: InvalidInput , 520); 521// Adapter metadata coexists with backend state without interference. 522 store. set ( "ks" , b"k" , b"v" ). unwrap (); 523assert_eq! ( 524 store 525. read_tx ( |tx| tx. get_meta ( "webstorage/bytes_used" )) 526. unwrap (), 527Some ( 8192 ) 528); 529assert_eq! ( get ( & store, "ks" , b"k" ). unwrap (). 0 , b"v" ); 530} 531 532# [ test ] 533fn entries_and_count_cover_uncommitted_and_committed_state () { 534let root = tempfile:: tempdir (). unwrap (); 535let store =open_shared ( root. path (), "kv.redb" ); 536assert_eq! ( store. count ( "missing" ). unwrap (), 0 ); 537 store 538. write_tx ( |tx|{ 539assert_eq! ( tx. count ( "ks" ). unwrap (), 0 ); 540 tx. put ( "ks" , b"b" , b"2" ) ?; 541 tx. put ( "ks" , b"a" , b"1" ) ?; 542// Full-scan entries in byte order, including uncommitted writes. 543let keys: Vec < Vec < u8 >> = tx 544. entries ( "ks" ) ? 545. into_iter () 546. map ( |( key, _, _) | key) 547. collect (); 548assert_eq! ( keys, vec! [ b"a" . to_vec (), b"b" . to_vec ()]); 549assert_eq! ( tx. count ( "ks" ). unwrap (), 2 ); 550Ok ( Decision :: Commit (())) 551}) 552. unwrap (); 553assert_eq! ( store. count ( "ks" ). unwrap (), 2 ); 554 store 555. read_tx ( |tx|{ 556assert_eq! ( tx. entries ( "missing" ). unwrap (), Vec :: new ()); 557Ok (()) 558}) 559. unwrap (); 560 store. clear ( "ks" ). unwrap (); 561assert_eq! ( store. count ( "ks" ). unwrap (), 0 ); 562} 563 564# [ test ] 565fn oversized_batches_are_rejected () { 566let root = tempfile:: tempdir (). unwrap (); 567let store =open_shared ( root. path (), "kv.redb" ); 568let too_large =[ set ( "ks" , b"k" , & vec! [ 0 ; MAX_BATCH_BYTES ])]; 569let too_many =vec! [ delete ( "ks" , b"k" ); MAX_BATCH_OPS +1 ]; 570for batchin [ too_large. as_slice (), & too_many] { 571assert_eq! ( 572 store. atomic_commit ( batch). unwrap_err (). kind (), 573 std:: io:: ErrorKind :: InvalidInput 574); 575} 576assert_eq! ( store. get ( "ks" , b"k" ). unwrap (), None ); 577} 578 579# [ test ] 580fn open_read_only_repairing_recovers_unclean_shutdown () { 581// Child mode: commit a value, then exit without dropping the store, 582// leaving the file marked as needing repair. 583if let Ok ( cache) = std:: env:: var ( "AVRIL_KV_UNCLEAN_CACHE" ) { 584let path =Path :: new ( & cache). join ( "kv.redb" ); 585let store =Store :: open ( & path, Access :: Exclusive , true ). unwrap (); 586 store. set ( "ks" , b"key" , b"unclean" ). unwrap (); 587 std:: process:: exit ( 0 ); 588} 589let dir = tempfile:: tempdir (). unwrap (); 590let status = std:: process:: Command :: new ( std:: env:: current_exe (). unwrap ()) 591. args ([ 592"--exact" , 593"tests::open_read_only_repairing_recovers_unclean_shutdown" , 594]) 595. env ( "AVRIL_KV_UNCLEAN_CACHE" , dir. path ()) 596. stdout ( std:: process:: Stdio :: null ()) 597. status () 598. unwrap (); 599assert! ( status. success ()); 600let store =Store :: open_read_only_repairing ( & dir. path (). join ( "kv.redb" )). unwrap (); 601let entry = store 602. get ( "ks" , b"key" ) 603. unwrap () 604. expect ( "repaired value readable" ); 605assert_eq! ( entry. 0 , b"unclean" . to_vec ()); 606parse_stamp ( & entry. 1 ). unwrap (); 607// A missing file opens as empty without creating anything. 608let missing = dir. path (). join ( "absent.redb" ); 609let store =Store :: open_read_only_repairing ( & missing). unwrap (); 610assert_eq! ( store. get ( "ks" , b"key" ). unwrap (), None ); 611assert! ( !missing. exists ()); 612} 613 614# [ test ] 615fn write_tx_reads_and_deletes_materialize_nothing () { 616let root = tempfile:: tempdir (). unwrap (); 617let store =open_shared ( root. path (), "kv.redb" ); 618 store 619. write_tx ( |tx|{ 620assert_eq! ( tx. get ( "ghost" , b"k" ) ?, None ); 621assert! ( !tx. delete ( "ghost" , b"k" ) ?); 622assert_eq! ( tx. count ( "ghost" ) ?, 0 ); 623assert_eq! ( tx. get_meta ( "adapter" ) ?, None ); 624Ok ( Decision :: Commit (())) 625}) 626. unwrap (); 627assert! ( store. list_keyspaces (). unwrap (). is_empty ()); 628 store. clear ( "empty" ). unwrap (); 629assert_eq! ( store. list_keyspaces (). unwrap (), vec! [ "empty" . to_owned ()]); 630}