github.com/wolfi-dev/wolfictl@v0.16.11/pkg/configs/rwfs/os/tester/tester.go (about) 1 package tester 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "io/fs" 9 "os" 10 "path/filepath" 11 "sort" 12 "strings" 13 14 "github.com/google/go-cmp/cmp" 15 "github.com/samber/lo" 16 "github.com/wolfi-dev/wolfictl/pkg/configs/rwfs" 17 ) 18 19 const expectedSuffix = "_expected" 20 const specialFileContentForSkippingDiff = "# skip" 21 22 var expectedSuffixWithYAML = expectedSuffix + ".yaml" 23 24 var _ rwfs.FS = (*FS)(nil) 25 26 type FS struct { 27 rootDir string 28 fixtures map[string]*testFile 29 } 30 31 func NewFSWithRoot(root string, fixtures ...string) (*FS, error) { 32 realDirFS := os.DirFS(root) 33 testerFS := new(FS) 34 testerFS.rootDir = root 35 36 testerFS.addDir(".") 37 38 for _, f := range fixtures { 39 stat, err := fs.Stat(realDirFS, f) 40 if err != nil { 41 return nil, fmt.Errorf("unable to stat file %q: %w", f, err) 42 } 43 44 if stat.IsDir() { 45 err := fs.WalkDir(realDirFS, f, func(path string, d fs.DirEntry, err error) error { 46 if err != nil { 47 return err 48 } 49 50 if d.Type().IsDir() { 51 testerFS.addDir(path) 52 return nil 53 } 54 55 if d.Type().IsRegular() { 56 if strings.HasSuffix(path, expectedSuffixWithYAML) { 57 // this is a special file for this tester.FS! Skip. 58 return nil 59 } 60 61 err := testerFS.addFixtureFileFromOS(path) 62 if err != nil { 63 return fmt.Errorf("unable to create new tester.FS: %w", err) 64 } 65 } 66 67 return nil 68 }) 69 if err != nil { 70 return nil, fmt.Errorf("unable to walk fixture directory %q: %w", f, err) 71 } 72 73 continue 74 } 75 76 err = testerFS.addFixtureFileFromOS(f) 77 if err != nil { 78 return nil, fmt.Errorf("unable to add fixture file %q to new tester.FS: %w", f, err) 79 } 80 } 81 82 return testerFS, nil 83 } 84 85 func NewFS(fixtures ...string) (*FS, error) { 86 return NewFSWithRoot(".", fixtures...) 87 } 88 89 func expectedName(original string) string { 90 dir, file := filepath.Split(original) 91 parts := strings.SplitN(file, ".", 2) 92 93 expectedFile := strings.Join([]string{parts[0] + expectedSuffix, parts[1]}, ".") 94 return filepath.Join(dir, expectedFile) 95 } 96 97 func (fsys *FS) Create(name string) (rwfs.File, error) { 98 tf := new(testFile) 99 tf.path = name 100 101 err := tf.loadExpected(fsys) 102 if err != nil { 103 return nil, err 104 } 105 106 tf.writtenBack = new(bytes.Buffer) 107 108 fsys.addTestFile(name, tf) 109 110 return tf, nil 111 } 112 113 func (fsys *FS) Open(name string) (fs.File, error) { 114 if f, ok := fsys.fixtures[name]; ok { 115 return f, nil 116 } 117 118 return nil, os.ErrNotExist 119 } 120 121 func (fsys *FS) OpenAsWritable(name string) (rwfs.File, error) { 122 if f, ok := fsys.fixtures[name]; ok { 123 return f, nil 124 } 125 126 return nil, os.ErrNotExist 127 } 128 129 func (fsys *FS) Truncate(string, int64) error { 130 // TODO: decide if there's a reason for anything but a no-op 131 return nil 132 } 133 134 func (fsys *FS) Diff(name string) string { 135 if tf, ok := fsys.fixtures[name]; ok { 136 want := tf.expectedRead 137 got := tf.writtenBack 138 139 if want.String() == specialFileContentForSkippingDiff { 140 return "" 141 } 142 143 diff := cmp.Diff(want.Bytes(), got.Bytes()) 144 145 if diff == "" { 146 return "" 147 } 148 149 return fmt.Sprintf( 150 "unexpected result (-want, +got):\n%s\n", 151 diff, 152 ) 153 } 154 155 return fmt.Sprintf("unable to find test file %q in tester.FS", name) 156 } 157 158 func (fsys *FS) DiffAll() string { 159 fixtureFiles := lo.Keys(fsys.fixtures) 160 sort.Strings(fixtureFiles) 161 162 var result string 163 for _, ff := range fixtureFiles { 164 if fsys.fixtures[ff].isDir { 165 continue 166 } 167 168 if diff := fsys.Diff(ff); diff != "" { 169 result += fmt.Sprintf("\ndiff found for %q:\n", ff) 170 result += diff 171 } 172 } 173 174 return result 175 } 176 177 func (fsys *FS) addTestFile(path string, tf *testFile) { 178 if fsys.fixtures == nil { 179 fsys.fixtures = make(map[string]*testFile) 180 } 181 182 fsys.fixtures[path] = tf 183 } 184 185 func (fsys *FS) addFixtureFileFromOS(path string) error { 186 tf := new(testFile) 187 tf.path = path 188 189 err := tf.loadOriginal(fsys) 190 if err != nil { 191 return err 192 } 193 194 err = tf.loadExpected(fsys) 195 if err != nil { 196 return err 197 } 198 199 tf.writtenBack = new(bytes.Buffer) 200 201 fsys.addTestFile(path, tf) 202 203 return nil 204 } 205 206 func (fsys *FS) readPath(path string) ([]byte, error) { 207 effectivePath := filepath.Join(fsys.rootDir, path) 208 return os.ReadFile(effectivePath) 209 } 210 211 func (fsys *FS) addDir(path string) { 212 // For dirs, we'll punt to the real os FS. 213 214 tf := new(testFile) 215 tf.isDir = true 216 tf.path = path 217 218 fsys.addTestFile(path, tf) 219 } 220 221 type testFile struct { 222 path string 223 isDir bool 224 originalBytes []byte 225 readCompleted bool 226 originalRead, expectedRead, writtenBack *bytes.Buffer 227 } 228 229 func (t *testFile) ReadDir(_ int) ([]fs.DirEntry, error) { 230 if !t.isDir { 231 return nil, fmt.Errorf("not a directory") 232 } 233 234 dirEntries, err := os.ReadDir(t.path) 235 if err != nil { 236 return nil, err 237 } 238 239 filteredDirEntries := lo.Filter(dirEntries, func(e os.DirEntry, _ int) bool { 240 return !strings.HasSuffix(e.Name(), expectedSuffixWithYAML) 241 }) 242 243 return filteredDirEntries, nil 244 } 245 246 func (t *testFile) Stat() (fs.FileInfo, error) { 247 return os.Stat(t.path) 248 } 249 250 func (t *testFile) Read(p []byte) (int, error) { 251 // We need to reset the t.originalRead buffer if it's already been read fully. 252 if t.readCompleted { 253 b := make([]byte, len(t.originalBytes)) 254 copy(b, t.originalBytes) 255 t.originalRead = bytes.NewBuffer(b) 256 257 t.readCompleted = false 258 } 259 260 n, err := t.originalRead.Read(p) 261 if err != nil && errors.Is(err, io.EOF) { 262 t.readCompleted = true 263 } 264 265 return n, err 266 } 267 268 func (t *testFile) Close() error { 269 return nil 270 } 271 272 func (t *testFile) Write(p []byte) (n int, err error) { 273 return t.writtenBack.Write(p) 274 } 275 276 func (t *testFile) loadOriginal(fsys *FS) error { 277 originalBytes, err := fsys.readPath(t.path) 278 if err != nil { 279 return fmt.Errorf("unable to load fixture %q into tester.FS: %w", t.path, err) 280 } 281 282 t.originalBytes = originalBytes 283 284 forBuf := make([]byte, len(originalBytes)) 285 copy(forBuf, originalBytes) 286 t.originalRead = bytes.NewBuffer(forBuf) 287 288 return nil 289 } 290 291 func (t *testFile) loadExpected(fsys *FS) error { 292 expectedFile := expectedName(t.path) 293 expectedBytes, err := fsys.readPath(expectedFile) 294 if err != nil { 295 return fmt.Errorf("unable to load fixture %q into tester.FS: no expected file %q: %w", t.path, expectedFile, err) 296 } 297 298 t.expectedRead = bytes.NewBuffer(expectedBytes) 299 300 return nil 301 }