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  }