aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/internal/testsuite/fs.go
blob: 9acd24b651fa1f0be3a8a3542f79f834fccc4a18 (plain)
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
package testsuite

import (
	"errors"
	"fmt"
	"io/fs"
	"path/filepath"
	"strings"
)

var (
	// ErrFSBadLength is returned by [FS.Compare] for a directory with an
	// unexpected amount of dents.
	ErrFSBadLength = errors.New("bad dir length")
	// ErrFSBadData is returned by [FS.Compare] for a file with unexpected
	// contents.
	ErrFSBadData = errors.New("data differs")
	// ErrFSBadMode is returned by [FS.Compare] for an entry with unexpected
	// mode.
	ErrFSBadMode = errors.New("mode differs")
	// ErrFSInvalidEnt is returned by [FS.Compare] if an invalid [FS] is visited.
	ErrFSInvalidEnt = errors.New("invalid entry condition")
)

// FS represents part of a filesystem hierarchy.
type FS struct {
	// Expected mode of corresponding entry.
	Mode fs.FileMode `json:"mode"`
	// Expected directory contents. The directory is not descended if Dir is nil.
	Dir map[string]*FS `json:"dir"`
	// Expected file contents. The file is not read if Data is nil.
	Data *string `json:"data"`
}

// dprintf calls printf if it is non-nil.
func dprintf(printf func(format string, a ...any), format string, a ...any) {
	if printf == nil {
		return
	}
	printf(format, a...)
}

// printDir prints a failed [FS.Compare] directory.
func printDir(
	printf func(format string, a ...any),
	prefix string,
	dir []fs.DirEntry,
) {
	names := make([]string, len(dir))
	for i, ent := range dir {
		name := ent.Name()
		if ent.IsDir() {
			name += "/"
		}
		names[i] = fmt.Sprintf("%q", name)
	}
	dprintf(printf, "[FAIL] d %s: %s", prefix, strings.Join(names, " "))
}

// Compare compares the contents of prefix against the hierarchy described by s.
func (s *FS) Compare(
	printf func(format string, a ...any),
	prefix string,
	e fs.FS,
) error {
	if s.Data != nil {
		if s.Dir != nil {
			panic("invalid state")
		}
		panic("invalid compare call")
	}

	if s.Dir == nil {
		dprintf(printf, "[ OK ] s %s", prefix)
		return nil
	}

	var dir []fs.DirEntry
	if d, err := fs.ReadDir(e, prefix); err != nil {
		return err
	} else if len(d) != len(s.Dir) {
		printDir(printf, prefix, d)
		return ErrFSBadLength
	} else {
		dir = d
	}

	for _, got := range dir {
		name := got.Name()

		if want, ok := s.Dir[name]; !ok {
			printDir(printf, prefix, dir)
			return fs.ErrNotExist
		} else if want.Dir != nil && !got.IsDir() {
			printDir(printf, prefix, dir)
			return ErrFSInvalidEnt
		} else {
			name = filepath.Join(prefix, name)

			if fi, err := got.Info(); err != nil {
				return err
			} else if fi.Mode() != want.Mode {
				dprintf(printf, "[FAIL] m %s: %#o, want %#o",
					name, uint32(fi.Mode()), uint32(want.Mode))
				return ErrFSBadMode
			}

			if want.Data != nil {
				if want.Dir != nil {
					panic("invalid state")
				}
				if v, err := fs.ReadFile(e, name); err != nil {
					return err
				} else if string(v) != *want.Data {
					dprintf(printf,
						"[FAIL] f %s\n\t got: %s\n\twant: %s",
						name, v, *want.Data,
					)
					return ErrFSBadData
				}
				dprintf(printf, "[ OK ] f %s", name)
			} else if err := want.Compare(printf, name, e); err != nil {
				return err
			}
		}
	}
	dprintf(printf, "[ OK ] d %s", prefix)
	return nil
}