2018-06-08 12:09:51 -04:00
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
|
2020-02-07 08:39:24 -05:00
|
|
|
"gotest.tools/v3/assert"
|
2018-06-08 12:09:51 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// resourcePath is an adaptor for resources so they can be used as a Path
|
|
|
|
// with PathOps.
|
|
|
|
type resourcePath struct{}
|
|
|
|
|
|
|
|
func (p *resourcePath) Path() string {
|
|
|
|
return "manifest: not a filesystem path"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *resourcePath) Remove() {}
|
|
|
|
|
|
|
|
type filePath struct {
|
|
|
|
resourcePath
|
|
|
|
file *file
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *filePath) SetContent(content io.ReadCloser) {
|
|
|
|
p.file.content = content
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *filePath) SetUID(uid uint32) {
|
|
|
|
p.file.uid = uid
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *filePath) SetGID(gid uint32) {
|
|
|
|
p.file.gid = gid
|
|
|
|
}
|
|
|
|
|
|
|
|
type directoryPath struct {
|
|
|
|
resourcePath
|
|
|
|
directory *directory
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *directoryPath) SetUID(uid uint32) {
|
|
|
|
p.directory.uid = uid
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *directoryPath) SetGID(gid uint32) {
|
|
|
|
p.directory.gid = gid
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *directoryPath) AddSymlink(path, target string) error {
|
|
|
|
p.directory.items[path] = &symlink{
|
|
|
|
resource: newResource(defaultSymlinkMode),
|
|
|
|
target: target,
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *directoryPath) AddFile(path string, ops ...PathOp) error {
|
|
|
|
newFile := &file{resource: newResource(0)}
|
|
|
|
p.directory.items[path] = newFile
|
|
|
|
exp := &filePath{file: newFile}
|
|
|
|
return applyPathOps(exp, ops)
|
|
|
|
}
|
|
|
|
|
2019-04-05 11:02:23 -04:00
|
|
|
func (p *directoryPath) AddGlobFiles(glob string, ops ...PathOp) error {
|
|
|
|
newFile := &file{resource: newResource(0)}
|
|
|
|
newFilePath := &filePath{file: newFile}
|
|
|
|
p.directory.filepathGlobs[glob] = newFilePath
|
|
|
|
return applyPathOps(newFilePath, ops)
|
|
|
|
}
|
|
|
|
|
2018-06-08 12:09:51 -04:00
|
|
|
func (p *directoryPath) AddDirectory(path string, ops ...PathOp) error {
|
|
|
|
newDir := newDirectoryWithDefaults()
|
|
|
|
p.directory.items[path] = newDir
|
|
|
|
exp := &directoryPath{directory: newDir}
|
|
|
|
return applyPathOps(exp, ops)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expected returns a Manifest with a directory structured created by ops. The
|
|
|
|
// PathOp operations are applied to the manifest as expectations of the
|
|
|
|
// filesystem structure and properties.
|
|
|
|
func Expected(t assert.TestingT, ops ...PathOp) Manifest {
|
|
|
|
if ht, ok := t.(helperT); ok {
|
|
|
|
ht.Helper()
|
|
|
|
}
|
|
|
|
|
|
|
|
newDir := newDirectoryWithDefaults()
|
|
|
|
e := &directoryPath{directory: newDir}
|
|
|
|
assert.NilError(t, applyPathOps(e, ops))
|
|
|
|
return Manifest{root: newDir}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newDirectoryWithDefaults() *directory {
|
|
|
|
return &directory{
|
2019-04-05 11:02:23 -04:00
|
|
|
resource: newResource(defaultRootDirMode),
|
|
|
|
items: make(map[string]dirEntry),
|
|
|
|
filepathGlobs: make(map[string]*filePath),
|
2018-06-08 12:09:51 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newResource(mode os.FileMode) resource {
|
|
|
|
return resource{
|
|
|
|
mode: mode,
|
|
|
|
uid: currentUID(),
|
|
|
|
gid: currentGID(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func currentUID() uint32 {
|
|
|
|
return normalizeID(os.Getuid())
|
|
|
|
}
|
|
|
|
|
|
|
|
func currentGID() uint32 {
|
|
|
|
return normalizeID(os.Getgid())
|
|
|
|
}
|
|
|
|
|
|
|
|
func normalizeID(id int) uint32 {
|
|
|
|
// ids will be -1 on windows
|
|
|
|
if id < 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return uint32(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
var anyFileContent = ioutil.NopCloser(bytes.NewReader(nil))
|
|
|
|
|
|
|
|
// MatchAnyFileContent is a PathOp that updates a Manifest so that the file
|
|
|
|
// at path may contain any content.
|
|
|
|
func MatchAnyFileContent(path Path) error {
|
|
|
|
if m, ok := path.(*filePath); ok {
|
|
|
|
m.SetContent(anyFileContent)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-05 11:02:23 -04:00
|
|
|
// MatchContentIgnoreCarriageReturn is a PathOp that ignores cariage return
|
|
|
|
// discrepancies.
|
|
|
|
func MatchContentIgnoreCarriageReturn(path Path) error {
|
|
|
|
if m, ok := path.(*filePath); ok {
|
|
|
|
m.file.ignoreCariageReturn = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-06-08 12:09:51 -04:00
|
|
|
const anyFile = "*"
|
|
|
|
|
|
|
|
// MatchExtraFiles is a PathOp that updates a Manifest to allow a directory
|
|
|
|
// to contain unspecified files.
|
|
|
|
func MatchExtraFiles(path Path) error {
|
|
|
|
if m, ok := path.(*directoryPath); ok {
|
2020-02-07 08:39:24 -05:00
|
|
|
return m.AddFile(anyFile)
|
2018-06-08 12:09:51 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-04-05 11:02:23 -04:00
|
|
|
// CompareResult is the result of comparison.
|
|
|
|
//
|
|
|
|
// See gotest.tools/assert/cmp.StringResult for a convenient implementation of
|
|
|
|
// this interface.
|
|
|
|
type CompareResult interface {
|
|
|
|
Success() bool
|
|
|
|
FailureMessage() string
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchFileContent is a PathOp that updates a Manifest to use the provided
|
|
|
|
// function to determine if a file's content matches the expectation.
|
|
|
|
func MatchFileContent(f func([]byte) CompareResult) PathOp {
|
|
|
|
return func(path Path) error {
|
|
|
|
if m, ok := path.(*filePath); ok {
|
|
|
|
m.file.compareContentFunc = f
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchFilesWithGlob is a PathOp that updates a Manifest to match files using
|
|
|
|
// glob pattern, and check them using the ops.
|
|
|
|
func MatchFilesWithGlob(glob string, ops ...PathOp) PathOp {
|
|
|
|
return func(path Path) error {
|
|
|
|
if m, ok := path.(*directoryPath); ok {
|
2020-02-07 08:39:24 -05:00
|
|
|
return m.AddGlobFiles(glob, ops...)
|
2019-04-05 11:02:23 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-08 12:09:51 -04:00
|
|
|
// anyFileMode is represented by uint32_max
|
|
|
|
const anyFileMode os.FileMode = 4294967295
|
|
|
|
|
|
|
|
// MatchAnyFileMode is a PathOp that updates a Manifest so that the resource at path
|
|
|
|
// will match any file mode.
|
|
|
|
func MatchAnyFileMode(path Path) error {
|
|
|
|
if m, ok := path.(manifestResource); ok {
|
|
|
|
m.SetMode(anyFileMode)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|