github.com/google/yamlfmt@v0.12.2-0.20240514121411-7f77800e2681/internal/tempfile/golden.go (about) 1 // Copyright 2024 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tempfile 16 17 import ( 18 "errors" 19 "fmt" 20 "io/fs" 21 "os" 22 "path/filepath" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/yamlfmt/internal/collections" 26 ) 27 28 type GoldenCtx struct { 29 Dir string 30 Update bool 31 } 32 33 func (g GoldenCtx) goldenPath(path string) string { 34 return filepath.Join(g.Dir, path) 35 } 36 37 func (g GoldenCtx) CompareGoldenFile(path string, gotContent []byte) error { 38 // If we are updating, just rewrite the file. 39 if g.Update { 40 return os.WriteFile(g.goldenPath(path), gotContent, os.ModePerm) 41 } 42 43 // If we are not updating, check that the content is the same. 44 expectedContent, err := os.ReadFile(g.goldenPath(path)) 45 if err != nil { 46 return err 47 } 48 // Edge case for empty stdout. 49 if gotContent == nil { 50 gotContent = []byte{} 51 } 52 diff := cmp.Diff(string(expectedContent), string(gotContent)) 53 // If there is no diff between the content, nothing to do in either mode. 54 if diff == "" { 55 return nil 56 } 57 return &GoldenDiffError{path: path, diff: diff} 58 } 59 60 func (g GoldenCtx) CompareDirectory(resultPath string) error { 61 // If in update mode, clobber the whole directory and recreate it with 62 // the result of the test. 63 if g.Update { 64 return g.updateGoldenDirectory(resultPath) 65 } 66 67 // Compare the two directories by reading all paths. 68 resultPaths, err := readAllPaths(resultPath) 69 if err != nil { 70 return err 71 } 72 goldenPaths, err := readAllPaths(g.Dir) 73 if err != nil { 74 return err 75 } 76 77 // If the directories differ in paths then the test has failed. 78 if !resultPaths.Equals(goldenPaths) { 79 return errors.New("the directories were different") 80 } 81 82 // Compare each file and gather each error. 83 compareErrors := collections.Errors{} 84 for path := range resultPaths { 85 gotContent, err := os.ReadFile(filepath.Join(resultPath, path)) 86 if err != nil { 87 return fmt.Errorf("%s: %w", path, err) 88 } 89 err = g.CompareGoldenFile(path, gotContent) 90 if err != nil { 91 return fmt.Errorf("%s: %w", path, err) 92 } 93 } 94 // If there are no errors this will be nil, otherwise will be a 95 // combination of every error that occurred. 96 return compareErrors.Combine() 97 } 98 99 func (g GoldenCtx) updateGoldenDirectory(resultPath string) error { 100 // Clear the golden directory 101 err := os.RemoveAll(g.Dir) 102 if err != nil { 103 return fmt.Errorf("could not clear golden directory %s: %w", g.Dir, err) 104 } 105 err = os.Mkdir(g.Dir, os.ModePerm) 106 if err != nil { 107 return fmt.Errorf("could not recreate golden directory %s: %w", g.Dir, err) 108 } 109 110 // Recreate the goldens directory 111 paths, err := ReplicateDirectory(resultPath, g.Dir) 112 if err != nil { 113 return err 114 } 115 return paths.CreateAll() 116 } 117 118 func readAllPaths(dirPath string) (collections.Set[string], error) { 119 paths := collections.Set[string]{} 120 allNamesButCurrentDirectory := func(path string, d fs.DirEntry, err error) error { 121 if path == dirPath { 122 return nil 123 } 124 paths.Add(d.Name()) 125 return nil 126 } 127 err := filepath.WalkDir(dirPath, allNamesButCurrentDirectory) 128 return paths, err 129 } 130 131 type GoldenDiffError struct { 132 path string 133 diff string 134 } 135 136 func (e *GoldenDiffError) Error() string { 137 return fmt.Sprintf("golden: %s differed: %s", e.path, e.diff) 138 }