| package fs |
| |
| import ( |
| "bytes" |
| "io" |
| "io/ioutil" |
| "os" |
| |
| "gotest.tools/assert" |
| ) |
| |
| // 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) |
| } |
| |
| 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) |
| } |
| |
| 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{ |
| resource: newResource(defaultRootDirMode), |
| items: make(map[string]dirEntry), |
| filepathGlobs: make(map[string]*filePath), |
| } |
| } |
| |
| 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 |
| } |
| |
| // 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 |
| } |
| |
| 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 { |
| m.AddFile(anyFile) |
| } |
| return nil |
| } |
| |
| // 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 { |
| m.AddGlobFiles(glob, ops...) |
| } |
| return nil |
| } |
| } |
| |
| // 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 |
| } |