2024-06-25 17:15:21 +02:00
|
|
|
//go:build unix
|
|
|
|
|
|
|
|
package nfs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2024-10-11 18:26:29 +02:00
|
|
|
// NB to test the symlink cache, running with elevated permissions is needed
|
|
|
|
const testSymlinkCache = "go test -c && sudo setcap cap_dac_read_search+ep ./nfs.test && ./nfs.test -test.v -test.run TestCache/symlink"
|
|
|
|
|
2024-06-25 17:15:21 +02:00
|
|
|
// Check basic CRUD operations
|
|
|
|
func testCacheCRUD(t *testing.T, h *Handler, c Cache, fileName string) {
|
|
|
|
// Check reading a non existent handle returns an error
|
|
|
|
_, _, err := c.FromHandle([]byte{10})
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
|
|
|
// Write a handle
|
|
|
|
splitPath := []string{"dir", fileName}
|
|
|
|
fh := c.ToHandle(h.billyFS, splitPath)
|
|
|
|
assert.True(t, len(fh) > 0)
|
|
|
|
|
|
|
|
// Read the handle back
|
|
|
|
newFs, newSplitPath, err := c.FromHandle(fh)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, h.billyFS, newFs)
|
|
|
|
assert.Equal(t, splitPath, newSplitPath)
|
|
|
|
|
|
|
|
// Invalidate the handle
|
|
|
|
err = c.InvalidateHandle(h.billyFS, fh)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Invalidate the handle twice
|
|
|
|
err = c.InvalidateHandle(h.billyFS, fh)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check the handle is gone and returning stale handle error
|
|
|
|
_, _, err = c.FromHandle(fh)
|
|
|
|
require.Error(t, err)
|
|
|
|
assert.Equal(t, errStaleHandle, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Thrash the cache operations in parallel on different files
|
|
|
|
func testCacheThrashDifferent(t *testing.T, h *Handler, c Cache) {
|
|
|
|
var wg sync.WaitGroup
|
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 22:08:12 +01:00
|
|
|
for i := range 100 {
|
2024-06-25 17:15:21 +02:00
|
|
|
i := i
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
testCacheCRUD(t, h, c, fmt.Sprintf("file-%d", i))
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Thrash the cache operations in parallel on the same file
|
|
|
|
func testCacheThrashSame(t *testing.T, h *Handler, c Cache) {
|
|
|
|
var wg sync.WaitGroup
|
build: modernize Go usage
This commit modernizes Go usage. This was done with:
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
Then files needed to be `go fmt`ed and a few comments needed to be
restored.
The modernizations include replacing
- if/else conditional assignment by a call to the built-in min or max functions added in go1.21
- sort.Slice(x, func(i, j int) bool) { return s[i] < s[j] } by a call to slices.Sort(s), added in go1.21
- interface{} by the 'any' type added in go1.18
- append([]T(nil), s...) by slices.Clone(s) or slices.Concat(s), added in go1.21
- loop around an m[k]=v map update by a call to one of the Collect, Copy, Clone, or Insert functions from the maps package, added in go1.21
- []byte(fmt.Sprintf...) by fmt.Appendf(nil, ...), added in go1.19
- append(s[:i], s[i+1]...) by slices.Delete(s, i, i+1), added in go1.21
- a 3-clause for i := 0; i < n; i++ {} loop by for i := range n {}, added in go1.22
2025-02-26 22:08:12 +01:00
|
|
|
for range 100 {
|
2024-06-25 17:15:21 +02:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
// Write a handle
|
|
|
|
splitPath := []string{"file"}
|
|
|
|
fh := c.ToHandle(h.billyFS, splitPath)
|
|
|
|
assert.True(t, len(fh) > 0)
|
|
|
|
|
|
|
|
// Read the handle back
|
|
|
|
newFs, newSplitPath, err := c.FromHandle(fh)
|
|
|
|
if err != nil {
|
|
|
|
assert.Equal(t, errStaleHandle, err)
|
|
|
|
} else {
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, h.billyFS, newFs)
|
|
|
|
assert.Equal(t, splitPath, newSplitPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalidate the handle
|
|
|
|
err = c.InvalidateHandle(h.billyFS, fh)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check the handle is gone and returning stale handle error
|
|
|
|
_, _, err = c.FromHandle(fh)
|
|
|
|
if err != nil {
|
|
|
|
require.Error(t, err)
|
|
|
|
assert.Equal(t, errStaleHandle, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCache(t *testing.T) {
|
|
|
|
// Quieten the flood of ERROR messages!
|
|
|
|
ci := fs.GetConfig(context.Background())
|
|
|
|
oldLogLevel := ci.LogLevel
|
|
|
|
ci.LogLevel = fs.LogLevelEmergency
|
2024-10-11 18:26:29 +02:00
|
|
|
//ci.LogLevel = fs.LogLevelDebug
|
2024-06-25 17:15:21 +02:00
|
|
|
defer func() {
|
|
|
|
ci.LogLevel = oldLogLevel
|
|
|
|
}()
|
|
|
|
billyFS := &FS{nil} // place holder billyFS
|
2024-10-11 18:26:29 +02:00
|
|
|
for _, cacheType := range []handleCache{cacheMemory, cacheDisk, cacheSymlink} {
|
2024-06-25 17:15:21 +02:00
|
|
|
cacheType := cacheType
|
|
|
|
t.Run(cacheType.String(), func(t *testing.T) {
|
|
|
|
h := &Handler{
|
|
|
|
billyFS: billyFS,
|
|
|
|
}
|
|
|
|
h.opt.HandleLimit = 1000
|
|
|
|
h.opt.HandleCache = cacheType
|
|
|
|
h.opt.HandleCacheDir = t.TempDir()
|
|
|
|
c, err := h.getCache()
|
2024-10-11 18:26:29 +02:00
|
|
|
if err == ErrorSymlinkCacheNotSupported {
|
|
|
|
t.Skip(err.Error())
|
|
|
|
}
|
|
|
|
if err == ErrorSymlinkCacheNoPermission {
|
|
|
|
t.Skip("Need more permissions to run symlink cache tests: " + testSymlinkCache)
|
|
|
|
}
|
2024-06-25 17:15:21 +02:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2024-10-11 18:26:29 +02:00
|
|
|
t.Run("Empty", func(t *testing.T) {
|
|
|
|
// Write a handle
|
|
|
|
splitPath := []string{""}
|
|
|
|
fh := c.ToHandle(h.billyFS, splitPath)
|
|
|
|
assert.True(t, len(fh) > 0)
|
|
|
|
|
|
|
|
// Read the handle back
|
|
|
|
newFs, newSplitPath, err := c.FromHandle(fh)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, h.billyFS, newFs)
|
|
|
|
assert.Equal(t, splitPath, newSplitPath)
|
|
|
|
testCacheCRUD(t, h, c, "file")
|
|
|
|
})
|
2024-06-25 17:15:21 +02:00
|
|
|
t.Run("CRUD", func(t *testing.T) {
|
|
|
|
testCacheCRUD(t, h, c, "file")
|
|
|
|
})
|
|
|
|
// NB the default caching handler is not thread safe!
|
|
|
|
if cacheType != cacheMemory {
|
|
|
|
t.Run("ThrashDifferent", func(t *testing.T) {
|
|
|
|
testCacheThrashDifferent(t, h, c)
|
|
|
|
})
|
|
|
|
t.Run("ThrashSame", func(t *testing.T) {
|
|
|
|
testCacheThrashSame(t, h, c)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|