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