github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/cyclonedxxml/encoder_test.go (about) 1 package cyclonedxxml 2 3 import ( 4 "bytes" 5 "flag" 6 "regexp" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 13 "github.com/anchore/syft/syft/format/internal/cyclonedxutil" 14 "github.com/anchore/syft/syft/format/internal/testutil" 15 "github.com/anchore/syft/syft/sbom" 16 ) 17 18 var updateSnapshot = flag.Bool("update-cyclonedx-xml", false, "update the *.golden files for cyclone-dx XML encoders") 19 var updateImage = flag.Bool("update-image", false, "update the golden image used for image encoder testing") 20 21 func getEncoder(t testing.TB) sbom.FormatEncoder { 22 cfg := DefaultEncoderConfig() 23 cfg.Pretty = true 24 25 enc, err := NewFormatEncoderWithConfig(cfg) 26 require.NoError(t, err) 27 return enc 28 } 29 30 func TestPrettyOutput(t *testing.T) { 31 enc, err := NewFormatEncoderWithConfig(EncoderConfig{ 32 Version: cyclonedxutil.DefaultVersion, 33 Pretty: false, 34 }) 35 require.NoError(t, err) 36 37 dir := t.TempDir() 38 s := testutil.DirectoryInput(t, dir) 39 40 var buffer bytes.Buffer 41 err = enc.Encode(&buffer, s) 42 require.NoError(t, err) 43 44 actual := buffer.String() 45 lines := strings.Split(actual, "\n") 46 require.NotEmpty(t, lines) 47 whitespace := regexp.MustCompile(`^\s+`) 48 for _, line := range lines { 49 if len(line) == 0 { 50 continue 51 } 52 53 // require a non-whitespace character (tab, space, etc) as the first character of the line 54 require.False(t, whitespace.Match([]byte(line)), "line should not start with whitespace: %q", line) 55 } 56 } 57 58 func TestCycloneDxDirectoryEncoder(t *testing.T) { 59 dir := t.TempDir() 60 testutil.AssertEncoderAgainstGoldenSnapshot(t, 61 testutil.EncoderSnapshotTestConfig{ 62 Subject: testutil.DirectoryInput(t, dir), 63 Format: getEncoder(t), 64 UpdateSnapshot: *updateSnapshot, 65 PersistRedactionsInSnapshot: true, 66 IsJSON: false, 67 Redactor: redactor(dir), 68 }, 69 ) 70 } 71 72 func TestCycloneDxImageEncoder(t *testing.T) { 73 testImage := "image-simple" 74 testutil.AssertEncoderAgainstGoldenImageSnapshot(t, 75 testutil.ImageSnapshotTestConfig{ 76 Image: testImage, 77 UpdateImageSnapshot: *updateImage, 78 }, 79 testutil.EncoderSnapshotTestConfig{ 80 Subject: testutil.ImageInput(t, testImage), 81 Format: getEncoder(t), 82 UpdateSnapshot: *updateSnapshot, 83 PersistRedactionsInSnapshot: true, 84 IsJSON: false, 85 Redactor: redactor(), 86 }, 87 ) 88 } 89 90 func redactor(values ...string) testutil.Redactor { 91 return testutil.NewRedactions(). 92 WithValuesRedacted(values...). 93 WithPatternRedactors( 94 map[string]string{ 95 // dates 96 `([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))`: `redacted`, 97 98 // image hashes and BOM refs 99 `sha256:[A-Za-z0-9]{64}`: `sha256:redacted`, 100 101 // serial numbers and BOM refs 102 `(serialNumber|bom-ref)="[^"]+"`: `$1="redacted"`, 103 }, 104 ) 105 } 106 107 func TestSupportedVersions(t *testing.T) { 108 encs := defaultFormatEncoders() 109 require.NotEmpty(t, encs) 110 111 versions := SupportedVersions() 112 require.Equal(t, len(versions), len(encs)) 113 114 subject := testutil.DirectoryInput(t, t.TempDir()) 115 dec := NewFormatDecoder() 116 117 for _, enc := range encs { 118 t.Run(enc.Version(), func(t *testing.T) { 119 require.Contains(t, versions, enc.Version()) 120 121 var buf bytes.Buffer 122 require.NoError(t, enc.Encode(&buf, subject)) 123 124 id, version := dec.Identify(bytes.NewReader(buf.Bytes())) 125 require.Equal(t, enc.ID(), id) 126 require.Equal(t, enc.Version(), version) 127 128 var s *sbom.SBOM 129 var err error 130 s, id, version, err = dec.Decode(bytes.NewReader(buf.Bytes())) 131 require.NoError(t, err) 132 require.Equal(t, enc.ID(), id) 133 require.Equal(t, enc.Version(), version) 134 135 require.NotEmpty(t, s.Artifacts.Packages.PackageCount()) 136 137 assert.Equal(t, len(subject.Relationships), len(s.Relationships), "mismatched relationship count") 138 139 if !assert.Equal(t, subject.Artifacts.Packages.PackageCount(), s.Artifacts.Packages.PackageCount(), "mismatched package count") { 140 t.Logf("expected: %d", subject.Artifacts.Packages.PackageCount()) 141 for _, p := range subject.Artifacts.Packages.Sorted() { 142 t.Logf(" - %s", p.String()) 143 } 144 t.Logf("actual: %d", s.Artifacts.Packages.PackageCount()) 145 for _, p := range s.Artifacts.Packages.Sorted() { 146 t.Logf(" - %s", p.String()) 147 } 148 } 149 }) 150 } 151 } 152 153 func defaultFormatEncoders() []sbom.FormatEncoder { 154 var encs []sbom.FormatEncoder 155 for _, version := range SupportedVersions() { 156 enc, err := NewFormatEncoderWithConfig(EncoderConfig{Version: version}) 157 if err != nil { 158 panic(err) 159 } 160 encs = append(encs, enc) 161 } 162 return encs 163 }