Skip to content

Commit

Permalink
refactor: replace afero with avfs
Browse files Browse the repository at this point in the history
[avfs](https://github.com/avfs/avfs) does the same thing as afero,
but provides a more complete abstraction layer, and a more
fully-functional MemFS for testing, i.e., it supports symlinks.

Move all file operations over to avfs, including `exec.RunInTempDir`,
and add unit tests for symlink handling.
  • Loading branch information
nisimond authored and retr0h committed Feb 27, 2024
1 parent bc0f5b7 commit bf7efae
Show file tree
Hide file tree
Showing 22 changed files with 196 additions and 163 deletions.
2 changes: 1 addition & 1 deletion examples/go-client/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ require (
)

require (
github.com/avfs/avfs v0.33.0 // indirect
github.com/danjacques/gofslock v0.0.0-20230728142113-ae8f59f9e88b // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.18.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions examples/go-client/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/avfs/avfs v0.33.0 h1:5WQXbUbr6VS7aani39ZN2Vrd/s3wLnyih1Sc4ExWTxs=
github.com/avfs/avfs v0.33.0/go.mod h1:Q59flcFRYe9KYkNMfrLUJney3yeKGQpcWRyxsDBW7vI=
github.com/danjacques/gofslock v0.0.0-20230728142113-ae8f59f9e88b h1:BBkZ6LZYtzMQ2Oo5LkovMmUp0gxAD+AnXzfknZlFTBo=
github.com/danjacques/gofslock v0.0.0-20230728142113-ae8f59f9e88b/go.mod h1:9LABMmUSkKzt6FVQNEWdUTM0bz8Bt8MPyEcuZe0Sr8c=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand All @@ -20,8 +22,6 @@ github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module github.com/retr0h/gilt/v2
go 1.21.0

require (
github.com/avfs/avfs v0.33.0
github.com/danjacques/gofslock v0.0.0-20230728142113-ae8f59f9e88b
github.com/go-playground/validator/v10 v10.18.0
github.com/golang/mock v1.6.0
github.com/lmittmann/tint v1.0.4
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
Expand All @@ -32,6 +32,7 @@ require (
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/avfs/avfs v0.33.0 h1:5WQXbUbr6VS7aani39ZN2Vrd/s3wLnyih1Sc4ExWTxs=
github.com/avfs/avfs v0.33.0/go.mod h1:Q59flcFRYe9KYkNMfrLUJney3yeKGQpcWRyxsDBW7vI=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
Expand Down
9 changes: 6 additions & 3 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@ package exec

import (
"log/slog"
"os"
"os/exec"
"strings"

"github.com/avfs/avfs"
)

// New factory to create a new Exec instance.
func New(
appFs avfs.VFS,
logger *slog.Logger,
) *Exec {
return &Exec{
appFs: appFs,
logger: logger,
}
}
Expand Down Expand Up @@ -79,7 +82,7 @@ func (e *Exec) RunCmdInDir(
// with the name of the directory as input. Then it cleans up the temporary
// directory.
func (e *Exec) RunInTempDir(dir, pattern string, fn func(string) error) error {
tmpDir, err := os.MkdirTemp(dir, pattern)
tmpDir, err := e.appFs.MkdirTemp(dir, pattern)
if err != nil {
return err
}
Expand All @@ -88,7 +91,7 @@ func (e *Exec) RunInTempDir(dir, pattern string, fn func(string) error) error {
// Ignoring errors as there's not much we can do and this is a cleanup function.
defer func() {
e.logger.Debug("removing tempdir", slog.String("dir", tmpDir))
_ = os.RemoveAll(tmpDir)
_ = e.appFs.RemoveAll(tmpDir)
}()

// Run the provided function.
Expand Down
2 changes: 2 additions & 0 deletions internal/exec/exec_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"testing"

"github.com/avfs/avfs/vfs/memfs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

Expand All @@ -38,6 +39,7 @@ type ExecManagerPublicTestSuite struct {

func (suite *ExecManagerPublicTestSuite) NewTestExecManager() internal.ExecManager {
return exec.New(
memfs.New(),
slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})),
Expand Down
3 changes: 3 additions & 0 deletions internal/exec/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ package exec

import (
"log/slog"

"github.com/avfs/avfs"
)

// Exec disk implementation.
type Exec struct {
appFs avfs.VFS
logger *slog.Logger
}
10 changes: 4 additions & 6 deletions internal/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,15 @@ package git

import (
"log/slog"
"os"
"path/filepath"

"github.com/spf13/afero"
"github.com/avfs/avfs"

"github.com/retr0h/gilt/v2/internal"
)

// New factory to create a new Git instance.
func New(
appFs afero.Fs,
appFs avfs.VFS,
execManager internal.ExecManager,
logger *slog.Logger,
) *Git {
Expand Down Expand Up @@ -71,7 +69,7 @@ func (g *Git) Worktree(
version string,
dstDir string,
) error {
dst, err := filepath.Abs(dstDir)
dst, err := g.appFs.Abs(dstDir)
if err != nil {
return err
}
Expand All @@ -91,7 +89,7 @@ func (g *Git) Worktree(
// `git worktree add` creates a breadcrumb file back to the original repo;
// this is just junk data in our use case, so get rid of it
if err == nil {
_ = os.Remove(filepath.Join(dst, ".git"))
_ = g.appFs.Remove(g.appFs.Join(dst, ".git"))
_ = g.execManager.RunCmdInDir("git", []string{"worktree", "prune", "--verbose"}, cloneDir)
}
return err
Expand Down
4 changes: 2 additions & 2 deletions internal/git/git_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import (
"os"
"testing"

"github.com/avfs/avfs/vfs/memfs"
"github.com/golang/mock/gomock"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

Expand All @@ -52,7 +52,7 @@ type GitManagerPublicTestSuite struct {

func (suite *GitManagerPublicTestSuite) NewTestGitManager() internal.GitManager {
return git.New(
afero.NewMemMapFs(),
memfs.New(),
suite.mockExec,
slog.New(slog.NewTextHandler(os.Stdout, nil)),
)
Expand Down
4 changes: 2 additions & 2 deletions internal/git/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ package git
import (
"log/slog"

"github.com/spf13/afero"
"github.com/avfs/avfs"

"github.com/retr0h/gilt/v2/internal"
)

// Git implementation responsible for Git operations.
type Git struct {
appFs afero.Fs
appFs avfs.VFS
execManager internal.ExecManager
logger *slog.Logger
}
18 changes: 8 additions & 10 deletions internal/repositories/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,9 @@ package repositories

import (
"log/slog"
"os"
"path/filepath"
"strings"

"github.com/spf13/afero"
"github.com/avfs/avfs"

"github.com/retr0h/gilt/v2/internal"
intPath "github.com/retr0h/gilt/v2/internal/path"
Expand All @@ -35,7 +33,7 @@ import (

// New factory to create a new Repository instance.
func New(
appFs afero.Fs,
appFs avfs.VFS,
c config.Repositories,
repoManager internal.RepositoryManager,
execManager internal.ExecManager,
Expand All @@ -62,13 +60,13 @@ func (r *Repositories) getCacheDir() (string, error) {
return "", err
}

cacheDir := filepath.Join(giltDir, "cache")
if _, err := r.appFs.Stat(cacheDir); os.IsNotExist(err) {
if err := r.appFs.Mkdir(cacheDir, 0o700); err != nil {
return "", err
cacheDir := r.appFs.Join(giltDir, "cache")
if err := r.appFs.MkdirAll(cacheDir, 0o700); err != nil {
if i, e := r.appFs.Stat(cacheDir); e == nil && i.IsDir() {
return cacheDir, nil
}
return "", err
}

return cacheDir, nil
}

Expand Down Expand Up @@ -112,7 +110,7 @@ func (r *Repositories) Overlay() error {
return err
}
err = r.execManager.RunInTempDir(giltDir, "tmp", func(tmpDir string) error {
tmpClone := filepath.Join(tmpDir, filepath.Base(targetDir))
tmpClone := r.appFs.Join(tmpDir, r.appFs.Base(targetDir))
if err := r.repoManager.Worktree(c, targetDir, tmpClone); err != nil {
return err
}
Expand Down
19 changes: 10 additions & 9 deletions internal/repositories/repositories_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import (
"errors"
"log/slog"
"os"
"path/filepath"
"testing"

"github.com/avfs/avfs"
"github.com/avfs/avfs/vfs/memfs"
"github.com/avfs/avfs/vfs/rofs"
"github.com/golang/mock/gomock"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

Expand All @@ -46,7 +47,7 @@ type RepositoriesPublicTestSuite struct {
mockRepo *repository.MockRepositoryManager
mockExec *exec.MockExecManager

appFs afero.Fs
appFs avfs.VFS
dstDir string
giltDir string
gitURL string
Expand Down Expand Up @@ -80,7 +81,7 @@ func (suite *RepositoriesPublicTestSuite) SetupTest() {
suite.mockExec = exec.NewMockExecManager(suite.ctrl)
defer suite.ctrl.Finish()

suite.appFs = afero.NewMemMapFs()
suite.appFs = memfs.New()
suite.dstDir = "/dstDir"
suite.giltDir = "/giltDir"
suite.gitURL = "https://example.com/user/repo.git"
Expand All @@ -98,7 +99,7 @@ func (suite *RepositoriesPublicTestSuite) SetupTest() {

func (suite *RepositoriesPublicTestSuite) TestOverlayOkWhenDstDir() {
repos := suite.NewTestRepositoriesManager(suite.repoConfigDstDir)
expected := filepath.Join(suite.giltDir, "cache")
expected := suite.appFs.Join(suite.giltDir, "cache")

suite.mockRepo.EXPECT().
Clone(suite.repoConfigDstDir[0], expected).
Expand All @@ -113,7 +114,7 @@ func (suite *RepositoriesPublicTestSuite) TestOverlayOkWhenDstDir() {

func (suite *RepositoriesPublicTestSuite) TestOverlayErrorWhenDstDir() {
repos := suite.NewTestRepositoriesManager(suite.repoConfigDstDir)
expected := filepath.Join(suite.giltDir, "cache")
expected := suite.appFs.Join(suite.giltDir, "cache")
errors := errors.New("tests error")

suite.mockRepo.EXPECT().
Expand All @@ -129,7 +130,7 @@ func (suite *RepositoriesPublicTestSuite) TestOverlayErrorWhenDstDir() {

func (suite *RepositoriesPublicTestSuite) TestOverlayCacheDirCreateError() {
// Replace the test FS with a read-only copy
suite.appFs = afero.NewReadOnlyFs(suite.appFs)
suite.appFs = rofs.New(suite.appFs)
repos := suite.NewTestRepositoriesManager(suite.repoConfigDstDir)
err := repos.Overlay()
assert.Error(suite.T(), err)
Expand All @@ -148,10 +149,10 @@ func (suite *RepositoriesPublicTestSuite) TestOverlayDstDirExists() {

func (suite *RepositoriesPublicTestSuite) TestOverlayErrorRemovingDstDir() {
suite.mockRepo.EXPECT().Clone(gomock.Any(), gomock.Any()).Return("", nil)
_ = suite.appFs.MkdirAll(filepath.Join(suite.giltDir, "cache"), 0o700)
_ = suite.appFs.MkdirAll(suite.appFs.Join(suite.giltDir, "cache"), 0o700)
_ = suite.appFs.MkdirAll(suite.dstDir, 0o755)
// Replace the test FS with a read-only copy
suite.appFs = afero.NewReadOnlyFs(suite.appFs)
suite.appFs = rofs.New(suite.appFs)
repos := suite.NewTestRepositoriesManager(suite.repoConfigDstDir)
err := repos.Overlay()
assert.Error(suite.T(), err)
Expand Down
12 changes: 7 additions & 5 deletions internal/repositories/repositories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
"os"
"testing"

"github.com/avfs/avfs"
"github.com/avfs/avfs/vfs/memfs"
"github.com/avfs/avfs/vfs/rofs"
"github.com/golang/mock/gomock"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"

Expand All @@ -42,7 +44,7 @@ type RepositoriesTestSuite struct {
mockRepo *repository.MockRepositoryManager
mockExec *exec.MockExecManager

appFs afero.Fs
appFs avfs.VFS
giltDir string
logger *slog.Logger
}
Expand Down Expand Up @@ -72,7 +74,7 @@ func (suite *RepositoriesTestSuite) SetupTest() {
suite.mockExec = exec.NewMockExecManager(suite.ctrl)
defer suite.ctrl.Finish()

suite.appFs = afero.NewMemMapFs()
suite.appFs = memfs.New()
suite.giltDir = "/giltDir"

suite.logger = slog.New(slog.NewTextHandler(os.Stdout, nil))
Expand All @@ -86,14 +88,14 @@ func (suite *RepositoriesTestSuite) TestgetCacheDir() {
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), expectedDir, got)

exists, err := afero.Exists(suite.appFs, expectedDir)
exists, err := avfs.Exists(suite.appFs, expectedDir)
assert.NoError(suite.T(), err)
assert.True(suite.T(), exists)
}

func (suite *RepositoriesTestSuite) TestgetCacheDirCannotCreateError() {
// Replace the test FS with a read-only copy
suite.appFs = afero.NewReadOnlyFs(suite.appFs)
suite.appFs = rofs.New(suite.appFs)
repos := suite.NewTestRepositories(suite.giltDir)

got, err := repos.getCacheDir()
Expand Down
4 changes: 2 additions & 2 deletions internal/repositories/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ package repositories
import (
"log/slog"

"github.com/spf13/afero"
"github.com/avfs/avfs"

"github.com/retr0h/gilt/v2/internal"
"github.com/retr0h/gilt/v2/pkg/config"
)

// Repositories perform repository operations.
type Repositories struct {
appFs afero.Fs
appFs avfs.VFS
config config.Repositories
logger *slog.Logger

Expand Down
Loading

0 comments on commit bf7efae

Please sign in to comment.