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 }