github.com/replicatedcom/ship@v0.50.0/integration/lib.go (about) 1 package integration 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strings" 11 12 . "github.com/onsi/gomega" 13 "github.com/pkg/errors" 14 "github.com/pmezard/go-difflib/difflib" 15 ) 16 17 // files and directories with non-deterministic output 18 var skipFiles = []string{ 19 "installer/terraform/.terraform/plugins", 20 "installer/terraform/plan.tfplan", 21 "installer/charts/rendered/secrets.yaml", 22 "installer/base/consul-test.yaml", 23 "installer/base/gossip-secret.yaml", 24 "installer/consul-rendered.yaml", 25 } 26 27 func skipCheck(filePath string, ignoredFiles []string) bool { 28 for _, f := range ignoredFiles { 29 if strings.HasSuffix(filePath, f) { 30 return true 31 } 32 } 33 34 for _, f := range skipFiles { 35 if strings.HasSuffix(filePath, f) { 36 return true 37 } 38 } 39 return false 40 } 41 42 // CompareDir returns false if the two directories have different contents 43 func CompareDir(expected, actual string, replacements map[string]string, ignoredFiles []string, ignoredKeys map[string][]string) (bool, error) { 44 if skipCheck(actual, ignoredFiles) { 45 return true, nil 46 } 47 48 expectedDir, err := ioutil.ReadDir(expected) 49 Expect(err).NotTo(HaveOccurred()) 50 51 actualDir, err := ioutil.ReadDir(actual) 52 Expect(err).NotTo(HaveOccurred()) 53 54 expectedMap := make(map[string]os.FileInfo) 55 expectedFilenamesMap := make(map[string]struct{}) 56 for _, file := range expectedDir { 57 expectedMap[file.Name()] = file 58 expectedFilenamesMap[file.Name()] = struct{}{} 59 } 60 61 actualMap := make(map[string]os.FileInfo) 62 actualFilenamesMap := make(map[string]struct{}) 63 for _, file := range actualDir { 64 actualMap[file.Name()] = file 65 actualFilenamesMap[file.Name()] = struct{}{} 66 } 67 68 Expect(actualFilenamesMap).To(Equal(expectedFilenamesMap), fmt.Sprintf("Contents of directories %s (expected) and %s (actual) did not match", expected, actual)) 69 70 for name, expectedFile := range expectedMap { 71 actualFile, ok := actualMap[name] 72 Expect(ok).To(BeTrue()) 73 Expect(actualFile.IsDir()).To(Equal(expectedFile.IsDir())) 74 75 expectedFilePath := filepath.Join(expected, expectedFile.Name()) 76 actualFilePath := filepath.Join(actual, actualFile.Name()) 77 78 if expectedFile.IsDir() { 79 // compare child items 80 result, err := CompareDir(expectedFilePath, actualFilePath, replacements, ignoredFiles, ignoredKeys) 81 if !result || err != nil { 82 return result, err 83 } 84 } else if skipCheck(expectedFilePath, ignoredFiles) { 85 continue 86 } else { 87 // compare expectedFile contents 88 expectedContentsBytes, err := ioutil.ReadFile(expectedFilePath) 89 Expect(err).NotTo(HaveOccurred()) 90 actualContentsBytes, err := ioutil.ReadFile(actualFilePath) 91 Expect(err).NotTo(HaveOccurred()) 92 93 // another hack for ease of testing -- pretty print json before comparing so diffs 94 // are easier to read 95 if strings.HasSuffix(actualFilePath, ".json") { 96 cwd, err := os.Getwd() 97 Expect(err).NotTo(HaveOccurred()) 98 99 relativeActualFilePath, err := filepath.Rel(cwd, actualFilePath) 100 Expect(err).NotTo(HaveOccurred()) 101 102 fileIgnorePaths := ignoredKeys[relativeActualFilePath] 103 104 expectedContentsBytes, err = prettyAndCleanJSON(expectedContentsBytes, fileIgnorePaths) 105 Expect(err).NotTo(HaveOccurred()) 106 107 actualContentsBytes, err = prettyAndCleanJSON(actualContentsBytes, fileIgnorePaths) 108 Expect(err).NotTo(HaveOccurred()) 109 } 110 111 // kind of a hack -- remove any trailing newlines (because text editors are hard to use) 112 expectedContents := strings.TrimRight(string(expectedContentsBytes), "\n") 113 actualContents := strings.TrimRight(string(actualContentsBytes), "\n") 114 115 // find and replace strings from the expected contents (customerID, installationID, etc) 116 for k, v := range replacements { 117 re := regexp.MustCompile(k) 118 expectedContents = re.ReplaceAllString(expectedContents, v) 119 } 120 121 // find and replace strings from the actual contents (datetime, signature, etc) 122 for k, v := range replacements { 123 re := regexp.MustCompile(k) 124 actualContents = re.ReplaceAllString(actualContents, v) 125 } 126 127 diff := difflib.UnifiedDiff{ 128 A: difflib.SplitLines(expectedContents), 129 B: difflib.SplitLines(actualContents), 130 FromFile: "expected contents", 131 ToFile: "actual contents", 132 Context: 3, 133 } 134 135 diffText, err := difflib.GetUnifiedDiffString(diff) 136 Expect(err).NotTo(HaveOccurred()) 137 Expect(diffText).To(BeEmpty(), fmt.Sprintf("Contents of files %s (expected) and %s (actual) did not match", expectedFilePath, actualFilePath)) 138 } 139 } 140 141 return true, nil 142 } 143 144 func prettyAndCleanJSON(data []byte, keysToIgnore []string) ([]byte, error) { 145 var obj interface{} 146 err := json.Unmarshal(data, &obj) 147 if err != nil { 148 return nil, errors.Wrap(err, "unmarshal") 149 } 150 151 if _, ok := obj.(map[string]interface{}); ok && keysToIgnore != nil { 152 for _, key := range keysToIgnore { 153 obj = replaceInJSON(obj.(map[string]interface{}), key) 154 } 155 } 156 157 data, err = json.MarshalIndent(obj, "", " ") 158 if err != nil { 159 return nil, errors.Wrap(err, "marshal") 160 } 161 return data, nil 162 } 163 164 func replaceInJSON(obj map[string]interface{}, path string) map[string]interface{} { 165 // split path on '.' 166 167 if path == "" { 168 return obj 169 } 170 171 fullpath := strings.Split(path, ".") 172 173 // if the object to delete is at this level, delete it from the map and return 174 if len(fullpath) == 1 { 175 delete(obj, fullpath[0]) 176 return obj 177 } 178 179 // the object to delete is at a deeper level - check if the specified key exists, if it does not then return 180 // else recursively call replaceInJSON 181 182 if _, exists := obj[fullpath[0]]; !exists { 183 // the object to delete does not exist 184 return obj 185 } 186 187 subObj, ok := obj[fullpath[0]].(map[string]interface{}) 188 if !ok { 189 fmt.Printf("looking for %q and this is not a map[string]interface{}: %+v\n", path, obj[fullpath[0]]) 190 delete(obj, fullpath[0]) 191 return obj 192 } 193 194 replacedObj := replaceInJSON(subObj, strings.Join(fullpath[1:], ".")) 195 obj[fullpath[0]] = replacedObj 196 197 if len(replacedObj) == 0 { 198 delete(obj, fullpath[0]) 199 } 200 201 return obj 202 } 203 204 func RecursiveCopy(sourceDir, destDir string) { 205 err := os.MkdirAll(destDir, os.ModePerm) 206 Expect(err).NotTo(HaveOccurred()) 207 srcFiles, err := ioutil.ReadDir(sourceDir) 208 Expect(err).NotTo(HaveOccurred()) 209 for _, file := range srcFiles { 210 if file.IsDir() { 211 RecursiveCopy(filepath.Join(sourceDir, file.Name()), filepath.Join(destDir, file.Name())) 212 } else { 213 // is file 214 contents, err := ioutil.ReadFile(filepath.Join(sourceDir, file.Name())) 215 Expect(err).NotTo(HaveOccurred()) 216 217 err = ioutil.WriteFile(filepath.Join(destDir, file.Name()), contents, file.Mode()) 218 Expect(err).NotTo(HaveOccurred()) 219 } 220 } 221 }