2020-03-26 09:23:21 +02:00
|
|
|
// +build !release
|
|
|
|
|
|
|
|
package mock
|
|
|
|
|
|
|
|
import (
|
2020-06-15 09:47:33 +02:00
|
|
|
"fmt"
|
|
|
|
"github.com/bmatcuk/doublestar"
|
2020-03-26 09:23:21 +02:00
|
|
|
"os"
|
2020-06-15 09:47:33 +02:00
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
2020-03-26 09:23:21 +02:00
|
|
|
)
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
var dirContent []byte
|
|
|
|
|
2020-06-25 14:39:15 +02:00
|
|
|
type fileProperties struct {
|
|
|
|
content *[]byte
|
|
|
|
mode *os.FileMode
|
|
|
|
}
|
|
|
|
|
|
|
|
//FilesMock implements the functions from piperutils.Files with an in-memory file system.
|
2020-03-26 09:23:21 +02:00
|
|
|
type FilesMock struct {
|
2020-06-25 14:39:15 +02:00
|
|
|
files map[string]*fileProperties
|
2020-07-14 15:37:08 +02:00
|
|
|
writtenFiles []string
|
|
|
|
removedFiles []string
|
2020-06-15 09:47:33 +02:00
|
|
|
currentDir string
|
2020-06-18 17:30:17 +02:00
|
|
|
Separator string
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
func (f *FilesMock) init() {
|
|
|
|
if f.files == nil {
|
2020-06-25 14:39:15 +02:00
|
|
|
f.files = map[string]*fileProperties{}
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
2020-06-18 17:30:17 +02:00
|
|
|
if f.Separator == "" {
|
|
|
|
f.Separator = string(os.PathSeparator)
|
|
|
|
}
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
2020-03-26 09:23:21 +02:00
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
func (f *FilesMock) toAbsPath(path string) string {
|
2020-06-18 17:30:17 +02:00
|
|
|
if !strings.HasPrefix(path, f.Separator) {
|
|
|
|
path = f.Separator + filepath.Join(f.currentDir, path)
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
2020-06-25 14:39:15 +02:00
|
|
|
// AddFile establishes the existence of a virtual file. The file is
|
|
|
|
// added with mode 644
|
2020-06-15 09:47:33 +02:00
|
|
|
func (f *FilesMock) AddFile(path string, contents []byte) {
|
2020-06-25 14:39:15 +02:00
|
|
|
f.AddFileWithMode(path, contents, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddFileWithMode establishes the existence of a virtual file.
|
|
|
|
func (f *FilesMock) AddFileWithMode(path string, contents []byte, mode os.FileMode) {
|
|
|
|
f.associateContent(path, &contents, &mode)
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
|
2020-06-25 14:39:15 +02:00
|
|
|
// AddDir establishes the existence of a virtual directory. The directory
|
|
|
|
// is add with default mode 755
|
2020-06-15 09:47:33 +02:00
|
|
|
func (f *FilesMock) AddDir(path string) {
|
2020-06-25 14:39:15 +02:00
|
|
|
f.AddDirWithMode(path, 0755)
|
2020-06-18 17:30:17 +02:00
|
|
|
}
|
|
|
|
|
2020-06-25 14:39:15 +02:00
|
|
|
// AddDirWithMode establishes the existence of a virtual directory.
|
|
|
|
func (f *FilesMock) AddDirWithMode(path string, mode os.FileMode) {
|
|
|
|
f.associateContent(path, &dirContent, &mode)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *FilesMock) associateContent(path string, content *[]byte, mode *os.FileMode) {
|
2020-06-15 09:47:33 +02:00
|
|
|
f.init()
|
2020-06-18 17:30:17 +02:00
|
|
|
path = strings.ReplaceAll(path, "/", f.Separator)
|
|
|
|
path = strings.ReplaceAll(path, "\\", f.Separator)
|
2020-06-25 14:39:15 +02:00
|
|
|
path = f.toAbsPath(path)
|
|
|
|
if _, ok := f.files[path]; !ok {
|
|
|
|
f.files[path] = &fileProperties{}
|
|
|
|
}
|
|
|
|
props := f.files[path]
|
|
|
|
props.content = content
|
|
|
|
props.mode = mode
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
2020-03-26 09:23:21 +02:00
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// HasFile returns true if the virtual file system contains an entry for the given path.
|
|
|
|
func (f *FilesMock) HasFile(path string) bool {
|
|
|
|
_, exists := f.files[f.toAbsPath(path)]
|
|
|
|
return exists
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasRemovedFile returns true if the virtual file system at one point contained an entry for the given path,
|
|
|
|
// and it was removed via FileRemove().
|
|
|
|
func (f *FilesMock) HasRemovedFile(path string) bool {
|
2020-07-14 15:37:08 +02:00
|
|
|
return contains(f.removedFiles, f.toAbsPath(path))
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
|
2020-06-24 11:27:28 +02:00
|
|
|
// HasWrittenFile returns true if the virtual file system at one point contained an entry for the given path,
|
|
|
|
// and it was written via FileWrite().
|
|
|
|
func (f *FilesMock) HasWrittenFile(path string) bool {
|
2020-07-14 15:37:08 +02:00
|
|
|
return contains(f.writtenFiles, f.toAbsPath(path))
|
|
|
|
}
|
|
|
|
|
|
|
|
func contains(collection []string, name string) bool {
|
|
|
|
for _, entry := range collection {
|
|
|
|
if entry == name {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2020-06-24 11:27:28 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// FileExists returns true if file content has been associated with the given path, false otherwise.
|
|
|
|
// Only relative paths are supported.
|
|
|
|
func (f *FilesMock) FileExists(path string) (bool, error) {
|
|
|
|
if f.files == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
2020-06-25 14:39:15 +02:00
|
|
|
props, exists := f.files[f.toAbsPath(path)]
|
2020-06-15 09:47:33 +02:00
|
|
|
if !exists {
|
2020-06-18 17:30:17 +02:00
|
|
|
return false, nil
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|
2020-06-25 14:39:15 +02:00
|
|
|
return props.content != &dirContent, nil
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
2020-03-26 09:23:21 +02:00
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// DirExists returns true, if the given path is a previously added directory, or a parent directory for any of the
|
|
|
|
// previously added files.
|
|
|
|
func (f *FilesMock) DirExists(path string) (bool, error) {
|
|
|
|
path = f.toAbsPath(path)
|
2020-06-25 14:39:15 +02:00
|
|
|
for entry, props := range f.files {
|
2020-06-15 09:47:33 +02:00
|
|
|
var dirComponents []string
|
2020-06-25 14:39:15 +02:00
|
|
|
if props.content == &dirContent {
|
2020-06-18 17:30:17 +02:00
|
|
|
dirComponents = strings.Split(entry, f.Separator)
|
2020-06-15 09:47:33 +02:00
|
|
|
} else {
|
2020-06-18 17:30:17 +02:00
|
|
|
dirComponents = strings.Split(filepath.Dir(entry), f.Separator)
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
if len(dirComponents) > 0 {
|
|
|
|
dir := ""
|
|
|
|
for i, component := range dirComponents {
|
|
|
|
if i == 0 {
|
|
|
|
dir = component
|
|
|
|
} else {
|
2020-06-18 17:30:17 +02:00
|
|
|
dir = dir + f.Separator + component
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
if dir == path {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-26 09:23:21 +02:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// Copy checks if content has been associated with the given src path, and if so copies it under the given path dst.
|
|
|
|
func (f *FilesMock) Copy(src, dst string) (int64, error) {
|
|
|
|
f.init()
|
2020-06-25 14:39:15 +02:00
|
|
|
props, exists := f.files[f.toAbsPath(src)]
|
|
|
|
if !exists || props.content == &dirContent {
|
2020-06-15 09:47:33 +02:00
|
|
|
return 0, fmt.Errorf("cannot copy '%s': %w", src, os.ErrNotExist)
|
|
|
|
}
|
2020-06-25 14:39:15 +02:00
|
|
|
f.AddFileWithMode(dst, *props.content, *props.mode)
|
|
|
|
return int64(len(*props.content)), nil
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// FileRead returns the content previously associated with the given path via AddFile(), or an error if no
|
|
|
|
// content has been associated.
|
|
|
|
func (f *FilesMock) FileRead(path string) ([]byte, error) {
|
|
|
|
f.init()
|
2020-06-25 14:39:15 +02:00
|
|
|
props, exists := f.files[f.toAbsPath(path)]
|
2020-06-15 09:47:33 +02:00
|
|
|
if !exists {
|
|
|
|
return nil, fmt.Errorf("could not read '%s'", path)
|
|
|
|
}
|
|
|
|
// check if trying to open a directory for reading
|
2020-06-25 14:39:15 +02:00
|
|
|
if props.content == &dirContent {
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil, fmt.Errorf("could not read '%s': %w", path, os.ErrInvalid)
|
|
|
|
}
|
2020-06-25 14:39:15 +02:00
|
|
|
return *props.content, nil
|
2020-06-15 09:47:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// FileWrite just forwards to AddFile(), i.e. the content is associated with the given path.
|
2020-06-25 14:39:15 +02:00
|
|
|
func (f *FilesMock) FileWrite(path string, content []byte, mode os.FileMode) error {
|
2020-06-24 11:27:28 +02:00
|
|
|
f.init()
|
2020-06-15 09:47:33 +02:00
|
|
|
// NOTE: FilesMock could be extended to have a set of paths for which FileWrite should fail.
|
|
|
|
// This is why AddFile() exists separately, to differentiate the notion of setting up the mocking
|
|
|
|
// versus implementing the methods from Files.
|
2020-07-14 15:37:08 +02:00
|
|
|
f.writtenFiles = append(f.writtenFiles, f.toAbsPath(path))
|
2020-06-25 14:39:15 +02:00
|
|
|
f.AddFileWithMode(path, content, mode)
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileRemove deletes the association of the given path with any content and records the removal of the file.
|
|
|
|
// If the path has not been registered before, it returns an error.
|
|
|
|
func (f *FilesMock) FileRemove(path string) error {
|
|
|
|
if f.files == nil {
|
|
|
|
return fmt.Errorf("the file '%s' does not exist: %w", path, os.ErrNotExist)
|
|
|
|
}
|
|
|
|
absPath := f.toAbsPath(path)
|
2020-07-14 15:37:08 +02:00
|
|
|
_, exists := f.files[absPath]
|
2020-06-15 09:47:33 +02:00
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("the file '%s' does not exist: %w", path, os.ErrNotExist)
|
|
|
|
}
|
|
|
|
delete(f.files, absPath)
|
2020-07-14 15:37:08 +02:00
|
|
|
f.removedFiles = append(f.removedFiles, absPath)
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MkdirAll creates a directory in the in-memory file system, so that this path is established to exist.
|
2020-06-25 14:39:15 +02:00
|
|
|
func (f *FilesMock) MkdirAll(path string, mode os.FileMode) error {
|
2020-06-15 09:47:33 +02:00
|
|
|
// NOTE: FilesMock could be extended to have a set of paths for which MkdirAll should fail.
|
|
|
|
// This is why AddDir() exists separately, to differentiate the notion of setting up the mocking
|
|
|
|
// versus implementing the methods from Files.
|
2020-06-25 14:39:15 +02:00
|
|
|
f.AddDirWithMode(path, mode)
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// Glob returns an array of path strings which match the given glob-pattern. Double star matching is supported.
|
|
|
|
func (f *FilesMock) Glob(pattern string) ([]string, error) {
|
|
|
|
var matches []string
|
|
|
|
if f.files == nil {
|
|
|
|
return matches, nil
|
|
|
|
}
|
|
|
|
for path := range f.files {
|
2020-06-18 17:30:17 +02:00
|
|
|
path = strings.TrimLeft(path, f.Separator)
|
|
|
|
matched, _ := doublestar.PathMatch(pattern, path)
|
2020-06-15 09:47:33 +02:00
|
|
|
if matched {
|
|
|
|
matches = append(matches, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// The order in f.files is not deterministic, this would result in flaky tests.
|
|
|
|
sort.Strings(matches)
|
|
|
|
return matches, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Getwd returns the rooted current virtual working directory
|
|
|
|
func (f *FilesMock) Getwd() (string, error) {
|
2020-06-18 17:30:17 +02:00
|
|
|
f.init()
|
2020-06-15 09:47:33 +02:00
|
|
|
return f.toAbsPath(""), nil
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|
|
|
|
|
2020-06-15 09:47:33 +02:00
|
|
|
// Chdir changes virtually in to the given directory.
|
|
|
|
// The directory needs to exist according to the files and directories via AddFile() and AddDirectory().
|
|
|
|
// The implementation does not support relative path components such as "..".
|
|
|
|
func (f *FilesMock) Chdir(path string) error {
|
2020-06-18 17:30:17 +02:00
|
|
|
if path == "." || path == "."+f.Separator {
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
path = f.toAbsPath(path)
|
|
|
|
|
|
|
|
exists, _ := f.DirExists(path)
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("failed to change current directory into '%s': %w", path, os.ErrNotExist)
|
|
|
|
}
|
|
|
|
|
2020-06-18 17:30:17 +02:00
|
|
|
f.currentDir = strings.TrimLeft(path, f.Separator)
|
2020-06-15 09:47:33 +02:00
|
|
|
return nil
|
2020-03-26 09:23:21 +02:00
|
|
|
}
|