/
path.go
198 lines (165 loc) · 4.6 KB
/
path.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package fs
import (
"bytes"
"io"
"os"
"gotest.tools/v3/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 = io.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 {
return m.AddFile(anyFile)
}
return nil
}
// CompareResult is the result of comparison.
//
// See [gotest.tools/v3/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 {
return 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
}