lib/fs: Add test reproducing missing mtimefs issue (ref #9677) (#9687)

The test is quite odd and specific, but it does reproduce the issue that
caused #9677, so I'd propose to add it to have a simple regression test
for the basic scenario. Also the option to the fakefs might come handy
for other scenarios where you want to quickly test some behaviour on a
filesystem without nanosecond precision, without actually needing access
to one.
This commit is contained in:
Simon Frei
2024-09-10 13:36:17 +02:00
committed by GitHub
parent a7f9ed4a80
commit 29f7510f5a
2 changed files with 78 additions and 9 deletions
+63
View File
@@ -7,8 +7,10 @@
package fs
import (
"fmt"
"path/filepath"
"testing"
"time"
"github.com/syncthing/syncthing/lib/build"
)
@@ -171,3 +173,64 @@ func TestIsParent(t *testing.T) {
}
}
}
// Reproduces issue 9677:
// The combination of caching the entire case FS and moving the case FS to be
// the outmost layer of the FS lead to the mtime FS disappearing. This is
// because in many places we intentionally create the filesystem without access
// to the DB and thus without the mtime FS layer. With the case FS layer
// outside, all the inner layers are also cached - notable without an mtime FS
// layer. Later when we do try to create an FS with DB/mtime FS, we still get
// the cached FS without mtime FS.
func TestRepro9677MissingMtimeFS(t *testing.T) {
mtimeDB := make(mapStore)
name := "Testfile"
nameLower := UnicodeLowercaseNormalized(name)
testTime := time.Unix(1723491493, 123456789)
// Create a file with an mtime FS entry
firstFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB))
// Create a file, set its mtime and check that we get the expected mtime when stat-ing.
file, err := firstFS.Create(name)
if err != nil {
t.Fatal(err)
}
file.Close()
err = firstFS.Chtimes(name, testTime, testTime)
if err != nil {
t.Fatal(err)
}
checkMtime := func(fs Filesystem) {
t.Helper()
info, err := fs.Lstat(name)
if err != nil {
t.Fatal(err)
}
if !info.ModTime().Equal(testTime) {
t.Errorf("Expected mtime %v for %v, got %v", testTime, name, info.ModTime())
}
info, err = fs.Lstat(nameLower)
if !IsErrCaseConflict(err) {
t.Errorf("Expected case-conflict error, got %v", err)
}
}
checkMtime(firstFS)
// Now syncthing gets upgraded (or even just restarted), which resets the
// case FS registry as it lives in memory.
globalCaseFilesystemRegistry = caseFilesystemRegistry{fss: make(map[fskey]*caseFilesystem)}
// This time we first create some filesystem without a database and thus no
// mtime-FS, which is used in various places outside of the folder code. We
// aren't actually going to do anything, this just adds an entry to the
// caseFS cache. And that's the crucial bit: In the broken case this test is
// reproducing, it will add the FS without mtime-FS, so all future FSes will
// be without mtime, even if requested:
NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{})
newFS := NewFilesystem(FilesystemTypeFake, fmt.Sprintf("%v?insens=true&timeprecisionsecond=true", t.Name()), &OptionDetectCaseConflicts{}, NewMtimeOption(mtimeDB))
checkMtime(newFS)
}