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 }