增加运行测试的命令;修复一些测试问题

This commit is contained in:
Sydonian 2025-04-15 11:43:41 +08:00
parent 67c29cbf1c
commit ee5deffc82
19 changed files with 1592 additions and 33 deletions

View File

@ -0,0 +1,231 @@
package cmdline
import (
"fmt"
"os"
"testing"
"time"
"github.com/spf13/cobra"
"gitlink.org.cn/cloudream/common/pkgs/logger"
"gitlink.org.cn/cloudream/storage2/client/internal/accessstat"
"gitlink.org.cn/cloudream/storage2/client/internal/config"
"gitlink.org.cn/cloudream/storage2/client/internal/db"
"gitlink.org.cn/cloudream/storage2/client/internal/downloader"
"gitlink.org.cn/cloudream/storage2/client/internal/downloader/strategy"
"gitlink.org.cn/cloudream/storage2/client/internal/http"
"gitlink.org.cn/cloudream/storage2/client/internal/metacache"
"gitlink.org.cn/cloudream/storage2/client/internal/mount"
"gitlink.org.cn/cloudream/storage2/client/internal/mount/vfstest"
"gitlink.org.cn/cloudream/storage2/client/internal/services"
"gitlink.org.cn/cloudream/storage2/client/internal/uploader"
stgglb "gitlink.org.cn/cloudream/storage2/common/globals"
"gitlink.org.cn/cloudream/storage2/common/models/datamap"
"gitlink.org.cn/cloudream/storage2/common/pkgs/connectivity"
"gitlink.org.cn/cloudream/storage2/common/pkgs/distlock"
"gitlink.org.cn/cloudream/storage2/common/pkgs/storage/pool"
"gitlink.org.cn/cloudream/storage2/common/pkgs/sysevent"
)
// 初始化函数将ServeHTTP命令注册到命令列表中。
func init() {
var configPath string
var opt serveHTTPOptions
cmd := cobra.Command{
Use: "vfstest",
Short: "test vfs",
Run: func(cmd *cobra.Command, args []string) {
vfsTest(configPath, opt)
},
}
cmd.Flags().StringVarP(&configPath, "config", "c", "", "config file path")
cmd.Flags().BoolVarP(&opt.DisableHTTP, "no-http", "", false, "disable http server")
cmd.Flags().StringVarP(&opt.HTTPListenAddr, "listen", "", "", "http listen address, will override config file")
cmd.Flags().BoolVarP(&opt.DisableMount, "no-mount", "", false, "disable mount")
cmd.Flags().StringVarP(&opt.MountPoint, "mount", "", "", "mount point, will override config file")
RootCmd.AddCommand(&cmd)
}
func vfsTest(configPath string, opts serveHTTPOptions) {
err := config.Init(configPath)
if err != nil {
fmt.Printf("init config failed, err: %s", err.Error())
os.Exit(1)
}
err = logger.Init(&config.Cfg().Logger)
if err != nil {
fmt.Printf("init logger failed, err: %s", err.Error())
os.Exit(1)
}
stgglb.InitLocal(config.Cfg().Local)
stgglb.InitMQPool(config.Cfg().RabbitMQ)
stgglb.InitHubRPCPool(&config.Cfg().HubGRPC)
// 数据库
db, err := db.NewDB(&config.Cfg().DB)
if err != nil {
logger.Fatalf("new db failed, err: %s", err.Error())
}
// 初始化系统事件发布器
evtPub, err := sysevent.NewPublisher(sysevent.ConfigFromMQConfig(config.Cfg().RabbitMQ), &datamap.SourceClient{
UserID: config.Cfg().Local.UserID,
})
if err != nil {
logger.Errorf("new sysevent publisher: %v", err)
os.Exit(1)
}
evtPubChan := evtPub.Start()
defer evtPub.Stop()
// 连接性信息收集
conCol := connectivity.NewCollector(&config.Cfg().Connectivity, nil)
conCol.CollectInPlace()
// 元数据缓存
metaCacheHost := metacache.NewHost(db)
go metaCacheHost.Serve()
stgMeta := metaCacheHost.AddStorageMeta()
hubMeta := metaCacheHost.AddHubMeta()
conMeta := metaCacheHost.AddConnectivity()
// 分布式锁
distlockSvc, err := distlock.NewService(&config.Cfg().DistLock)
if err != nil {
logger.Warnf("new distlock service failed, err: %s", err.Error())
os.Exit(1)
}
go serveDistLock(distlockSvc)
// 访问统计
acStat := accessstat.NewAccessStat(accessstat.Config{
// TODO 考虑放到配置里
ReportInterval: time.Second * 10,
}, db)
acStatChan := acStat.Start()
defer acStat.Stop()
// 存储管理器
stgPool := pool.NewPool()
// 下载策略
strgSel := strategy.NewSelector(config.Cfg().DownloadStrategy, stgMeta, hubMeta, conMeta)
// 下载器
dlder := downloader.NewDownloader(config.Cfg().Downloader, &conCol, stgPool, strgSel, db)
// 上传器
uploader := uploader.NewUploader(distlockSvc, &conCol, stgPool, stgMeta, db)
// 挂载
mntCfg := config.Cfg().Mount
if !opts.DisableMount && mntCfg != nil && mntCfg.Enabled {
if opts.MountPoint != "" {
mntCfg.MountPoint = opts.MountPoint
}
} else {
mntCfg = nil
}
mnt := mount.NewMount(mntCfg, db, uploader, dlder)
mntChan := mnt.Start()
defer mnt.Stop()
svc := services.NewService(distlockSvc, dlder, acStat, uploader, strgSel, stgMeta, db, evtPub, mnt)
// HTTP接口
httpCfg := config.Cfg().HTTP
if !opts.DisableHTTP && httpCfg != nil && httpCfg.Enabled {
if opts.HTTPListenAddr != "" {
httpCfg.Listen = opts.HTTPListenAddr
}
} else {
httpCfg = nil
}
httpSvr := http.NewServer(httpCfg, svc)
httpChan := httpSvr.Start()
defer httpSvr.Stop()
go func() {
<-time.After(5 * time.Second)
testing.Main(nil, []testing.InternalTest{
{"VFS", func(t *testing.T) {
vfstest.RunTests(t, mnt)
}},
}, nil, nil)
}()
/// 开始监听各个模块的事件
evtPubEvt := evtPubChan.Receive()
acStatEvt := acStatChan.Receive()
httpEvt := httpChan.Receive()
mntEvt := mntChan.Receive()
loop:
for {
select {
case e := <-evtPubEvt.Chan():
if e.Err != nil {
logger.Errorf("receive publisher event: %v", err)
break loop
}
switch val := e.Value.(type) {
case sysevent.PublishError:
logger.Errorf("publishing event: %v", val)
case sysevent.PublisherExited:
if val.Err != nil {
logger.Errorf("publisher exited with error: %v", val.Err)
} else {
logger.Info("publisher exited")
}
break loop
case sysevent.OtherError:
logger.Errorf("sysevent: %v", val)
}
evtPubEvt = evtPubChan.Receive()
case e := <-acStatEvt.Chan():
if e.Err != nil {
logger.Errorf("receive access stat event: %v", err)
break loop
}
switch e := e.Value.(type) {
case accessstat.ExitEvent:
logger.Infof("access stat exited, err: %v", e.Err)
break loop
}
acStatEvt = acStatChan.Receive()
case e := <-httpEvt.Chan():
if e.Err != nil {
logger.Errorf("receive http event: %v", err)
break loop
}
switch e := e.Value.(type) {
case http.ExitEvent:
logger.Infof("http server exited, err: %v", e.Err)
break loop
}
httpEvt = httpChan.Receive()
case e := <-mntEvt.Chan():
if e.Err != nil {
logger.Errorf("receive mount event: %v", e.Err)
break loop
}
switch e := e.Value.(type) {
case mount.ExitEvent:
logger.Infof("mount exited, err: %v", e.Err)
break loop
}
mntEvt = mntChan.Receive()
}
}
}

View File

@ -566,10 +566,6 @@ func (db *ObjectDB) BatchUpdateRedundancy(ctx SQLContext, updates []UpdatingObje
return nil
}
func (db *ObjectDB) DeleteByPath(ctx SQLContext, packageID types.PackageID, path string) error {
return ctx.Table("Object").Where("PackageID = ? AND Path = ?", packageID, path).Delete(&types.Object{}).Error
}
func (db *ObjectDB) MoveByPrefix(ctx SQLContext, oldPkgID types.PackageID, oldPrefix string, newPkgID types.PackageID, newPrefix string) error {
return ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", oldPkgID, escapeLike("", "%", oldPrefix)).
Updates(map[string]any{
@ -628,3 +624,36 @@ func (db *ObjectDB) AppendPart(tx SQLContext, block types.ObjectBlock) error {
return nil
}
func (db *ObjectDB) BatchDeleteComplete(ctx SQLContext, objectIDs []types.ObjectID) error {
err := db.Object().BatchDelete(ctx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting objects: %w", err)
}
err = db.ObjectBlock().BatchDeleteByObjectID(ctx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting object blocks: %w", err)
}
err = db.PinnedObject().BatchDeleteByObjectID(ctx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting pinned objects: %w", err)
}
err = db.ObjectAccessStat().BatchDeleteByObjectID(ctx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting object access stats: %w", err)
}
return nil
}
func (db *ObjectDB) DeleteCompleteByPath(ctx SQLContext, packageID types.PackageID, path string) error {
obj, err := db.Object().GetByPath(ctx, packageID, path)
if err != nil {
return err
}
return db.BatchDeleteComplete(ctx, []types.ObjectID{obj.ObjectID})
}

View File

@ -77,6 +77,14 @@ func (m *Mount) Stop() {
m.fuseServer.Unmount()
}
func (m *Mount) MountPoint() string {
if m.cfg == nil {
return ""
}
return m.cfg.MountPoint
}
func (m *Mount) Dump() MountStatus {
if m.vfs == nil {
return MountStatus{}

View File

@ -27,6 +27,10 @@ func (m *Mount) Start() *MountEventChan {
return m.eventChan
}
func (m *Mount) MountPoint() string {
return ""
}
func (m *Mount) Stop() {
m.eventChan.Send(ExitEvent{})
}

View File

@ -52,6 +52,7 @@ func loadCacheDir(c *Cache, pathComps []string) (*CacheDir, error) {
}
return &CacheDir{
cache: c,
pathComps: pathComps,
name: stat.Name(),
modTime: stat.ModTime(),
@ -75,6 +76,7 @@ func makeCacheDirFromOption(c *Cache, pathComps []string, opt CreateDirOption) (
os.Chtimes(dataPath, opt.ModTime, opt.ModTime)
return &CacheDir{
cache: c,
pathComps: pathComps,
name: pathComps[len(pathComps)-1],
modTime: opt.ModTime,

View File

@ -99,8 +99,12 @@ func listChildren(vfs *Vfs, ctx context.Context, parent FuseNode) ([]fuse.FsEntr
}
objPath := clitypes.JoinObjectPath(myPathComps[2:]...)
objPrefix := objPath
if objPath != "" {
objPrefix += clitypes.ObjectPathSeparator
}
objs, coms, err := d.Object().GetByPrefixGrouped(tx, pkg.PackageID, objPath+clitypes.ObjectPathSeparator)
objs, coms, err := d.Object().GetByPrefixGrouped(tx, pkg.PackageID, objPrefix)
if err != nil {
return err
}
@ -186,8 +190,8 @@ func removeChild(vfs *Vfs, ctx context.Context, name string, parent FuseNode) er
}
// 存储系统不会保存目录结构,所以这里是尝试删除同名文件
err = d.Object().DeleteByPath(tx, pkg.PackageID, joinedPath)
if err != nil {
err = d.Object().DeleteCompleteByPath(tx, pkg.PackageID, joinedPath)
if err != nil && err != gorm.ErrRecordNotFound {
return err
}
@ -223,8 +227,11 @@ func moveChild(vfs *Vfs, ctx context.Context, oldName string, oldParent FuseNode
}
err2 := vfs.cache.Move(oldChildPath, newChildPath)
if err == fuse.ErrNotExists && err2 == fuse.ErrNotExists {
return fuse.ErrNotExists
if err2 == fuse.ErrNotExists {
if err == fuse.ErrNotExists {
return fuse.ErrNotExists
}
return nil
}
return err2
@ -273,11 +280,11 @@ func moveRemote(vfs *Vfs, tx db.SQLContext, oldChildPath []string, newParentPath
return d.Object().BatchUpdate(tx, []clitypes.Object{oldObj})
}
if err == gorm.ErrRecordNotFound {
return fuse.ErrNotExists
if err != gorm.ErrRecordNotFound {
return err
}
err = d.Object().HasObjectWithPrefix(tx, oldObj.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator)
err = d.Object().HasObjectWithPrefix(tx, oldPkg.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator)
if err == nil {
return d.Object().MoveByPrefix(tx,
oldPkg.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator,

View File

@ -0,0 +1,225 @@
package vfstest
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestDirLs checks out listing
func TestDirLs(t *testing.T) {
run.skipIfNoFUSE(t)
run.checkDir(t, "")
run.mkdir(t, "a directory")
run.createFile(t, "a file", "hello")
run.checkDir(t, "a directory/|a file 5")
run.rmdir(t, "a directory")
run.rm(t, "a file")
run.checkDir(t, "")
}
// TestDirCreateAndRemoveDir tests creating and removing a directory
func TestDirCreateAndRemoveDir(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.mkdir(t, "dir/subdir")
run.checkDir(t, "dir/|dir/subdir/")
// Check we can't delete a directory with stuff in
err := run.os.Remove(run.path("dir"))
assert.Error(t, err, "file exists")
// Now delete subdir then dir - should produce no errors
run.rmdir(t, "dir/subdir")
run.checkDir(t, "dir/")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirCreateAndRemoveFile tests creating and removing a file
func TestDirCreateAndRemoveFile(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.createFile(t, "dir/file", "potato")
run.checkDir(t, "dir/|dir/file 6")
// Check we can't delete a directory with stuff in
err := run.os.Remove(run.path("dir"))
assert.Error(t, err, "file exists")
// Now delete file
run.rm(t, "dir/file")
run.checkDir(t, "dir/")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirRenameFile tests renaming a file
func TestDirRenameFile(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.createFile(t, "file", "potato")
run.checkDir(t, "dir/|file 6")
err := run.os.Rename(run.path("file"), run.path("file2"))
require.NoError(t, err)
run.checkDir(t, "dir/|file2 6")
data := run.readFile(t, "file2")
assert.Equal(t, "potato", data)
err = run.os.Rename(run.path("file2"), run.path("dir/file3"))
require.NoError(t, err)
run.checkDir(t, "dir/|dir/file3 6")
data = run.readFile(t, "dir/file3")
require.NoError(t, err)
assert.Equal(t, "potato", data)
run.rm(t, "dir/file3")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirRenameEmptyDir tests renaming and empty directory
func TestDirRenameEmptyDir(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.mkdir(t, "dir1")
run.checkDir(t, "dir/|dir1/")
err := run.os.Rename(run.path("dir1"), run.path("dir/dir2"))
require.NoError(t, err)
run.checkDir(t, "dir/|dir/dir2/")
err = run.os.Rename(run.path("dir/dir2"), run.path("dir/dir3"))
require.NoError(t, err)
run.checkDir(t, "dir/|dir/dir3/")
run.rmdir(t, "dir/dir3")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirRenameFullDir tests renaming a full directory
func TestDirRenameFullDir(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.mkdir(t, "dir1")
run.createFile(t, "dir1/potato.txt", "maris piper")
run.checkDir(t, "dir/|dir1/|dir1/potato.txt 11")
err := run.os.Rename(run.path("dir1"), run.path("dir/dir2"))
require.NoError(t, err)
run.checkDir(t, "dir/|dir/dir2/|dir/dir2/potato.txt 11")
err = run.os.Rename(run.path("dir/dir2"), run.path("dir/dir3"))
require.NoError(t, err)
run.checkDir(t, "dir/|dir/dir3/|dir/dir3/potato.txt 11")
run.rm(t, "dir/dir3/potato.txt")
run.rmdir(t, "dir/dir3")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirModTime tests mod times
func TestDirModTime(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
err := run.os.Chtimes(run.path("dir"), mtime, mtime)
require.NoError(t, err)
info, err := run.os.Stat(run.path("dir"))
require.NoError(t, err)
// avoid errors because of timezone differences
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
run.rmdir(t, "dir")
}
/*
// TestDirCacheFlush tests flushing the dir cache
func TestDirCacheFlush(t *testing.T) {
run.skipIfNoFUSE(t)
run.checkDir(t, "")
run.mkdir(t, "dir")
run.mkdir(t, "otherdir")
run.createFile(t, "dir/file", "1")
run.createFile(t, "otherdir/file", "1")
dm := newDirMap("otherdir/|otherdir/file 1|dir/|dir/file 1")
localDm := make(dirMap)
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
err := run.fremote.Mkdir(context.Background(), "dir/subdir")
require.NoError(t, err)
// expect newly created "subdir" on remote to not show up
run.forget("otherdir")
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
run.forget("dir")
dm = newDirMap("otherdir/|otherdir/file 1|dir/|dir/file 1|dir/subdir/")
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
run.rm(t, "otherdir/file")
run.rmdir(t, "otherdir")
run.rm(t, "dir/file")
run.rmdir(t, "dir/subdir")
run.rmdir(t, "dir")
run.checkDir(t, "")
}
// TestDirCacheFlushOnDirRename tests flushing the dir cache on rename
func TestDirCacheFlushOnDirRename(t *testing.T) {
run.skipIfNoFUSE(t)
run.mkdir(t, "dir")
run.createFile(t, "dir/file", "1")
dm := newDirMap("dir/|dir/file 1")
localDm := make(dirMap)
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
// expect remotely created directory to not show up
err := run.fremote.Mkdir(context.Background(), "dir/subdir")
require.NoError(t, err)
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
err = run.os.Rename(run.path("dir"), run.path("rid"))
require.NoError(t, err)
dm = newDirMap("rid/|rid/subdir/|rid/file 1")
localDm = make(dirMap)
run.readLocal(t, localDm, "")
assert.Equal(t, dm, localDm, "expected vs fuse mount")
run.rm(t, "rid/file")
run.rmdir(t, "rid/subdir")
run.rmdir(t, "rid")
run.checkDir(t, "")
}
*/

View File

@ -0,0 +1,60 @@
package vfstest
import (
"runtime"
"testing"
"github.com/stretchr/testify/require"
)
// TestTouchAndDelete checks that writing a zero byte file and immediately
// deleting it is not racy. See https://github.com/rclone/rclone/issues/1181
func TestTouchAndDelete(t *testing.T) {
run.skipIfNoFUSE(t)
run.checkDir(t, "")
run.createFile(t, "touched", "")
run.rm(t, "touched")
run.checkDir(t, "")
}
// TestRenameOpenHandle checks that a file with open writers is successfully
// renamed after all writers close. See https://github.com/rclone/rclone/issues/2130
func TestRenameOpenHandle(t *testing.T) {
run.skipIfNoFUSE(t)
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
run.checkDir(t, "")
// create file
example := []byte("Some Data")
path := run.path("rename")
file, err := osCreate(path)
require.NoError(t, err)
// write some data
_, err = file.Write(example)
require.NoError(t, err)
err = file.Sync()
require.NoError(t, err)
// attempt to rename open file
err = run.os.Rename(path, path+"bla")
require.NoError(t, err)
// close open writers to allow rename on remote to go through
err = file.Close()
require.NoError(t, err)
run.waitForWriters()
// verify file was renamed properly
run.checkDir(t, "renamebla 9")
// cleanup
run.rm(t, "renamebla")
run.checkDir(t, "")
}

View File

@ -0,0 +1,73 @@
package vfstest
import (
"os"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestFileModTime tests mod times on files
func TestFileModTime(t *testing.T) {
run.skipIfNoFUSE(t)
run.createFile(t, "file", "123")
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
err := run.os.Chtimes(run.path("file"), mtime, mtime)
require.NoError(t, err)
info, err := run.os.Stat(run.path("file"))
require.NoError(t, err)
// avoid errors because of timezone differences
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
run.rm(t, "file")
}
// run.os.Create without opening for write too
func osCreate(name string) (OsFiler, error) {
return run.os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
}
// run.os.Create with append
func osAppend(name string) (OsFiler, error) {
return run.os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0666)
}
// TestFileModTimeWithOpenWriters tests mod time on open files
func TestFileModTimeWithOpenWriters(t *testing.T) {
run.skipIfNoFUSE(t)
if runtime.GOOS == "windows" {
t.Skip("Skipping test on Windows")
}
mtime := time.Date(2012, time.November, 18, 17, 32, 31, 0, time.UTC)
filepath := run.path("cp-archive-test")
f, err := osCreate(filepath)
require.NoError(t, err)
_, err = f.Write([]byte{104, 105})
require.NoError(t, err)
err = run.os.Chtimes(filepath, mtime, mtime)
require.NoError(t, err)
err = f.Close()
require.NoError(t, err)
run.waitForWriters()
info, err := run.os.Stat(filepath)
require.NoError(t, err)
// avoid errors because of timezone differences
assert.Equal(t, info.ModTime().Unix(), mtime.Unix())
run.rm(t, "cp-archive-test")
}

View File

@ -0,0 +1,338 @@
// Test suite for rclonefs
package vfstest
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlink.org.cn/cloudream/common/pkgs/logger"
"gitlink.org.cn/cloudream/storage2/client/internal/mount"
)
const (
waitForWritersDelay = 30 * time.Second // time to wait for existing writers
testRoot = "b1/p1"
)
// RunTests runs all the tests against all the VFS cache modes
//
// If useVFS is set then it runs the tests against a VFS rather than a
// mount
//
// If useVFS is not set then it runs the mount in a subprocess in
// order to avoid kernel deadlocks.
func RunTests(t *testing.T, mnt *mount.Mount) {
run = &Run{
os: realOs{},
mountPath: mnt.MountPoint(),
mnt: mnt,
}
run.Init()
logger.Infof("Starting test run")
ok := t.Run("", func(t *testing.T) {
// t.Run("TestTouchAndDelete", TestTouchAndDelete)
// t.Run("TestRenameOpenHandle", TestRenameOpenHandle)
// t.Run("TestDirLs", TestDirLs)
// t.Run("TestDirCreateAndRemoveDir", TestDirCreateAndRemoveDir)
// t.Run("TestDirCreateAndRemoveFile", TestDirCreateAndRemoveFile)
// t.Run("TestDirRenameFile", TestDirRenameFile)
// t.Run("TestDirRenameEmptyDir", TestDirRenameEmptyDir)
// t.Run("TestDirRenameFullDir", TestDirRenameFullDir)
// t.Run("TestDirModTime", TestDirModTime)
// if enableCacheTests {
// t.Run("TestDirCacheFlush", TestDirCacheFlush)
// }
// t.Run("TestDirCacheFlushOnDirRename", TestDirCacheFlushOnDirRename)
// t.Run("TestFileModTime", TestFileModTime)
// t.Run("TestFileModTimeWithOpenWriters", TestFileModTimeWithOpenWriters)
// t.Run("TestMount", TestMount)
// t.Run("TestRoot", TestRoot)
// t.Run("TestReadByByte", TestReadByByte)
// t.Run("TestReadChecksum", TestReadChecksum)
// t.Run("TestReadFileDoubleClose", TestReadFileDoubleClose)
// t.Run("TestReadSeek", TestReadSeek)
// t.Run("TestWriteFileNoWrite", TestWriteFileNoWrite)
// t.Run("TestWriteFileWrite", TestWriteFileWrite)
// t.Run("TestWriteFileOverwrite", TestWriteFileOverwrite)
// t.Run("TestWriteFileDoubleClose", TestWriteFileDoubleClose)
// t.Run("TestWriteFileFsync", TestWriteFileFsync)
// t.Run("TestWriteFileDup", TestWriteFileDup)
t.Run("TestWriteFileAppend", TestWriteFileAppend)
})
logger.Infof("Finished test run (ok=%v)", ok)
run.Finalise()
}
// Run holds the remotes for a test run
type Run struct {
os Oser
mountPath string
skip bool
mnt *mount.Mount
}
// run holds the master Run data
var run *Run
func (r *Run) skipIfNoFUSE(t *testing.T) {
if r.skip {
t.Skip("FUSE not found so skipping test")
}
}
func (r *Run) skipIfVFS(t *testing.T) {
if r.skip {
t.Skip("Not running under VFS")
}
}
func (r *Run) Init() {
testRootDir := filepath.Join(r.mountPath, testRoot)
err := os.MkdirAll(testRootDir, 0644)
if err != nil {
logger.Infof("Failed to make test root dir %q: %v", testRootDir, err)
}
}
// Finalise cleans the remote and unmounts
func (r *Run) Finalise() {
r.mnt.Stop()
err := os.RemoveAll(r.mountPath)
if err != nil {
logger.Infof("Failed to clean mountPath %q: %v", r.mountPath, err)
}
}
// path returns an OS local path for filepath
func (r *Run) path(filePath string) string {
// return windows drive letter root as E:\
if filePath == "" && runtime.GOOS == "windows" {
return r.mountPath + `\`
}
return filepath.Join(r.mountPath, testRoot, filepath.FromSlash(filePath))
}
type dirMap map[string]struct{}
// Create a dirMap from a string
func newDirMap(dirString string) (dm dirMap) {
dm = make(dirMap)
for _, entry := range strings.Split(dirString, "|") {
if entry != "" {
dm[entry] = struct{}{}
}
}
return dm
}
// Returns a dirmap with only the files in
func (dm dirMap) filesOnly() dirMap {
newDm := make(dirMap)
for name := range dm {
if !strings.HasSuffix(name, "/") {
newDm[name] = struct{}{}
}
}
return newDm
}
// reads the local tree into dir
func (r *Run) readLocal(t *testing.T, dir dirMap, filePath string) {
realPath := r.path(filePath)
files, err := r.os.ReadDir(realPath)
require.NoError(t, err)
for _, fi := range files {
name := path.Join(filePath, fi.Name())
if fi.IsDir() {
dir[name+"/"] = struct{}{}
r.readLocal(t, dir, name)
// assert.Equal(t, os.FileMode(r.vfsOpt.DirPerms)&os.ModePerm, fi.Mode().Perm())
} else {
dir[fmt.Sprintf("%s %d", name, fi.Size())] = struct{}{}
// assert.Equal(t, os.FileMode(r.vfsOpt.FilePerms)&os.ModePerm, fi.Mode().Perm())
}
}
}
// reads the remote tree into dir
// func (r *Run) readRemote(t *testing.T, dir dirMap, filepath string) {
// // objs, dirs, err := walk.GetAll(context.Background(), r.fremote, filepath, true, 1)
// // if err == fs.ErrorDirNotFound {
// // return
// // }
// // require.NoError(t, err)
// // for _, obj := range objs {
// // dir[fmt.Sprintf("%s %d", obj.Remote(), obj.Size())] = struct{}{}
// // }
// // for _, d := range dirs {
// // name := d.Remote()
// // dir[name+"/"] = struct{}{}
// // r.readRemote(t, dir, name)
// // }
// }
// checkDir checks the local and remote against the string passed in
func (r *Run) checkDir(t *testing.T, dirString string) {
var retries = 3
sleep := time.Second / 5
var fuseOK bool
var dm, localDm dirMap
for i := 1; i <= retries; i++ {
dm = newDirMap(dirString)
localDm = make(dirMap)
r.readLocal(t, localDm, "")
fuseOK = reflect.DeepEqual(dm, localDm)
if fuseOK {
return
}
sleep *= 2
t.Logf("Sleeping for %v for list eventual consistency: %d/%d", sleep, i, retries)
time.Sleep(sleep)
}
assert.Equal(t, dm, localDm, "expected vs fuse mount")
}
// writeFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it with permissions perm;
// otherwise writeFile truncates it before writing.
// If there is an error writing then writeFile
// deletes it an existing file and tries again.
func writeFile(filename string, data []byte, perm os.FileMode) error {
f, err := run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
err = run.os.Remove(filename)
if err != nil {
return err
}
f, err = run.os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm)
if err != nil {
return err
}
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
func (r *Run) createFile(t *testing.T, filepath string, contents string) {
filepath = r.path(filepath)
err := writeFile(filepath, []byte(contents), 0644)
require.NoError(t, err)
r.waitForWriters()
}
func (r *Run) readFile(t *testing.T, filepath string) string {
filepath = r.path(filepath)
result, err := r.os.ReadFile(filepath)
require.NoError(t, err)
return string(result)
}
func (r *Run) mkdir(t *testing.T, filepath string) {
filepath = r.path(filepath)
err := r.os.Mkdir(filepath, 0755)
require.NoError(t, err)
}
func (r *Run) rm(t *testing.T, filepath string) {
filepath = r.path(filepath)
err := r.os.Remove(filepath)
require.NoError(t, err)
// Wait for file to disappear from listing
for i := 0; i < 100; i++ {
_, err := r.os.Stat(filepath)
if os.IsNotExist(err) {
return
}
time.Sleep(100 * time.Millisecond)
}
assert.Fail(t, "failed to delete file", filepath)
}
func (r *Run) rmdir(t *testing.T, filepath string) {
filepath = r.path(filepath)
err := r.os.Remove(filepath)
require.NoError(t, err)
}
func (r *Run) waitForWriters() {
timeout := waitForWritersDelay
tickTime := 10 * time.Millisecond
deadline := time.NewTimer(timeout)
defer deadline.Stop()
tick := time.NewTimer(tickTime)
defer tick.Stop()
tick.Stop()
for {
writers := 0
cacheInUse := 0
status := r.mnt.Dump()
for _, f := range status.Cache.ActiveFiles {
if f.RefCount > 0 {
writers++
}
}
cacheInUse = len(status.Cache.ActiveFiles)
if writers == 0 && cacheInUse == 0 {
return
}
logger.Debugf("Still %d writers active and %d cache items in use, waiting %v", writers, cacheInUse, tickTime)
tick.Reset(tickTime)
select {
case <-tick.C:
case <-deadline.C:
logger.Errorf("Exiting even though %d writers active and %d cache items in use after %v\n%v", writers, cacheInUse, timeout, status)
return
}
tickTime *= 2
if tickTime > time.Second {
tickTime = time.Second
}
}
}
// TestMount checks that the Fs is mounted by seeing if the mountpoint
// is in the mount output
// func TestMount(t *testing.T) {
// run.skipIfVFS(t)
// run.skipIfNoFUSE(t)
// if runtime.GOOS == "windows" {
// t.Skip("not running on windows")
// }
// out, err := exec.Command("mount").Output()
// require.NoError(t, err)
// assert.Contains(t, string(out), run.mountPath)
// }
// TestRoot checks root directory is present and correct
func TestRoot(t *testing.T) {
run.skipIfVFS(t)
run.skipIfNoFUSE(t)
fi, err := os.Lstat(run.mountPath)
require.NoError(t, err)
assert.True(t, fi.IsDir())
// assert.Equal(t, os.FileMode(run.vfsOpt.DirPerms)&os.ModePerm, fi.Mode().Perm())
}

View File

@ -0,0 +1,139 @@
package vfstest
import (
"os"
"time"
)
// Oser defines the things that the "os" package can do
//
// This covers what the VFS can do also
type Oser interface {
Chtimes(name string, atime time.Time, mtime time.Time) error
Create(name string) (OsFiler, error)
Mkdir(name string, perm os.FileMode) error
Open(name string) (OsFiler, error)
OpenFile(name string, flags int, perm os.FileMode) (fd OsFiler, err error)
ReadDir(dirname string) ([]os.FileInfo, error)
ReadFile(filename string) (b []byte, err error)
Remove(name string) error
Rename(oldName, newName string) error
Stat(path string) (os.FileInfo, error)
}
// realOs is an implementation of Oser backed by the "os" package
type realOs struct {
}
// realOsFile is an implementation of vfs.Handle
type realOsFile struct {
*os.File
}
// Flush
func (f realOsFile) Flush() error {
return nil
}
// Release
func (f realOsFile) Release() error {
return f.File.Close()
}
// Chtimes
func (r realOs) Chtimes(name string, atime time.Time, mtime time.Time) error {
return os.Chtimes(name, atime, mtime)
}
// Create
func (r realOs) Create(name string) (OsFiler, error) {
fd, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return nil, err
}
return realOsFile{File: fd}, err
}
// Mkdir
func (r realOs) Mkdir(name string, perm os.FileMode) error {
return os.Mkdir(name, perm)
}
// Open
func (r realOs) Open(name string) (OsFiler, error) {
fd, err := os.Open(name)
if err != nil {
return nil, err
}
return realOsFile{File: fd}, err
}
// OpenFile
func (r realOs) OpenFile(name string, flags int, perm os.FileMode) (OsFiler, error) {
fd, err := os.OpenFile(name, flags, perm)
if err != nil {
return nil, err
}
return realOsFile{File: fd}, err
}
// ReadDir
func (r realOs) ReadDir(dirname string) ([]os.FileInfo, error) {
entries, err := os.ReadDir(dirname)
if err != nil {
return nil, err
}
infos := make([]os.FileInfo, 0, len(entries))
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
return nil, err
}
infos = append(infos, info)
}
return infos, nil
}
// ReadFile
func (r realOs) ReadFile(filename string) (b []byte, err error) {
return os.ReadFile(filename)
}
// Remove
func (r realOs) Remove(name string) error {
return os.Remove(name)
}
// Rename
func (r realOs) Rename(oldName, newName string) error {
return os.Rename(oldName, newName)
}
// Stat
func (r realOs) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
// Check interfaces
var _ Oser = &realOs{}
// OsFiler is the methods on *os.File
type OsFiler interface {
Chdir() error
Chmod(mode os.FileMode) error
Chown(uid, gid int) error
Close() error
Fd() uintptr
Name() string
Read(b []byte) (n int, err error)
ReadAt(b []byte, off int64) (n int, err error)
Readdir(n int) ([]os.FileInfo, error)
Readdirnames(n int) (names []string, err error)
Seek(offset int64, whence int) (ret int64, err error)
Stat() (os.FileInfo, error)
Sync() error
Truncate(size int64) error
Write(b []byte) (n int, err error)
WriteAt(b []byte, off int64) (n int, err error)
WriteString(s string) (n int, err error)
}

View File

@ -0,0 +1,123 @@
package vfstest
import (
"io"
"testing"
"github.com/stretchr/testify/assert"
)
// TestReadByByte reads by byte including don't read any bytes
func TestReadByByte(t *testing.T) {
run.skipIfNoFUSE(t)
var data = []byte("hellohello")
run.createFile(t, "testfile", string(data))
run.checkDir(t, "testfile 10")
for i := 0; i < len(data); i++ {
fd, err := run.os.Open(run.path("testfile"))
assert.NoError(t, err)
for j := 0; j < i; j++ {
buf := make([]byte, 1)
n, err := io.ReadFull(fd, buf)
assert.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, buf[0], data[j])
}
err = fd.Close()
assert.NoError(t, err)
}
run.rm(t, "testfile")
}
// TestReadChecksum checks the checksum reading is working
func TestReadChecksum(t *testing.T) {
run.skipIfNoFUSE(t)
// create file big enough so we exceed any single FUSE read
// request
b := make([]rune, 3*128*1024)
for i := range b {
b[i] = 'r'
}
run.createFile(t, "bigfile", string(b))
// The hash comparison would fail in Flush, if we did not
// ensure we read the whole file
fd, err := run.os.Open(run.path("bigfile"))
assert.NoError(t, err)
buf := make([]byte, 10)
_, err = io.ReadFull(fd, buf)
assert.NoError(t, err)
err = fd.Close()
assert.NoError(t, err)
// The hash comparison would fail, because we only read parts
// of the file
fd, err = run.os.Open(run.path("bigfile"))
assert.NoError(t, err)
// read at start
_, err = io.ReadFull(fd, buf)
assert.NoError(t, err)
// read at end
_, err = fd.Seek(int64(len(b)-len(buf)), io.SeekStart)
assert.NoError(t, err)
_, err = io.ReadFull(fd, buf)
assert.NoError(t, err)
// ensure we don't compare hashes
err = fd.Close()
assert.NoError(t, err)
run.rm(t, "bigfile")
}
// TestReadSeek test seeking
func TestReadSeek(t *testing.T) {
run.skipIfNoFUSE(t)
var data = []byte("helloHELLO")
run.createFile(t, "testfile", string(data))
run.checkDir(t, "testfile 10")
fd, err := run.os.Open(run.path("testfile"))
assert.NoError(t, err)
// Seek to half way
_, err = fd.Seek(5, io.SeekStart)
assert.NoError(t, err)
buf, err := io.ReadAll(fd)
assert.NoError(t, err)
assert.Equal(t, buf, []byte("HELLO"))
// Test seeking to the end
_, err = fd.Seek(10, io.SeekStart)
assert.NoError(t, err)
buf, err = io.ReadAll(fd)
assert.NoError(t, err)
assert.Equal(t, buf, []byte(""))
// Test seeking beyond the end
_, err = fd.Seek(1000000, io.SeekStart)
assert.NoError(t, err)
buf, err = io.ReadAll(fd)
assert.NoError(t, err)
assert.Equal(t, buf, []byte(""))
// Now back to the start
_, err = fd.Seek(0, io.SeekStart)
assert.NoError(t, err)
buf, err = io.ReadAll(fd)
assert.NoError(t, err)
assert.Equal(t, buf, []byte("helloHELLO"))
err = fd.Close()
assert.NoError(t, err)
run.rm(t, "testfile")
}

View File

@ -0,0 +1,13 @@
//go:build !linux && !darwin && !freebsd
package vfstest
import (
"runtime"
"testing"
)
// TestReadFileDoubleClose tests double close on read
func TestReadFileDoubleClose(t *testing.T) {
t.Skip("not supported on " + runtime.GOOS)
}

View File

@ -0,0 +1,53 @@
//go:build linux || darwin || freebsd
package vfstest
import (
"syscall"
"testing"
"github.com/stretchr/testify/assert"
)
// TestReadFileDoubleClose tests double close on read
func TestReadFileDoubleClose(t *testing.T) {
run.skipIfVFS(t)
run.skipIfNoFUSE(t)
run.createFile(t, "testdoubleclose", "hello")
in, err := run.os.Open(run.path("testdoubleclose"))
assert.NoError(t, err)
fd := in.Fd()
fd1, err := syscall.Dup(int(fd))
assert.NoError(t, err)
fd2, err := syscall.Dup(int(fd))
assert.NoError(t, err)
// close one of the dups - should produce no error
err = syscall.Close(fd1)
assert.NoError(t, err)
// read from the file
buf := make([]byte, 1)
_, err = in.Read(buf)
assert.NoError(t, err)
// close it
err = in.Close()
assert.NoError(t, err)
// read from the other dup - should produce no error as this
// file is now buffered
n, err := syscall.Read(fd2, buf)
assert.NoError(t, err)
assert.Equal(t, 1, n)
// close the dup - should not produce an error
err = syscall.Close(fd2)
assert.NoError(t, err, "input/output error")
run.rm(t, "testdoubleclose")
}

View File

@ -0,0 +1,170 @@
package vfstest
import (
"os"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestWriteFileNoWrite tests writing a file with no write()'s to it
func TestWriteFileNoWrite(t *testing.T) {
run.skipIfNoFUSE(t)
fd, err := osCreate(run.path("testnowrite"))
require.NoError(t, err)
err = fd.Close()
assert.NoError(t, err)
run.waitForWriters()
run.checkDir(t, "testnowrite 0")
run.rm(t, "testnowrite")
}
// FIXMETestWriteOpenFileInDirListing tests open file in directory listing
func FIXMETestWriteOpenFileInDirListing(t *testing.T) {
run.skipIfNoFUSE(t)
fd, err := osCreate(run.path("testnowrite"))
assert.NoError(t, err)
run.checkDir(t, "testnowrite 0")
err = fd.Close()
assert.NoError(t, err)
run.waitForWriters()
run.rm(t, "testnowrite")
}
// TestWriteFileWrite tests writing a file and reading it back
func TestWriteFileWrite(t *testing.T) {
run.skipIfNoFUSE(t)
run.createFile(t, "testwrite", "data")
run.checkDir(t, "testwrite 4")
contents := run.readFile(t, "testwrite")
assert.Equal(t, "data", contents)
run.rm(t, "testwrite")
}
// TestWriteFileOverwrite tests overwriting a file
func TestWriteFileOverwrite(t *testing.T) {
run.skipIfNoFUSE(t)
run.createFile(t, "testwrite", "data")
run.checkDir(t, "testwrite 4")
run.createFile(t, "testwrite", "potato")
contents := run.readFile(t, "testwrite")
assert.Equal(t, "potato", contents)
run.rm(t, "testwrite")
}
// TestWriteFileFsync tests Fsync
//
// NB the code for this is in file.go rather than write.go
func TestWriteFileFsync(t *testing.T) {
run.skipIfNoFUSE(t)
filepath := run.path("to be synced")
fd, err := osCreate(filepath)
require.NoError(t, err)
_, err = fd.Write([]byte("hello"))
require.NoError(t, err)
err = fd.Sync()
require.NoError(t, err)
err = fd.Close()
require.NoError(t, err)
run.waitForWriters()
run.rm(t, "to be synced")
}
// TestWriteFileDup tests behavior of mmap() in Python by using dup() on a file handle
func TestWriteFileDup(t *testing.T) {
run.skipIfVFS(t)
run.skipIfNoFUSE(t)
filepath := run.path("to be synced")
fh, err := osCreate(filepath)
require.NoError(t, err)
testData := []byte("0123456789")
err = fh.Truncate(int64(len(testData) + 2))
require.NoError(t, err)
err = fh.Sync()
require.NoError(t, err)
var dupFd uintptr
dupFd, err = writeTestDup(fh.Fd())
require.NoError(t, err)
dupFile := os.NewFile(dupFd, fh.Name())
_, err = dupFile.Write(testData)
require.NoError(t, err)
err = dupFile.Close()
require.NoError(t, err)
_, err = fh.Seek(int64(len(testData)), 0)
require.NoError(t, err)
_, err = fh.Write([]byte("10"))
require.NoError(t, err)
err = fh.Close()
require.NoError(t, err)
run.waitForWriters()
run.rm(t, "to be synced")
}
// TestWriteFileAppend tests that O_APPEND works on cache backends >= writes
func TestWriteFileAppend(t *testing.T) {
run.skipIfNoFUSE(t)
// TODO: Windows needs the v1.5 release of WinFsp to handle O_APPEND properly.
// Until it gets released, skip this test on Windows.
if runtime.GOOS == "windows" {
t.Skip("currently unsupported on Windows")
}
filepath := run.path("to be synced")
fh, err := osCreate(filepath)
require.NoError(t, err)
testData := []byte("0123456789")
appendData := []byte("10")
_, err = fh.Write(testData)
require.NoError(t, err)
err = fh.Close()
require.NoError(t, err)
fh, err = osAppend(filepath)
require.NoError(t, err)
_, err = fh.Write(appendData)
require.NoError(t, err)
err = fh.Close()
require.NoError(t, err)
<-time.After(time.Second * 10)
info, err := run.os.Stat(filepath)
require.NoError(t, err)
require.EqualValues(t, len(testData)+len(appendData), info.Size())
run.waitForWriters()
run.rm(t, "to be synced")
}

View File

@ -0,0 +1,20 @@
//go:build !linux && !darwin && !freebsd && !windows
// +build !linux,!darwin,!freebsd,!windows
package vfstest
import (
"errors"
"runtime"
"testing"
)
// TestWriteFileDoubleClose tests double close on write
func TestWriteFileDoubleClose(t *testing.T) {
t.Skip("not supported on " + runtime.GOOS)
}
// writeTestDup performs the platform-specific implementation of the dup() unix
func writeTestDup(oldfd uintptr) (uintptr, error) {
return 0, errors.New("not supported on " + runtime.GOOS)
}

View File

@ -0,0 +1,62 @@
//go:build linux || darwin || freebsd
package vfstest
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
// TestWriteFileDoubleClose tests double close on write
func TestWriteFileDoubleClose(t *testing.T) {
run.skipIfVFS(t)
run.skipIfNoFUSE(t)
if runtime.GOOS == "darwin" {
t.Skip("Skipping test on OSX")
}
out, err := osCreate(run.path("testdoubleclose"))
require.NoError(t, err)
fd := out.Fd()
fd1, err := unix.Dup(int(fd))
assert.NoError(t, err)
fd2, err := unix.Dup(int(fd))
assert.NoError(t, err)
// close one of the dups - should produce no error
err = unix.Close(fd1)
assert.NoError(t, err)
// write to the file
buf := []byte("hello")
n, err := out.Write(buf)
assert.NoError(t, err)
assert.Equal(t, 5, n)
// close it
err = out.Close()
assert.NoError(t, err)
// write to the other dup
_, err = unix.Write(fd2, buf)
assert.NoError(t, err)
// close the dup - should not produce an error
err = unix.Close(fd2)
assert.NoError(t, err)
run.waitForWriters()
run.rm(t, "testdoubleclose")
}
// writeTestDup performs the platform-specific implementation of the dup() unix
func writeTestDup(oldfd uintptr) (uintptr, error) {
newfd, err := unix.Dup(int(oldfd))
return uintptr(newfd), err
}

View File

@ -0,0 +1,22 @@
//go:build windows
package vfstest
import (
"runtime"
"testing"
"golang.org/x/sys/windows"
)
// TestWriteFileDoubleClose tests double close on write
func TestWriteFileDoubleClose(t *testing.T) {
t.Skip("not supported on " + runtime.GOOS)
}
// writeTestDup performs the platform-specific implementation of the dup() syscall
func writeTestDup(oldfd uintptr) (uintptr, error) {
p := windows.CurrentProcess()
var h windows.Handle
return uintptr(h), windows.DuplicateHandle(p, windows.Handle(oldfd), p, &h, 0, true, windows.DUPLICATE_SAME_ACCESS)
}

View File

@ -382,27 +382,7 @@ func (svc *ObjectService) Delete(objectIDs []types.ObjectID) error {
}
sucs = lo.Keys(avaiIDs)
err = svc.DB.Object().BatchDelete(tx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting objects: %w", err)
}
err = svc.DB.ObjectBlock().BatchDeleteByObjectID(tx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting object blocks: %w", err)
}
err = svc.DB.PinnedObject().BatchDeleteByObjectID(tx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting pinned objects: %w", err)
}
err = svc.DB.ObjectAccessStat().BatchDeleteByObjectID(tx, objectIDs)
if err != nil {
return fmt.Errorf("batch deleting object access stats: %w", err)
}
return nil
return svc.DB.Object().BatchDeleteComplete(tx, sucs)
})
if err != nil {
return err