-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement MakeDirectoryArtifactsProcessor (#4)
* Implement MakeDirectoryArtifactsProcessor
- Loading branch information
Showing
8 changed files
with
532 additions
and
317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package specter | ||
|
||
import ( | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
) | ||
|
||
// FileSystem is an abstraction layer over various types of file systems. | ||
// It provides a unified interface to interact with different file system implementations, | ||
// whether they are local, remote, or virtual. | ||
// | ||
// Implementations of this interface allow consumers to perform common file system operations such as | ||
// obtaining absolute paths, retrieving file information, walking through directories, and reading files. | ||
type FileSystem interface { | ||
// Abs converts a relative file path to an absolute one. The implementation may | ||
// differ depending on the underlying file system. | ||
Abs(location string) (string, error) | ||
|
||
// StatPath returns file information for the specified location. This typically | ||
// includes details like size, modification time, and whether the path is a file | ||
// or directory. | ||
StatPath(location string) (os.FileInfo, error) | ||
|
||
// WalkDir traverses the directory tree rooted at the specified path, calling the | ||
// provided function for each file or directory encountered. This allows for | ||
// efficient processing of large directory structures and can handle errors for | ||
// individual files or directories. | ||
WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error | ||
|
||
// ReadFile reads the contents of the specified file and returns it as a byte | ||
// slice. This method abstracts away the specifics of how the file is accessed, | ||
// making it easier to work with files across different types of file systems. | ||
ReadFile(filePath string) ([]byte, error) | ||
|
||
// WriteFile writes data to the specified file path with the given permissions. | ||
// If the file exists, it will be overwritten. | ||
WriteFile(path string, data []byte, perm fs.FileMode) error | ||
|
||
// Mkdir creates a new directory at the specified path along with any necessary | ||
// parents, and returns nil, If the directory already exists, the implementation | ||
// may either return an error or ignore the request, depending on the file | ||
// system. This method abstracts the underlying mechanism of directory creation | ||
// across different file systems. | ||
Mkdir(dirPath string, mode fs.FileMode) error | ||
|
||
// Remove removes the named file or (empty) directory. | ||
// If there is an error, it will be of type *PathError. | ||
Remove(path string) error | ||
} | ||
|
||
var _ FileSystem = LocalFileSystem{} | ||
|
||
// LocalFileSystem is an implementation of a FileSystem that works on the local file system where this program is running. | ||
type LocalFileSystem struct{} | ||
|
||
func (l LocalFileSystem) Remove(path string) error { | ||
return os.Remove(path) | ||
} | ||
|
||
func (l LocalFileSystem) WriteFile(path string, data []byte, perm fs.FileMode) error { | ||
return os.WriteFile(path, data, perm) | ||
} | ||
|
||
func (l LocalFileSystem) Mkdir(dirPath string, mode fs.FileMode) error { | ||
return os.MkdirAll(dirPath, mode) | ||
} | ||
|
||
func (l LocalFileSystem) ReadFile(filePath string) ([]byte, error) { | ||
return os.ReadFile(filePath) | ||
} | ||
|
||
func (l LocalFileSystem) WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error { | ||
return filepath.WalkDir(dirPath, f) | ||
} | ||
|
||
func (l LocalFileSystem) StatPath(location string) (os.FileInfo, error) { | ||
return os.Stat(location) | ||
} | ||
|
||
func (l LocalFileSystem) Abs(location string) (string, error) { | ||
return filepath.Abs(location) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package specter | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"io/fs" | ||
"os" | ||
"runtime" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
var _ FileSystem = (*mockFileSystem)(nil) | ||
|
||
// Mock implementations to use in tests. | ||
type mockFileInfo struct { | ||
os.FileInfo | ||
name string | ||
size int64 | ||
mode os.FileMode | ||
modTime int64 | ||
isDir bool | ||
} | ||
|
||
func (m mockFileInfo) Type() fs.FileMode { | ||
if m.isDir { | ||
return os.ModeDir | ||
} | ||
|
||
return os.ModeAppend | ||
} | ||
|
||
func (m mockFileInfo) Info() (fs.FileInfo, error) { | ||
return m, nil | ||
} | ||
|
||
func (m mockFileInfo) Name() string { return m.name } | ||
func (m mockFileInfo) Size() int64 { return m.size } | ||
func (m mockFileInfo) Mode() os.FileMode { return m.mode } | ||
func (m mockFileInfo) IsDir() bool { return m.isDir } | ||
func (m mockFileInfo) Sys() interface{} { return nil } | ||
|
||
type mockFileSystem struct { | ||
files map[string][]byte | ||
dirs map[string]bool | ||
|
||
absErr error | ||
statErr error | ||
walkDirErr error | ||
readFileErr error | ||
writeFileErr error | ||
mkdirErr error | ||
rmErr error | ||
} | ||
|
||
func (m *mockFileSystem) WriteFile(filePath string, data []byte, _ fs.FileMode) error { | ||
if m.writeFileErr != nil { | ||
return m.writeFileErr | ||
} | ||
|
||
if m.files == nil { | ||
m.files = map[string][]byte{} | ||
} | ||
|
||
m.files[filePath] = data | ||
|
||
return nil | ||
} | ||
|
||
func (m *mockFileSystem) Mkdir(dirPath string, _ fs.FileMode) error { | ||
if m.mkdirErr != nil { | ||
return m.mkdirErr | ||
} | ||
|
||
m.dirs[dirPath] = true | ||
|
||
return nil | ||
} | ||
|
||
func (m *mockFileSystem) Remove(path string) error { | ||
if m.rmErr != nil { | ||
return m.rmErr | ||
} | ||
|
||
m.dirs[path] = false | ||
delete(m.files, path) | ||
|
||
return nil | ||
} | ||
|
||
func (m *mockFileSystem) Abs(location string) (string, error) { | ||
if m.absErr != nil { | ||
return "", m.absErr | ||
} | ||
|
||
absPaths := make(map[string]bool, len(m.files)+len(m.dirs)) | ||
|
||
for k := range m.files { | ||
absPaths[k] = true | ||
} | ||
for k := range m.dirs { | ||
absPaths[k] = true | ||
} | ||
|
||
if absPath, exists := absPaths[location]; exists && absPath { | ||
return location, nil | ||
} | ||
return "", nil | ||
} | ||
|
||
func (m *mockFileSystem) StatPath(location string) (os.FileInfo, error) { | ||
if m.statErr != nil { | ||
return nil, m.statErr | ||
} | ||
|
||
if isDir, exists := m.dirs[location]; exists { | ||
return mockFileInfo{name: location, isDir: isDir}, nil | ||
} | ||
|
||
if data, exists := m.files[location]; exists { | ||
return mockFileInfo{name: location, size: int64(len(data))}, nil | ||
} | ||
|
||
return nil, os.ErrNotExist | ||
} | ||
|
||
func (m *mockFileSystem) WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error { | ||
if m.walkDirErr != nil { | ||
return m.walkDirErr | ||
} | ||
|
||
for path, isDir := range m.dirs { | ||
if strings.HasPrefix(path, dirPath) { | ||
err := f(path, mockFileInfo{name: path, isDir: isDir}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
for path := range m.files { | ||
if strings.HasPrefix(path, dirPath) { | ||
err := f(path, mockFileInfo{name: path, isDir: false}, nil) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (m *mockFileSystem) ReadFile(filePath string) ([]byte, error) { | ||
if m.readFileErr != nil { | ||
return nil, m.readFileErr | ||
} | ||
|
||
if data, exists := m.files[filePath]; exists { | ||
return data, nil | ||
} | ||
|
||
return nil, os.ErrNotExist | ||
} | ||
|
||
func TestLocalFileSystem_ReadFile(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
given string | ||
then []byte | ||
thenErrContains string | ||
}{ | ||
{ | ||
name: "GIVEN a file that does not exists THEN return error", | ||
given: "/fake/dir/file1.txt", | ||
thenErrContains: "no such file or directory", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
l := LocalFileSystem{} | ||
got, err := l.ReadFile(tt.given) | ||
|
||
if tt.thenErrContains != "" { | ||
require.Error(t, err) | ||
assert.ErrorContains(t, err, tt.thenErrContains) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
assert.Equal(t, tt.then, got) | ||
}) | ||
} | ||
} | ||
|
||
func TestLocalFileSystem_WalkDir(t *testing.T) { | ||
// Make sure the closure gets called. | ||
lfs := LocalFileSystem{} | ||
closureCalled := false | ||
err := lfs.WalkDir("/fake/dir", func(path string, d fs.DirEntry, err error) error { | ||
closureCalled = true | ||
return nil | ||
}) | ||
require.True(t, closureCalled) | ||
require.NoError(t, err) | ||
} | ||
|
||
func TestLocalFileSystem_StatPath(t *testing.T) { | ||
lfs := LocalFileSystem{} | ||
_, filename, _, ok := runtime.Caller(0) | ||
require.True(t, ok) | ||
stat, err := lfs.StatPath(filename) | ||
require.NoError(t, err) | ||
assert.NotNil(t, stat) | ||
} |
Oops, something went wrong.