istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/test/util/diff.go (about) 1 // Copyright Istio Authors 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 util 16 17 import ( 18 "errors" 19 "os" 20 "regexp" 21 "strings" 22 23 "github.com/pmezard/go-difflib/difflib" 24 25 "istio.io/istio/pkg/env" 26 "istio.io/istio/pkg/file" 27 "istio.io/istio/pkg/test" 28 ) 29 30 const ( 31 statusReplacement = "sidecar.istio.io/status: '{\"version\":\"\"," 32 ) 33 34 var statusPattern = regexp.MustCompile("sidecar.istio.io/status: '{\"version\":\"([0-9a-f]+)\",") 35 36 // Refresh controls whether to update the golden artifacts instead. 37 // It is set using the environment variable REFRESH_GOLDEN. 38 func Refresh() bool { 39 return env.Register("REFRESH_GOLDEN", false, "").Get() 40 } 41 42 // Compare compares two byte slices. It returns an error with a 43 // contextual diff if they are not equal. 44 func Compare(content, golden []byte) error { 45 data := strings.TrimSpace(string(content)) 46 expected := strings.TrimSpace(string(golden)) 47 48 if data != expected { 49 diff := difflib.UnifiedDiff{ 50 A: difflib.SplitLines(expected), 51 B: difflib.SplitLines(data), 52 Context: 2, 53 } 54 text, err := difflib.GetUnifiedDiffString(diff) 55 if err != nil { 56 return err 57 } 58 return errors.New(text) 59 } 60 61 return nil 62 } 63 64 // CompareContent compares the content value against the golden file and fails the test if they differ 65 func CompareContent(t test.Failer, content []byte, goldenFile string) { 66 t.Helper() 67 golden := ReadGoldenFile(t, content, goldenFile) 68 CompareBytes(t, content, golden, goldenFile) 69 } 70 71 // ReadGoldenFile reads the content of the golden file and fails the test if an error is encountered 72 func ReadGoldenFile(t test.Failer, content []byte, goldenFile string) []byte { 73 t.Helper() 74 RefreshGoldenFile(t, content, goldenFile) 75 76 return ReadFile(t, goldenFile) 77 } 78 79 // StripVersion strips the version fields of a YAML content. 80 func StripVersion(yaml []byte) []byte { 81 return statusPattern.ReplaceAllLiteral(yaml, []byte(statusReplacement)) 82 } 83 84 // RefreshGoldenFile updates the golden file with the given content 85 func RefreshGoldenFile(t test.Failer, content []byte, goldenFile string) { 86 if Refresh() { 87 t.Logf("Refreshing golden file %s", goldenFile) 88 if err := file.AtomicWrite(goldenFile, content, os.FileMode(0o644)); err != nil { 89 t.Fatal(err.Error()) 90 } 91 } 92 } 93 94 // ReadFile reads the content of the given file or fails the test if an error is encountered. 95 func ReadFile(t test.Failer, file string) []byte { 96 t.Helper() 97 golden, err := os.ReadFile(file) 98 if err != nil { 99 t.Fatal(err.Error()) 100 } 101 return golden 102 } 103 104 // CompareBytes compares the content value against the golden bytes and fails the test if they differ 105 func CompareBytes(t test.Failer, content []byte, golden []byte, name string) { 106 t.Helper() 107 if err := Compare(content, golden); err != nil { 108 t.Fatalf("Failed validating golden file %s:\n%v", name, err) 109 } 110 }