github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/cmd/syft/internal/test/integration/encode_decode_cycle_test.go (about)

     1  package integration
     2  
     3  import (
     4  	"bytes"
     5  	"regexp"
     6  	"testing"
     7  
     8  	"github.com/google/go-cmp/cmp"
     9  	"github.com/sergi/go-diff/diffmatchpatch"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/anchore/syft/cmd/syft/internal/options"
    14  	"github.com/anchore/syft/syft/format"
    15  	"github.com/anchore/syft/syft/format/cyclonedxjson"
    16  	"github.com/anchore/syft/syft/format/cyclonedxxml"
    17  	"github.com/anchore/syft/syft/format/syftjson"
    18  	"github.com/anchore/syft/syft/source"
    19  )
    20  
    21  // TestEncodeDecodeEncodeCycleComparison is testing for differences in how SBOM documents get encoded on multiple cycles.
    22  // By encoding and decoding the sbom we can compare the differences between the set of resulting objects. However,
    23  // this requires specific comparisons being done, and select redactions/omissions being made. Additionally, there are
    24  // already unit tests on each format encoder-decoder for properly functioning comparisons in depth, so there is no need
    25  // to do an object-to-object comparison. For this reason this test focuses on a bytes-to-bytes comparison after an
    26  // encode-decode-encode loop which will detect lossy behavior in both directions.
    27  func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
    28  	// use second image for relationships
    29  	images := []string{
    30  		"image-pkg-coverage",
    31  		"image-owning-package",
    32  	}
    33  	tests := []struct {
    34  		name     string
    35  		redactor func(in []byte) []byte
    36  		json     bool
    37  	}{
    38  		{
    39  			name: syftjson.ID.String(),
    40  			redactor: func(in []byte) []byte {
    41  				// no redactions necessary
    42  				return in
    43  			},
    44  			json: true,
    45  		},
    46  		{
    47  			name: cyclonedxjson.ID.String(),
    48  			redactor: func(in []byte) []byte {
    49  				// unstable values
    50  				in = regexp.MustCompile(`"(timestamp|serialNumber|bom-ref|ref)":\s*"(\n|[^"])+"`).ReplaceAll(in, []byte(`"$1": "redacted"`))
    51  				in = regexp.MustCompile(`"(dependsOn)":\s*\[(?:\s|[^]])+]`).ReplaceAll(in, []byte(`"$1": []`))
    52  				return in
    53  			},
    54  			json: true,
    55  		},
    56  		{
    57  			name: cyclonedxxml.ID.String(),
    58  			redactor: func(in []byte) []byte {
    59  				// unstable values
    60  				in = regexp.MustCompile(`(serialNumber|bom-ref|ref)="[^"]+"`).ReplaceAll(in, []byte{})
    61  				in = regexp.MustCompile(`<timestamp>[^<]+</timestamp>`).ReplaceAll(in, []byte{})
    62  
    63  				return in
    64  			},
    65  		},
    66  	}
    67  
    68  	opts := options.DefaultOutput()
    69  	require.NoError(t, opts.PostLoad())
    70  	encoderList, err := opts.Encoders()
    71  	require.NoError(t, err)
    72  
    73  	encoders := format.NewEncoderCollection(encoderList...)
    74  	decoders := format.NewDecoderCollection(format.Decoders()...)
    75  
    76  	for _, test := range tests {
    77  		t.Run(test.name, func(t *testing.T) {
    78  			for _, image := range images {
    79  				originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope)
    80  
    81  				f := encoders.GetByString(test.name)
    82  				require.NotNil(t, f)
    83  
    84  				var buff1 bytes.Buffer
    85  				err := f.Encode(&buff1, originalSBOM)
    86  				require.NoError(t, err)
    87  
    88  				newSBOM, formatID, formatVersion, err := decoders.Decode(bytes.NewReader(buff1.Bytes()))
    89  				require.NoError(t, err)
    90  				require.Equal(t, f.ID(), formatID)
    91  				require.Equal(t, f.Version(), formatVersion)
    92  
    93  				var buff2 bytes.Buffer
    94  				err = f.Encode(&buff2, *newSBOM)
    95  				require.NoError(t, err)
    96  
    97  				by1 := buff1.Bytes()
    98  				by2 := buff2.Bytes()
    99  				if test.redactor != nil {
   100  					by1 = test.redactor(by1)
   101  					by2 = test.redactor(by2)
   102  				}
   103  
   104  				if test.json {
   105  					s1 := string(by1)
   106  					s2 := string(by2)
   107  					if diff := cmp.Diff(s1, s2); diff != "" {
   108  						t.Errorf("Encode/Decode mismatch (-want +got) [image %q]:\n%s", image, diff)
   109  					}
   110  				} else if !assert.True(t, bytes.Equal(by1, by2)) {
   111  					dmp := diffmatchpatch.New()
   112  					diffs := dmp.DiffMain(string(by1), string(by2), true)
   113  					t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
   114  				}
   115  			}
   116  		})
   117  	}
   118  }