@@ -171,6 +171,9 @@ func (tt *tester) reopen(minVer int64, minExact bool, format string, args ...any
171171 if version < minVer || minExact != exact {
172172 tt .t .Fatalf ("reopen: %s: version = %d,%v, want ≥ %d,%v" , kind , version , exact , minVer , minExact )
173173 }
174+ if tree .PersistedVersion () != version {
175+ tt .t .Fatalf ("reopen: %s: PersistedVersion = %d, want %d" , kind , tree .PersistedVersion (), version )
176+ }
174177 if ! exact {
175178 f1 .readOnly = false
176179 f2 .readOnly = false
@@ -437,3 +440,158 @@ func TestSetOverwriteDiskSize(t *testing.T) {
437440 }
438441 })
439442}
443+
444+ func TestPersistedVersion (t * testing.T ) {
445+ type step struct {
446+ action string // "check", "set", "snap", "sync", "reopen"
447+ key string
448+ val string
449+ version int64
450+ wantVersion int64
451+ wantPersisted int64 // expected PersistedVersion on diskTree (on memTree, equals wantVersion)
452+ }
453+
454+ tests := []struct {
455+ name string
456+ diskOnly bool
457+ steps []step
458+ }{
459+ {
460+ name : "initial_empty" ,
461+ steps : []step {
462+ {action : "check" , wantVersion : 0 , wantPersisted : 0 },
463+ },
464+ },
465+ {
466+ name : "snap_without_sync" ,
467+ steps : []step {
468+ {action : "set" , key : "k1" , val : "v1" , wantVersion : 0 , wantPersisted : 0 },
469+ {action : "snap" , version : 10 , wantVersion : 10 , wantPersisted : 0 },
470+ },
471+ },
472+ {
473+ name : "snap_then_sync" ,
474+ steps : []step {
475+ {action : "set" , key : "k1" , val : "v1" , wantVersion : 0 , wantPersisted : 0 },
476+ {action : "snap" , version : 10 , wantVersion : 10 , wantPersisted : 0 },
477+ {action : "sync" , wantVersion : 10 , wantPersisted : 10 },
478+ },
479+ },
480+ {
481+ name : "unflushed_crash_recovery" ,
482+ diskOnly : true ,
483+ steps : []step {
484+ {action : "set" , key : "k1" , val : "v1" , wantVersion : 0 , wantPersisted : 0 },
485+ {action : "snap" , version : 10 , wantVersion : 10 , wantPersisted : 0 },
486+ {action : "sync" , wantVersion : 10 , wantPersisted : 10 },
487+ {action : "set" , key : "k2" , val : "v2" , wantVersion : 10 , wantPersisted : 10 },
488+ {action : "snap" , version : 20 , wantVersion : 20 , wantPersisted : 10 },
489+ {action : "reopen" , wantVersion : 10 , wantPersisted : 10 },
490+ },
491+ },
492+ {
493+ name : "negative_version_snap" ,
494+ steps : []step {
495+ {action : "set" , key : "k1" , val : "v1" , wantVersion : 0 , wantPersisted : 0 },
496+ {action : "snap" , version : 10 , wantVersion : 10 , wantPersisted : 0 },
497+ {action : "sync" , wantVersion : 10 , wantPersisted : 10 },
498+ {action : "set" , key : "k2" , val : "v2" , wantVersion : 10 , wantPersisted : 10 },
499+ {action : "snap" , version : - 1 , wantVersion : 10 , wantPersisted : 10 },
500+ {action : "sync" , wantVersion : 10 , wantPersisted : 10 },
501+ },
502+ },
503+ {
504+ name : "multiple_sync_cycles" ,
505+ steps : []step {
506+ {action : "set" , key : "k1" , val : "v1" , wantVersion : 0 , wantPersisted : 0 },
507+ {action : "snap" , version : 10 , wantVersion : 10 , wantPersisted : 0 },
508+ {action : "sync" , wantVersion : 10 , wantPersisted : 10 },
509+ {action : "set" , key : "k2" , val : "v2" , wantVersion : 10 , wantPersisted : 10 },
510+ {action : "snap" , version : 20 , wantVersion : 20 , wantPersisted : 10 },
511+ {action : "sync" , wantVersion : 20 , wantPersisted : 20 },
512+ {action : "set" , key : "k3" , val : "v3" , wantVersion : 20 , wantPersisted : 20 },
513+ {action : "snap" , version : 30 , wantVersion : 30 , wantPersisted : 20 },
514+ {action : "sync" , wantVersion : 30 , wantPersisted : 30 },
515+ },
516+ },
517+ }
518+
519+ t .Run ("diskTree" , func (t * testing.T ) {
520+ for _ , tc := range tests {
521+ t .Run (tc .name , func (t * testing.T ) {
522+ f1 := new (memFile )
523+ f2 := new (memFile )
524+ f3 := new (memFile )
525+
526+ tree , err := New (f1 , f2 , f3 )
527+ check (t , err )
528+ defer tree .Close ()
529+
530+ for i , s := range tc .steps {
531+ switch s .action {
532+ case "set" :
533+ check (t , tree .Set (Key (s .key ), Val (s .val )))
534+ case "snap" :
535+ _ , err := tree .Snap (s .version )
536+ check (t , err )
537+ case "sync" :
538+ check (t , tree .Sync ())
539+ case "reopen" :
540+ f1Copy := & memFile {data : slices .Clone (f1 .data )}
541+ f2Copy := & memFile {data : slices .Clone (f2 .data )}
542+ f3Copy := & memFile {data : slices .Clone (f3 .data )}
543+ check (t , tree .Close ())
544+ tree , err = New (f1Copy , f2Copy , f3Copy )
545+ check (t , err )
546+ case "check" :
547+ // Just assert versions.
548+ default :
549+ t .Fatalf ("unknown action %q" , s .action )
550+ }
551+
552+ if v , _ := tree .Version (); v != s .wantVersion {
553+ t .Fatalf ("step %d (%s): Version() = %d, want %d" , i , s .action , v , s .wantVersion )
554+ }
555+ if got := tree .PersistedVersion (); got != s .wantPersisted {
556+ t .Fatalf ("step %d (%s): PersistedVersion() = %d, want %d" , i , s .action , got , s .wantPersisted )
557+ }
558+ }
559+ })
560+ }
561+ })
562+
563+ t .Run ("memTree" , func (t * testing.T ) {
564+ for _ , tc := range tests {
565+ if tc .diskOnly {
566+ continue
567+ }
568+ t .Run (tc .name , func (t * testing.T ) {
569+ tree := NewMemTree ()
570+ defer tree .Close ()
571+
572+ for i , s := range tc .steps {
573+ switch s .action {
574+ case "set" :
575+ check (t , tree .Set (Key (s .key ), Val (s .val )))
576+ case "snap" :
577+ _ , err := tree .Snap (s .version )
578+ check (t , err )
579+ case "sync" :
580+ check (t , tree .Sync ())
581+ case "check" :
582+ // Just assert versions.
583+ default :
584+ t .Fatalf ("unknown action %q" , s .action )
585+ }
586+
587+ if v , _ := tree .Version (); v != s .wantVersion {
588+ t .Fatalf ("step %d (%s): Version() = %d, want %d" , i , s .action , v , s .wantVersion )
589+ }
590+ if got := tree .PersistedVersion (); got != s .wantVersion {
591+ t .Fatalf ("step %d (%s): PersistedVersion() = %d, want %d" , i , s .action , got , s .wantVersion )
592+ }
593+ }
594+ })
595+ }
596+ })
597+ }
0 commit comments