Files
gvisor-tap-vsock/pkg/utils/filewatcher_test.go
Fata Nugraha c56ed7ab4a Use poll based file watcher
fsnotify/fsnotify can't watch a folder that contains a symlink into
a socket or named pipe.

Use poll-based mechanism to watch the file for the time being until
we find a better way or fix the issue in the upstream.

Signed-off-by: Fata Nugraha <fatanugraha@outlook.com>
2025-01-22 21:59:37 +08:00

102 lines
2.4 KiB
Go

package utils
import (
"os"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestFileWatcher(t *testing.T) {
if testing.Short() {
// we can't really speed up the test as we need to wait for the
// inode cache to expire so that we can read the latest
// file's modtime.
t.Skip("skipping test in short mode.")
}
t.Parallel()
assertSpec := func(t *testing.T, watchedPath, filePath string) {
t.Helper()
fw := NewFileWatcher(watchedPath)
var numTriggered atomic.Int64
assertNumTriggered := func(expected int) {
time.Sleep(fw.pollInterval + 200*time.Millisecond)
assert.Equal(t, int64(expected), numTriggered.Load())
}
fw.Start(func() {
numTriggered.Add(1)
})
// important: adding a random sleep here so that the next fw's poll
// results in inode cache miss, hence calling stat will return the latest
// modtime.
time.Sleep(1 * time.Second)
// CASE: can detect changes to the file.
if err := os.WriteFile(filePath, []byte("2"), 0o600); err != nil {
panic(err)
}
assertNumTriggered(1)
// CASE: can detect "swap"-based file modification.
tmpFile := filepath.Join(filepath.Dir(filePath), "tmp.txt")
if err := os.WriteFile(tmpFile, []byte("lol"), 0o600); err != nil {
panic(err)
}
if err := os.Rename(tmpFile, filePath); err != nil {
panic(err)
}
assertNumTriggered(2)
// CASE: combine multiple partial writes into single event.
fd, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0o600)
if err != nil {
panic(err)
}
// we assume these writes happens in less than 50ms.
_, _ = fd.Write([]byte("a"))
_ = fd.Sync()
_, _ = fd.Write([]byte("b"))
fd.Close()
assertNumTriggered(3)
// CASE: closed file watcher should not call the callback after Stop() is called.
fw.Stop()
if err := os.WriteFile(filePath, []byte("2"), 0o600); err != nil {
panic(err)
}
assertNumTriggered(3) // does not change.
}
t.Run("normal file", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "file.txt")
_ = os.WriteFile(path, []byte("1"), 0o600)
assertSpec(t, path, path)
})
t.Run("symlink", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
path := filepath.Join(dir, "file.txt")
_ = os.WriteFile(path, []byte("1"), 0o600)
symlinkPath := filepath.Join(dir, "symlink.txt")
_ = os.Symlink(path, symlinkPath)
assertSpec(t, symlinkPath, path)
})
}