@@ -357,7 +357,7 @@ func TestSingleNode_BackupClient(t *testing.T) {
357357 t .Run ("InitiateSnapshot" , func (t * testing.T ) {
358358 cmd0 := newMountCommand (t , t .TempDir (), nil )
359359 cmd0 .Config .Data .Retention = 1 * time .Microsecond
360- cmd0 .Config .Backup = main.BackupConfig {Type : "file" , Path : t .TempDir ()}
360+ cmd0 .Config .Backup = main.BackupConfig {Type : "file" , Path : t .TempDir (), Delay : 1 * time . Millisecond }
361361 runMountCommand (t , cmd0 )
362362
363363 // Create a simple table with a single value.
@@ -394,8 +394,10 @@ func TestSingleNode_BackupClient(t *testing.T) {
394394 // Ensure LiteFS can restore a database from backup if it doesn't exist locally.
395395 t .Run ("RestoreFromBackup/NoLocalDatabase" , func (t * testing.T ) {
396396 backupDir := t .TempDir ()
397+ backupConfig := main.BackupConfig {Type : "file" , Path : backupDir , Delay : 1 * time .Millisecond }
398+
397399 cmd0 := newMountCommand (t , t .TempDir (), nil )
398- cmd0 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
400+ cmd0 .Config .Backup = backupConfig
399401 runMountCommand (t , cmd0 )
400402
401403 // Create a simple table with a single value.
@@ -416,7 +418,7 @@ func TestSingleNode_BackupClient(t *testing.T) {
416418 t .Logf ("shutdown original mount, starting new one" )
417419
418420 cmd1 := newMountCommand (t , t .TempDir (), nil )
419- cmd1 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
421+ cmd1 .Config .Backup = backupConfig
420422 runMountCommand (t , cmd1 )
421423 waitForBackupSync (t , cmd1 )
422424
@@ -434,13 +436,14 @@ func TestSingleNode_BackupClient(t *testing.T) {
434436 t .Run ("RestoreFromBackup/RemoteAhead" , func (t * testing.T ) {
435437 dir0 , dir1 := t .TempDir (), t .TempDir ()
436438 backupDir := t .TempDir ()
439+ backupConfig := main.BackupConfig {Type : "file" , Path : backupDir , Delay : 1 * time .Millisecond }
437440
438441 cmd0 := newMountCommand (t , dir0 , nil )
439- cmd0 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
442+ cmd0 .Config .Backup = backupConfig
440443 waitForPrimary (t , runMountCommand (t , cmd0 ))
441444
442445 cmd1 := newMountCommand (t , dir1 , cmd0 )
443- cmd1 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
446+ cmd1 .Config .Backup = backupConfig
444447 runMountCommand (t , cmd1 )
445448
446449 // Create a simple table with a single value.
@@ -479,7 +482,7 @@ func TestSingleNode_BackupClient(t *testing.T) {
479482 // Restart the previous replica so it becomes the new primary.
480483 t .Logf ("restart replica and wait for promotion" )
481484 cmd1 = newMountCommand (t , dir1 , nil )
482- cmd1 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
485+ cmd1 .Config .Backup = backupConfig
483486 waitForPrimary (t , runMountCommand (t , cmd1 ))
484487 waitForBackupSync (t , cmd1 )
485488
@@ -498,13 +501,14 @@ func TestSingleNode_BackupClient(t *testing.T) {
498501 t .Run ("RestoreFromBackup/ChecksumMismatch" , func (t * testing.T ) {
499502 dir0 , dir1 := t .TempDir (), t .TempDir ()
500503 backupDir := t .TempDir ()
504+ backupConfig := main.BackupConfig {Type : "file" , Path : backupDir , Delay : 1 * time .Millisecond }
501505
502506 cmd0 := newMountCommand (t , dir0 , nil )
503- cmd0 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
507+ cmd0 .Config .Backup = backupConfig
504508 waitForPrimary (t , runMountCommand (t , cmd0 ))
505509
506510 cmd1 := newMountCommand (t , dir1 , cmd0 )
507- cmd1 .Config .Backup = main. BackupConfig { Type : "file" , Path : backupDir }
511+ cmd1 .Config .Backup = backupConfig
508512 runMountCommand (t , cmd1 )
509513
510514 // Create a simple table with a single value.
@@ -661,14 +665,85 @@ func TestSingleNode_BackupClient(t *testing.T) {
661665 }
662666
663667 if got , want := cmd0 .Config .Backup , (main.BackupConfig {
664- Type : "litefs-cloud" ,
665- URL : "https://litefs.fly.io" ,
666- AuthToken : "TOKENDATA" ,
667- Delay : litefs .DefaultBackupDelay ,
668+ Type : "litefs-cloud" ,
669+ URL : "https://litefs.fly.io" ,
670+ AuthToken : "TOKENDATA" ,
671+ Delay : litefs .DefaultBackupDelay ,
672+ FullSyncInterval : litefs .DefaultBackupFullSyncInterval ,
668673 }); got != want {
669674 t .Fatalf ("config=%#v, want %#v" , got , want )
670675 }
671676 })
677+
678+ // Ensure LiteFS periodically syncs with the backup server to detect restores.
679+ t .Run ("FullSync" , func (t * testing.T ) {
680+ backupConfig := main.BackupConfig {
681+ Type : "file" ,
682+ Path : t .TempDir (),
683+ Delay : 10 * time .Microsecond ,
684+ FullSyncInterval : 10 * time .Millisecond ,
685+ }
686+
687+ cmd0 := newMountCommand (t , t .TempDir (), nil )
688+ cmd0 .Config .Backup = backupConfig
689+ runMountCommand (t , cmd0 )
690+ waitForPrimary (t , cmd0 )
691+
692+ // Create a simple table with a single value.
693+ db0 := testingutil .OpenSQLDB (t , filepath .Join (cmd0 .Config .FUSE .Dir , "db" ))
694+ if _ , err := db0 .Exec (`CREATE TABLE t (x)` ); err != nil {
695+ t .Fatal (err )
696+ } else if _ , err := db0 .Exec (`INSERT INTO t VALUES (100)` ); err != nil {
697+ t .Fatal (err )
698+ } else if err := db0 .Close (); err != nil {
699+ t .Fatal (err )
700+ }
701+
702+ // Sync to backup.
703+ if err := cmd0 .Store .SyncBackup (context .Background ()); err != nil {
704+ t .Fatal (err )
705+ }
706+
707+ // Start replica, sync & shutdown.
708+ dir1 := t .TempDir ()
709+ cmd1 := newMountCommand (t , dir1 , cmd0 )
710+ cmd1 .Config .Backup = backupConfig
711+ runMountCommand (t , cmd1 )
712+ waitForSync (t , "db" , cmd0 , cmd1 )
713+ if err := cmd1 .Close (); err != nil {
714+ t .Fatal (err )
715+ }
716+
717+ // Restart replica as its own cluster so we can update the backup while
718+ // the old primary is still live.
719+ cmd1 = newMountCommand (t , dir1 , nil )
720+ cmd1 .Config .Backup = backupConfig
721+ runMountCommand (t , cmd1 )
722+ waitForPrimary (t , cmd1 )
723+
724+ // Insert data into second primary.
725+ db1 := testingutil .OpenSQLDB (t , filepath .Join (cmd1 .Config .FUSE .Dir , "db" ))
726+ if _ , err := db1 .Exec (`INSERT INTO t VALUES (200)` ); err != nil {
727+ t .Fatal (err )
728+ } else if err := db1 .Close (); err != nil {
729+ t .Fatal (err )
730+ }
731+ if err := cmd1 .Store .SyncBackup (context .Background ()); err != nil {
732+ t .Fatal (err )
733+ }
734+ waitForBackupSync (t , cmd1 )
735+
736+ // Wait a moment for the first primary to pick up the changes.
737+ time .Sleep (1 * time .Second )
738+
739+ db0 = testingutil .OpenSQLDB (t , filepath .Join (cmd1 .Config .FUSE .Dir , "db" ))
740+ var n int
741+ if err := db0 .QueryRow (`SELECT SUM(x) FROM t` ).Scan (& n ); err != nil {
742+ t .Fatal (err )
743+ } else if got , want := n , 300 ; got != want {
744+ t .Fatalf ("sum=%d, want %d" , got , want )
745+ }
746+ })
672747}
673748
674749func TestMultiNode_Simple (t * testing.T ) {
0 commit comments