chore: vendor

This commit is contained in:
2024-08-04 11:06:58 +02:00
parent 2a5985e44e
commit 04aec8232f
3557 changed files with 981078 additions and 1 deletions

129
vendor/gotest.tools/v3/fs/file.go vendored Normal file
View File

@ -0,0 +1,129 @@
/*
Package fs provides tools for creating temporary files, and testing the
contents and structure of a directory.
*/
package fs // import "gotest.tools/v3/fs"
import (
"os"
"path/filepath"
"runtime"
"strings"
"gotest.tools/v3/assert"
"gotest.tools/v3/internal/cleanup"
)
// Path objects return their filesystem path. Path may be implemented by a
// real filesystem object (such as File and Dir) or by a type which updates
// entries in a Manifest.
type Path interface {
Path() string
Remove()
}
var (
_ Path = &Dir{}
_ Path = &File{}
)
// File is a temporary file on the filesystem
type File struct {
path string
}
type helperT interface {
Helper()
}
// NewFile creates a new file in a temporary directory using prefix as part of
// the filename. The PathOps are applied to the before returning the File.
//
// When used with Go 1.14+ the file will be automatically removed when the test
// ends, unless the TEST_NOCLEANUP env var is set to true.
func NewFile(t assert.TestingT, prefix string, ops ...PathOp) *File {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
tempfile, err := os.CreateTemp("", cleanPrefix(prefix)+"-")
assert.NilError(t, err)
file := &File{path: tempfile.Name()}
cleanup.Cleanup(t, file.Remove)
assert.NilError(t, tempfile.Close())
assert.NilError(t, applyPathOps(file, ops))
return file
}
func cleanPrefix(prefix string) string {
// windows requires both / and \ are replaced
if runtime.GOOS == "windows" {
prefix = strings.Replace(prefix, string(os.PathSeparator), "-", -1)
}
return strings.Replace(prefix, "/", "-", -1)
}
// Path returns the full path to the file
func (f *File) Path() string {
return f.path
}
// Remove the file
func (f *File) Remove() {
_ = os.Remove(f.path)
}
// Dir is a temporary directory
type Dir struct {
path string
}
// NewDir returns a new temporary directory using prefix as part of the directory
// name. The PathOps are applied before returning the Dir.
//
// When used with Go 1.14+ the directory will be automatically removed when the test
// ends, unless the TEST_NOCLEANUP env var is set to true.
func NewDir(t assert.TestingT, prefix string, ops ...PathOp) *Dir {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
path, err := os.MkdirTemp("", cleanPrefix(prefix)+"-")
assert.NilError(t, err)
dir := &Dir{path: path}
cleanup.Cleanup(t, dir.Remove)
assert.NilError(t, applyPathOps(dir, ops))
return dir
}
// Path returns the full path to the directory
func (d *Dir) Path() string {
return d.path
}
// Remove the directory
func (d *Dir) Remove() {
_ = os.RemoveAll(d.path)
}
// Join returns a new path with this directory as the base of the path
func (d *Dir) Join(parts ...string) string {
return filepath.Join(append([]string{d.Path()}, parts...)...)
}
// DirFromPath returns a Dir for a path that already exists. No directory is created.
// Unlike NewDir the directory will not be removed automatically when the test exits,
// it is the callers responsibly to remove the directory.
// DirFromPath can be used with Apply to modify an existing directory.
//
// If the path does not already exist, use NewDir instead.
func DirFromPath(t assert.TestingT, path string, ops ...PathOp) *Dir {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
dir := &Dir{path: path}
assert.NilError(t, applyPathOps(dir, ops))
return dir
}

142
vendor/gotest.tools/v3/fs/manifest.go vendored Normal file
View File

@ -0,0 +1,142 @@
package fs
import (
"fmt"
"io"
"os"
"path/filepath"
"gotest.tools/v3/assert"
)
// Manifest stores the expected structure and properties of files and directories
// in a filesystem.
type Manifest struct {
root *directory
}
type resource struct {
mode os.FileMode
uid uint32
gid uint32
}
type file struct {
resource
content io.ReadCloser
ignoreCariageReturn bool
compareContentFunc func(b []byte) CompareResult
}
func (f *file) Type() string {
return "file"
}
type symlink struct {
resource
target string
}
func (f *symlink) Type() string {
return "symlink"
}
type directory struct {
resource
items map[string]dirEntry
filepathGlobs map[string]*filePath
}
func (f *directory) Type() string {
return "directory"
}
type dirEntry interface {
Type() string
}
// ManifestFromDir creates a [Manifest] by reading the directory at path. The
// manifest stores the structure and properties of files in the directory.
// ManifestFromDir can be used with [Equal] to compare two directories.
func ManifestFromDir(t assert.TestingT, path string) Manifest {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
manifest, err := manifestFromDir(path)
assert.NilError(t, err)
return manifest
}
func manifestFromDir(path string) (Manifest, error) {
info, err := os.Stat(path)
switch {
case err != nil:
return Manifest{}, err
case !info.IsDir():
return Manifest{}, fmt.Errorf("path %s must be a directory", path)
}
directory, err := newDirectory(path, info)
return Manifest{root: directory}, err
}
func newDirectory(path string, info os.FileInfo) (*directory, error) {
items := make(map[string]dirEntry)
children, err := os.ReadDir(path)
if err != nil {
return nil, err
}
for _, child := range children {
fullPath := filepath.Join(path, child.Name())
items[child.Name()], err = getTypedResource(fullPath, child)
if err != nil {
return nil, err
}
}
return &directory{
resource: newResourceFromInfo(info),
items: items,
filepathGlobs: make(map[string]*filePath),
}, nil
}
func getTypedResource(path string, entry os.DirEntry) (dirEntry, error) {
info, err := entry.Info()
if err != nil {
return nil, err
}
switch {
case info.IsDir():
return newDirectory(path, info)
case info.Mode()&os.ModeSymlink != 0:
return newSymlink(path, info)
// TODO: devices, pipes?
default:
return newFile(path, info)
}
}
func newSymlink(path string, info os.FileInfo) (*symlink, error) {
target, err := os.Readlink(path)
if err != nil {
return nil, err
}
return &symlink{
resource: newResourceFromInfo(info),
target: target,
}, err
}
func newFile(path string, info os.FileInfo) (*file, error) {
// TODO: defer file opening to reduce number of open FDs?
readCloser, err := os.Open(path)
if err != nil {
return nil, err
}
return &file{
resource: newResourceFromInfo(info),
content: readCloser,
}, err
}

View File

@ -0,0 +1,38 @@
//go:build !windows
// +build !windows
package fs
import (
"os"
"runtime"
"syscall"
)
const defaultRootDirMode = os.ModeDir | 0700
var defaultSymlinkMode = os.ModeSymlink | 0777
func init() {
switch runtime.GOOS {
case "darwin":
defaultSymlinkMode = os.ModeSymlink | 0755
}
}
func newResourceFromInfo(info os.FileInfo) resource {
statT := info.Sys().(*syscall.Stat_t)
return resource{
mode: info.Mode(),
uid: statT.Uid,
gid: statT.Gid,
}
}
func (p *filePath) SetMode(mode os.FileMode) {
p.file.mode = mode
}
func (p *directoryPath) SetMode(mode os.FileMode) {
p.directory.mode = mode | os.ModeDir
}

View File

@ -0,0 +1,22 @@
package fs
import "os"
const (
defaultRootDirMode = os.ModeDir | 0777
defaultSymlinkMode = os.ModeSymlink | 0666
)
func newResourceFromInfo(info os.FileInfo) resource {
return resource{mode: info.Mode()}
}
func (p *filePath) SetMode(mode os.FileMode) {
bits := mode & 0600
p.file.mode = bits + bits/010 + bits/0100
}
// TODO: is mode ignored on windows?
func (p *directoryPath) SetMode(mode os.FileMode) {
p.directory.mode = defaultRootDirMode
}

278
vendor/gotest.tools/v3/fs/ops.go vendored Normal file
View File

@ -0,0 +1,278 @@
package fs
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"gotest.tools/v3/assert"
)
const defaultFileMode = 0644
// PathOp is a function which accepts a [Path] and performs an operation on that
// path. When called with real filesystem objects ([File] or [Dir]) a PathOp modifies
// the filesystem at the path. When used with a [Manifest] object a PathOp updates
// the manifest to expect a value.
type PathOp func(path Path) error
type manifestResource interface {
SetMode(mode os.FileMode)
SetUID(uid uint32)
SetGID(gid uint32)
}
type manifestFile interface {
manifestResource
SetContent(content io.ReadCloser)
}
type manifestDirectory interface {
manifestResource
AddSymlink(path, target string) error
AddFile(path string, ops ...PathOp) error
AddDirectory(path string, ops ...PathOp) error
}
// WithContent writes content to a file at [Path]
func WithContent(content string) PathOp {
return func(path Path) error {
if m, ok := path.(manifestFile); ok {
m.SetContent(io.NopCloser(strings.NewReader(content)))
return nil
}
return os.WriteFile(path.Path(), []byte(content), defaultFileMode)
}
}
// WithBytes write bytes to a file at [Path]
func WithBytes(raw []byte) PathOp {
return func(path Path) error {
if m, ok := path.(manifestFile); ok {
m.SetContent(io.NopCloser(bytes.NewReader(raw)))
return nil
}
return os.WriteFile(path.Path(), raw, defaultFileMode)
}
}
// WithReaderContent copies the reader contents to the file at [Path]
func WithReaderContent(r io.Reader) PathOp {
return func(path Path) error {
if m, ok := path.(manifestFile); ok {
m.SetContent(io.NopCloser(r))
return nil
}
f, err := os.OpenFile(path.Path(), os.O_WRONLY, defaultFileMode)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, r)
return err
}
}
// AsUser changes ownership of the file system object at [Path]
func AsUser(uid, gid int) PathOp {
return func(path Path) error {
if m, ok := path.(manifestResource); ok {
m.SetUID(uint32(uid))
m.SetGID(uint32(gid))
return nil
}
return os.Chown(path.Path(), uid, gid)
}
}
// WithFile creates a file in the directory at path with content
func WithFile(filename, content string, ops ...PathOp) PathOp {
return func(path Path) error {
if m, ok := path.(manifestDirectory); ok {
ops = append([]PathOp{WithContent(content), WithMode(defaultFileMode)}, ops...)
return m.AddFile(filename, ops...)
}
fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil {
return err
}
return applyPathOps(&File{path: fullpath}, ops)
}
}
func createFile(fullpath string, content string) error {
return os.WriteFile(fullpath, []byte(content), defaultFileMode)
}
// WithFiles creates all the files in the directory at path with their content
func WithFiles(files map[string]string) PathOp {
return func(path Path) error {
if m, ok := path.(manifestDirectory); ok {
for filename, content := range files {
// TODO: remove duplication with WithFile
if err := m.AddFile(filename, WithContent(content), WithMode(defaultFileMode)); err != nil {
return err
}
}
return nil
}
for filename, content := range files {
fullpath := filepath.Join(path.Path(), filepath.FromSlash(filename))
if err := createFile(fullpath, content); err != nil {
return err
}
}
return nil
}
}
// FromDir copies the directory tree from the source path into the new [Dir]
func FromDir(source string) PathOp {
return func(path Path) error {
if _, ok := path.(manifestDirectory); ok {
return fmt.Errorf("use manifest.FromDir")
}
return copyDirectory(source, path.Path())
}
}
// WithDir creates a subdirectory in the directory at path. Additional [PathOp]
// can be used to modify the subdirectory
func WithDir(name string, ops ...PathOp) PathOp {
const defaultMode = 0755
return func(path Path) error {
if m, ok := path.(manifestDirectory); ok {
ops = append([]PathOp{WithMode(defaultMode)}, ops...)
return m.AddDirectory(name, ops...)
}
fullpath := filepath.Join(path.Path(), filepath.FromSlash(name))
err := os.MkdirAll(fullpath, defaultMode)
if err != nil {
return err
}
return applyPathOps(&Dir{path: fullpath}, ops)
}
}
// Apply the PathOps to the [File]
func Apply(t assert.TestingT, path Path, ops ...PathOp) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert.NilError(t, applyPathOps(path, ops))
}
func applyPathOps(path Path, ops []PathOp) error {
for _, op := range ops {
if err := op(path); err != nil {
return err
}
}
return nil
}
// WithMode sets the file mode on the directory or file at [Path]
func WithMode(mode os.FileMode) PathOp {
return func(path Path) error {
if m, ok := path.(manifestResource); ok {
m.SetMode(mode)
return nil
}
return os.Chmod(path.Path(), mode)
}
}
func copyDirectory(source, dest string) error {
entries, err := os.ReadDir(source)
if err != nil {
return err
}
for _, entry := range entries {
sourcePath := filepath.Join(source, entry.Name())
destPath := filepath.Join(dest, entry.Name())
err = copyEntry(entry, destPath, sourcePath)
if err != nil {
return err
}
}
return nil
}
func copyEntry(entry os.DirEntry, destPath string, sourcePath string) error {
if entry.IsDir() {
if err := os.Mkdir(destPath, 0755); err != nil {
return err
}
return copyDirectory(sourcePath, destPath)
}
info, err := entry.Info()
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
return copySymLink(sourcePath, destPath)
}
return copyFile(sourcePath, destPath)
}
func copySymLink(source, dest string) error {
link, err := os.Readlink(source)
if err != nil {
return err
}
return os.Symlink(link, dest)
}
func copyFile(source, dest string) error {
content, err := os.ReadFile(source)
if err != nil {
return err
}
return os.WriteFile(dest, content, 0644)
}
// WithSymlink creates a symlink in the directory which links to target.
// Target must be a path relative to the directory.
//
// Note: the argument order is the inverse of [os.Symlink] to be consistent with
// the other functions in this package.
func WithSymlink(path, target string) PathOp {
return func(root Path) error {
if v, ok := root.(manifestDirectory); ok {
return v.AddSymlink(path, target)
}
return os.Symlink(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path))
}
}
// WithHardlink creates a link in the directory which links to target.
// Target must be a path relative to the directory.
//
// Note: the argument order is the inverse of [os.Link] to be consistent with
// the other functions in this package.
func WithHardlink(path, target string) PathOp {
return func(root Path) error {
if _, ok := root.(manifestDirectory); ok {
return fmt.Errorf("WithHardlink not implemented for manifests")
}
return os.Link(filepath.Join(root.Path(), target), filepath.Join(root.Path(), path))
}
}
// WithTimestamps sets the access and modification times of the file system object
// at path.
func WithTimestamps(atime, mtime time.Time) PathOp {
return func(root Path) error {
if _, ok := root.(manifestDirectory); ok {
return fmt.Errorf("WithTimestamp not implemented for manifests")
}
return os.Chtimes(root.Path(), atime, mtime)
}
}

198
vendor/gotest.tools/v3/fs/path.go vendored Normal file
View File

@ -0,0 +1,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
}

276
vendor/gotest.tools/v3/fs/report.go vendored Normal file
View File

@ -0,0 +1,276 @@
package fs
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"gotest.tools/v3/assert/cmp"
"gotest.tools/v3/internal/format"
)
// Equal compares a directory to the expected structured described by a manifest
// and returns success if they match. If they do not match the failure message
// will contain all the differences between the directory structure and the
// expected structure defined by the [Manifest].
//
// Equal is a [cmp.Comparison] which can be used with [gotest.tools/v3/assert.Assert].
func Equal(path string, expected Manifest) cmp.Comparison {
return func() cmp.Result {
actual, err := manifestFromDir(path)
if err != nil {
return cmp.ResultFromError(err)
}
failures := eqDirectory(string(os.PathSeparator), expected.root, actual.root)
if len(failures) == 0 {
return cmp.ResultSuccess
}
msg := fmt.Sprintf("directory %s does not match expected:\n", path)
return cmp.ResultFailure(msg + formatFailures(failures))
}
}
type failure struct {
path string
problems []problem
}
type problem string
func notEqual(property string, x, y interface{}) problem {
return problem(fmt.Sprintf("%s: expected %s got %s", property, x, y))
}
func errProblem(reason string, err error) problem {
return problem(fmt.Sprintf("%s: %s", reason, err))
}
func existenceProblem(filename, reason string, args ...interface{}) problem {
return problem(filename + ": " + fmt.Sprintf(reason, args...))
}
func eqResource(x, y resource) []problem {
var p []problem
if x.uid != y.uid {
p = append(p, notEqual("uid", x.uid, y.uid))
}
if x.gid != y.gid {
p = append(p, notEqual("gid", x.gid, y.gid))
}
if x.mode != anyFileMode && x.mode != y.mode {
p = append(p, notEqual("mode", x.mode, y.mode))
}
return p
}
func removeCarriageReturn(in []byte) []byte {
return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1)
}
func eqFile(x, y *file) []problem {
p := eqResource(x.resource, y.resource)
switch {
case x.content == nil:
p = append(p, existenceProblem("content", "expected content is nil"))
return p
case x.content == anyFileContent:
return p
case y.content == nil:
p = append(p, existenceProblem("content", "actual content is nil"))
return p
}
xContent, xErr := io.ReadAll(x.content)
defer x.content.Close()
yContent, yErr := io.ReadAll(y.content)
defer y.content.Close()
if xErr != nil {
p = append(p, errProblem("failed to read expected content", xErr))
}
if yErr != nil {
p = append(p, errProblem("failed to read actual content", xErr))
}
if xErr != nil || yErr != nil {
return p
}
if x.compareContentFunc != nil {
r := x.compareContentFunc(yContent)
if !r.Success() {
p = append(p, existenceProblem("content", r.FailureMessage()))
}
return p
}
if x.ignoreCariageReturn || y.ignoreCariageReturn {
xContent = removeCarriageReturn(xContent)
yContent = removeCarriageReturn(yContent)
}
if !bytes.Equal(xContent, yContent) {
p = append(p, diffContent(xContent, yContent))
}
return p
}
func diffContent(x, y []byte) problem {
diff := format.UnifiedDiff(format.DiffConfig{
A: string(x),
B: string(y),
From: "expected",
To: "actual",
})
// Remove the trailing newline in the diff. A trailing newline is always
// added to a problem by formatFailures.
diff = strings.TrimSuffix(diff, "\n")
return problem("content:\n" + indent(diff, " "))
}
func indent(s, prefix string) string {
buf := new(bytes.Buffer)
lines := strings.SplitAfter(s, "\n")
for _, line := range lines {
buf.WriteString(prefix + line)
}
return buf.String()
}
func eqSymlink(x, y *symlink) []problem {
p := eqResource(x.resource, y.resource)
xTarget := x.target
yTarget := y.target
if runtime.GOOS == "windows" {
xTarget = strings.ToLower(xTarget)
yTarget = strings.ToLower(yTarget)
}
if xTarget != yTarget {
p = append(p, notEqual("target", x.target, y.target))
}
return p
}
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 {
p = append(p, existenceProblem(name, "expected %s to exist", xEntry.Type()))
continue
}
if xEntry.Type() != yEntry.Type() {
p = append(p, notEqual(name, xEntry.Type(), yEntry.Type()))
continue
}
f = append(f, eqEntry(filepath.Join(path, name), xEntry, yEntry)...)
}
if len(x.filepathGlobs) != 0 {
for _, name := range sortedKeys(y.items) {
m := matchGlob(name, y.items[name], x.filepathGlobs)
matchedFiles[name] = m.match
f = append(f, m.failures...)
}
}
if _, ok := x.items[anyFile]; ok {
return maybeAppendFailure(f, path, p)
}
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 {
keys := make([]string, 0, len(items))
for key := range items {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
// eqEntry assumes x and y to be the same type
func eqEntry(path string, x, y dirEntry) []failure {
resp := func(problems []problem) []failure {
if len(problems) == 0 {
return nil
}
return []failure{{path: path, problems: problems}}
}
switch typed := x.(type) {
case *file:
return resp(eqFile(typed, y.(*file)))
case *symlink:
return resp(eqSymlink(typed, y.(*symlink)))
case *directory:
return eqDirectory(path, typed, y.(*directory))
}
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
})
buf := new(bytes.Buffer)
for _, failure := range failures {
buf.WriteString(failure.path + "\n")
for _, problem := range failure.problems {
buf.WriteString(" " + string(problem) + "\n")
}
}
return buf.String()
}