Merge pull request #135 from glefloch/glob-pathop
Add MatchExtraFilesGlob PathOp
diff --git a/fs/manifest.go b/fs/manifest.go
index 28122f1..e5e693e 100644
--- a/fs/manifest.go
+++ b/fs/manifest.go
@@ -44,7 +44,8 @@
type directory struct {
resource
- items map[string]dirEntry
+ items map[string]dirEntry
+ filepathGlobs map[string]*filePath
}
func (f *directory) Type() string {
@@ -96,8 +97,9 @@
}
return &directory{
- resource: newResourceFromInfo(info),
- items: items,
+ resource: newResourceFromInfo(info),
+ items: items,
+ filepathGlobs: make(map[string]*filePath),
}, nil
}
diff --git a/fs/manifest_test.go b/fs/manifest_test.go
index 9e592f0..31086bb 100644
--- a/fs/manifest_test.go
+++ b/fs/manifest_test.go
@@ -54,6 +54,7 @@
content: readCloser("content k"),
},
},
+ filepathGlobs: map[string]*filePath{},
},
"f": &symlink{
resource: newResource(defaultSymlinkMode),
@@ -64,6 +65,7 @@
content: readCloser("content x"),
},
},
+ filepathGlobs: map[string]*filePath{},
},
}
actual := ManifestFromDir(t, srcDir.Path())
diff --git a/fs/path.go b/fs/path.go
index 0f447c3..4bb3877 100644
--- a/fs/path.go
+++ b/fs/path.go
@@ -64,6 +64,13 @@
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
@@ -87,8 +94,9 @@
func newDirectoryWithDefaults() *directory {
return &directory{
- resource: newResource(defaultRootDirMode),
- items: make(map[string]dirEntry),
+ resource: newResource(defaultRootDirMode),
+ items: make(map[string]dirEntry),
+ filepathGlobs: make(map[string]*filePath),
}
}
@@ -167,6 +175,17 @@
}
}
+// 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
diff --git a/fs/report.go b/fs/report.go
index 851dbd1..adc5a5f 100644
--- a/fs/report.go
+++ b/fs/report.go
@@ -160,11 +160,13 @@
func eqDirectory(path string, x, y *directory) []failure {
p := eqResource(x.resource, y.resource)
var f []failure
+ matchedFiles := make(map[string]bool)
for _, name := range sortedKeys(x.items) {
if name == anyFile {
continue
}
+ matchedFiles[name] = true
xEntry := x.items[name]
yEntry, ok := y.items[name]
if !ok {
@@ -180,19 +182,30 @@
f = append(f, eqEntry(filepath.Join(path, name), xEntry, yEntry)...)
}
- if _, ok := x.items[anyFile]; !ok {
+ if len(x.filepathGlobs) != 0 {
for _, name := range sortedKeys(y.items) {
- if _, ok := x.items[name]; !ok {
- yEntry := y.items[name]
- p = append(p, existenceProblem(name, "unexpected %s", yEntry.Type()))
- }
+ m := matchGlob(name, y.items[name], x.filepathGlobs)
+ matchedFiles[name] = m.match
+ f = append(f, m.failures...)
}
}
- if len(p) > 0 {
- f = append(f, failure{path: path, problems: p})
+ if _, ok := x.items[anyFile]; ok {
+ return maybeAppendFailure(f, path, p)
}
- return f
+ for _, name := range sortedKeys(y.items) {
+ if !matchedFiles[name] {
+ p = append(p, existenceProblem(name, "unexpected %s", y.items[name].Type()))
+ }
+ }
+ return maybeAppendFailure(f, path, p)
+}
+
+func maybeAppendFailure(failures []failure, path string, problems []problem) []failure {
+ if len(problems) > 0 {
+ return append(failures, failure{path: path, problems: problems})
+ }
+ return failures
}
func sortedKeys(items map[string]dirEntry) []string {
@@ -224,6 +237,30 @@
return nil
}
+type globMatch struct {
+ match bool
+ failures []failure
+}
+
+func matchGlob(name string, yEntry dirEntry, globs map[string]*filePath) globMatch {
+ m := globMatch{}
+
+ for glob, expectedFile := range globs {
+ ok, err := filepath.Match(glob, name)
+ if err != nil {
+ p := errProblem("failed to match glob pattern", err)
+ f := failure{path: name, problems: []problem{p}}
+ m.failures = append(m.failures, f)
+ }
+ if ok {
+ m.match = true
+ m.failures = eqEntry(name, expectedFile.file, yEntry)
+ return m
+ }
+ }
+ return m
+}
+
func formatFailures(failures []failure) string {
sort.Slice(failures, func(i, j int) bool {
return failures[i].path < failures[j].path
diff --git a/fs/report_test.go b/fs/report_test.go
index 5c17eca..26cf493 100644
--- a/fs/report_test.go
+++ b/fs/report_test.go
@@ -8,6 +8,7 @@
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
+ "gotest.tools/skip"
)
func TestEqualMissingRoot(t *testing.T) {
@@ -207,3 +208,67 @@
assert.Equal(t, result.(cmpFailure).FailureMessage(), expected)
})
}
+
+func TestMatchExtraFilesGlob(t *testing.T) {
+ dir := NewDir(t, t.Name(),
+ WithFile("t.go", "data"),
+ WithFile("a.go", "data"),
+ WithFile("conf.yml", "content", WithMode(0600)))
+ defer dir.Remove()
+
+ t.Run("matching globs", func(t *testing.T) {
+ manifest := Expected(t,
+ MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent),
+ MatchFilesWithGlob("*.yml", MatchAnyFileMode, MatchAnyFileContent))
+ assert.Assert(t, Equal(dir.Path(), manifest))
+ })
+
+ t.Run("matching globs with wrong mode", func(t *testing.T) {
+ skip.If(t, runtime.GOOS == "windows", "expect mode does not match on windows")
+ manifest := Expected(t,
+ MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent),
+ MatchFilesWithGlob("*.yml", MatchAnyFileContent, WithMode(0700)))
+
+ result := Equal(dir.Path(), manifest)()
+
+ assert.Assert(t, !result.Success())
+ expected := fmtExpected(`directory %s does not match expected:
+conf.yml
+ mode: expected -rwx------ got -rw-------
+`, dir.Path())
+ assert.Equal(t, result.(cmpFailure).FailureMessage(), expected)
+ })
+
+ t.Run("matching partial glob", func(t *testing.T) {
+ manifest := Expected(t, MatchFilesWithGlob("*.go", MatchAnyFileMode, MatchAnyFileContent))
+ result := Equal(dir.Path(), manifest)()
+ assert.Assert(t, !result.Success())
+
+ expected := fmtExpected(`directory %s does not match expected:
+/
+ conf.yml: unexpected file
+`, dir.Path())
+ assert.Equal(t, result.(cmpFailure).FailureMessage(), expected)
+ })
+
+ t.Run("invalid glob", func(t *testing.T) {
+ manifest := Expected(t, MatchFilesWithGlob("[-x]"))
+ result := Equal(dir.Path(), manifest)()
+ assert.Assert(t, !result.Success())
+
+ expected := fmtExpected(`directory %s does not match expected:
+/
+ a.go: unexpected file
+ conf.yml: unexpected file
+ t.go: unexpected file
+a.go
+ failed to match glob pattern: syntax error in pattern
+conf.yml
+ failed to match glob pattern: syntax error in pattern
+t.go
+ failed to match glob pattern: syntax error in pattern
+`, dir.Path())
+ assert.Equal(t, result.(cmpFailure).FailureMessage(), expected)
+ })
+
+}