github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/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/syft/formats"
    14  	"github.com/anchore/syft/syft/formats/cyclonedxjson"
    15  	"github.com/anchore/syft/syft/formats/cyclonedxxml"
    16  	"github.com/anchore/syft/syft/formats/syftjson"
    17  	"github.com/anchore/syft/syft/sbom"
    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  		formatOption sbom.FormatID
    35  		redactor     func(in []byte) []byte
    36  		json         bool
    37  	}{
    38  		{
    39  			formatOption: syftjson.ID,
    40  			redactor: func(in []byte) []byte {
    41  				// no redactions necessary
    42  				return in
    43  			},
    44  			json: true,
    45  		},
    46  		{
    47  			formatOption: cyclonedxjson.ID,
    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  			formatOption: cyclonedxxml.ID,
    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  	for _, test := range tests {
    69  		t.Run(string(test.formatOption), func(t *testing.T) {
    70  			for _, image := range images {
    71  				originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope, nil)
    72  
    73  				format := formats.ByName(string(test.formatOption))
    74  				require.NotNil(t, format)
    75  
    76  				by1, err := formats.Encode(originalSBOM, format)
    77  				require.NoError(t, err)
    78  
    79  				newSBOM, newFormat, err := formats.Decode(bytes.NewReader(by1))
    80  				require.NoError(t, err)
    81  				require.Equal(t, format.ID(), newFormat.ID())
    82  
    83  				by2, err := formats.Encode(*newSBOM, format)
    84  				require.NoError(t, err)
    85  
    86  				if test.redactor != nil {
    87  					by1 = test.redactor(by1)
    88  					by2 = test.redactor(by2)
    89  				}
    90  
    91  				if test.json {
    92  					s1 := string(by1)
    93  					s2 := string(by2)
    94  					if diff := cmp.Diff(s1, s2); diff != "" {
    95  						t.Errorf("Encode/Decode mismatch (-want +got) [image %q]:\n%s", image, diff)
    96  					}
    97  				} else if !assert.True(t, bytes.Equal(by1, by2)) {
    98  					dmp := diffmatchpatch.New()
    99  					diffs := dmp.DiffMain(string(by1), string(by2), true)
   100  					t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
   101  				}
   102  			}
   103  		})
   104  	}
   105  }