From 5da7e0e96ecc2407cc01e339a59ed115d4450286 Mon Sep 17 00:00:00 2001 From: Felicitas Pojtinger Date: Sun, 16 Jan 2022 02:45:32 +0100 Subject: [PATCH] feat: Add tests for `LstatIfPossible` --- pkg/config/config.go | 1 + pkg/fs/filesystem.go | 7 +- pkg/fs/filesystem_test.go | 184 +++++++++++++++++++++++++++++++++++++ pkg/inventory/stat.go | 6 +- pkg/persisters/metadata.go | 14 +++ 5 files changed, 207 insertions(+), 5 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index aace373..3c2ca4f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -68,6 +68,7 @@ type MetadataPersister interface { MoveHeader(ctx context.Context, oldName string, newName string, lastknownrecord, lastknownblock int64) error GetHeaders(ctx context.Context) ([]*Header, error) GetHeader(ctx context.Context, name string) (*Header, error) + GetHeaderByLinkname(ctx context.Context, linkname string) (*Header, error) GetHeaderChildren(ctx context.Context, name string) ([]*Header, error) GetRootPath(ctx context.Context) (string, error) GetHeaderDirectChildren(ctx context.Context, name string, limit int) ([]*Header, error) diff --git a/pkg/fs/filesystem.go b/pkg/fs/filesystem.go index 754c880..ac58de5 100644 --- a/pkg/fs/filesystem.go +++ b/pkg/fs/filesystem.go @@ -119,8 +119,11 @@ func (f *STFS) Create(name string) (afero.File, error) { func (f *STFS) mknodeWithoutLocking(dir bool, name string, perm os.FileMode, overwrite bool, linkname string, initializing bool) error { f.log.Trace("FileSystem.mknodeWithoutLocking", map[string]interface{}{ - "name": name, - "perm": perm, + "name": name, + "perm": perm, + "overwrite": overwrite, + "linkname": linkname, + "initializing": initializing, }) if f.readOnly { diff --git a/pkg/fs/filesystem_test.go b/pkg/fs/filesystem_test.go index 68776cc..a5abbd1 100644 --- a/pkg/fs/filesystem_test.go +++ b/pkg/fs/filesystem_test.go @@ -25,6 +25,7 @@ import ( "github.com/pojntfx/stfs/pkg/tape" "github.com/pojntfx/stfs/pkg/utility" "github.com/spf13/afero" + "github.com/volatiletech/sqlboiler/v4/boil" ) const ( @@ -91,6 +92,12 @@ type cryptoConfig struct { func TestMain(m *testing.M) { flag.Parse() // So that `testing.Short` can be called, see https://go-review.googlesource.com/c/go/+/7604/ + + if verbose { + boil.DebugMode = false + boil.DebugWriter = os.Stderr + } + if testing.Short() { for _, writeCacheType := range config.KnownWriteCacheTypes { for _, fileSystemCacheType := range config.KnownFileSystemCacheTypes { @@ -2886,3 +2893,180 @@ func TestSTFS_Chtimes(t *testing.T) { }) } } + +type lstatArgs struct { + name string +} + +var lstatTests = []struct { + name string + args lstatArgs + wantErr bool + prepare func(*STFS) error + check func(os.FileInfo) error + withCache bool + withOsFs bool +}{ + { + "Can not lstat /", + lstatArgs{"/"}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo) error { return nil }, + true, + true, + }, + { + "Can not lstat /test.txt without creating it", + lstatArgs{"/test.txt"}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo) error { return nil }, + true, + true, + }, + { + "Can lstat /test2.txt after creating /test.txt and symlinking it", + lstatArgs{"/test2.txt"}, + false, + func(f *STFS) error { + if _, err := f.Create("/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo) error { + want := "test.txt" + got := f.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Can not lstat /mydir/test.txt without creating it", + lstatArgs{"/mydir/test.txt"}, + true, + func(f *STFS) error { return nil }, + func(f os.FileInfo) error { return nil }, + true, + true, + }, + { + "Can lstat /mydir/test2.txt after creating /mydir/test.txt and symlinking it", + lstatArgs{"/mydir/test2.txt"}, + false, + func(f *STFS) error { + if err := f.Mkdir("/mydir", os.ModePerm); err != nil { + return err + } + + if _, err := f.Create("/mydir/test.txt"); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/mydir/test.txt", "/mydir/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo) error { + want := "test.txt" + got := f.Name() + + if want != got { + return fmt.Errorf("invalid name, got %v, want %v", got, want) + } + + return nil + }, + true, + true, + }, + { + "Result of lstat /test2.txt after creating /test.txt and symlinking it matches provided values", + lstatArgs{"/test2.txt"}, + false, + func(f *STFS) error { + file, err := f.OpenFile("/test.txt", os.O_CREATE, os.ModePerm) + if err != nil { + return err + } + if err := file.Close(); err != nil { + return err + } + + if err := f.SymlinkIfPossible("/test.txt", "/test2.txt"); err != nil { + return err + } + + return nil + }, + func(f os.FileInfo) error { + wantName := "test.txt" + gotName := f.Name() + + if wantName != gotName { + return fmt.Errorf("invalid name, got %v, want %v", gotName, wantName) + } + + wantPerm := os.ModePerm + gotPerm := f.Mode().Perm() + + if wantPerm != gotPerm { + return fmt.Errorf("invalid perm, got %v, want %v", gotPerm, wantPerm) + } + + return nil + }, + true, + true, + }, +} + +func TestSTFS_Lstat(t *testing.T) { + for _, tt := range lstatTests { + tt := tt + + runTestForAllFss(t, tt.name, true, tt.withCache, tt.withOsFs, func(t *testing.T, fs fsConfig) { + stfs, ok := fs.fs.(*STFS) + if !ok { + return + } + + if err := tt.prepare(stfs); err != nil { + t.Errorf("%v prepare() error = %v", stfs.Name(), err) + + return + } + + got, possible, err := stfs.LstatIfPossible(tt.args.name) + if !possible { + t.Errorf("%v.LstatIfPossible() possible = %v, want %v", stfs.Name(), possible, true) + } + + if (err != nil) != tt.wantErr { + t.Errorf("%v.LstatIfPossible() error = %v, wantErr %v", stfs.Name(), err, tt.wantErr) + + return + } + + if err := tt.check(got); err != nil { + t.Errorf("%v check() error = %v", stfs.Name(), err) + + return + } + }) + } +} diff --git a/pkg/inventory/stat.go b/pkg/inventory/stat.go index b85814e..ff564b4 100644 --- a/pkg/inventory/stat.go +++ b/pkg/inventory/stat.go @@ -23,10 +23,10 @@ func Stat( if symlink { // Resolve symlink - link, err := metadata.Metadata.GetHeader(context.Background(), name) + link, err := metadata.Metadata.GetHeaderByLinkname(context.Background(), name) if err != nil { if err == sql.ErrNoRows { - link, err = metadata.Metadata.GetHeader(context.Background(), strings.TrimSuffix(name, "/")+"/") + link, err = metadata.Metadata.GetHeaderByLinkname(context.Background(), strings.TrimSuffix(name, "/")+"/") if err != nil { return nil, err } @@ -35,7 +35,7 @@ func Stat( } } - name = link.Linkname + name = link.Name } dbhdr, err := metadata.Metadata.GetHeader(context.Background(), name) diff --git a/pkg/persisters/metadata.go b/pkg/persisters/metadata.go index 646b94f..2f8d718 100644 --- a/pkg/persisters/metadata.go +++ b/pkg/persisters/metadata.go @@ -217,6 +217,20 @@ func (p *MetadataPersister) GetHeader(ctx context.Context, name string) (*config return converters.DBHeaderToConfigHeader(hdr), nil } +func (p *MetadataPersister) GetHeaderByLinkname(ctx context.Context, linkname string) (*config.Header, error) { + linkname = p.getSanitizedPath(ctx, linkname) + + hdr, err := models.Headers( + qm.Where(models.HeaderColumns.Linkname+" = ?", linkname), + qm.Where(models.HeaderColumns.Deleted+" != 1"), + ).One(ctx, p.sqlite.DB) + if err != nil { + return nil, err + } + + return converters.DBHeaderToConfigHeader(hdr), nil +} + func (p *MetadataPersister) GetHeaderChildren(ctx context.Context, name string) ([]*config.Header, error) { name = p.getSanitizedPath(ctx, name)