github.com/CycloneDX/sbom-utility@v0.16.0/cmd/normalize_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 /* 3 * Licensed to the Apache Software Foundation (ASF) under one or more 4 * contributor license agreements. See the NOTICE file distributed with 5 * this work for additional information regarding copyright ownership. 6 * The ASF licenses this file to You under the Apache License, Version 2.0 7 * (the "License"); you may not use this file except in compliance with 8 * the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package cmd 20 21 import ( 22 "bufio" 23 "bytes" 24 "io" 25 "log" 26 "os" 27 "testing" 28 29 "github.com/CycloneDX/sbom-utility/utils" 30 ) 31 32 const ( 33 TEST_CDX_1_5_NORMALIZE_COMPONENTS = "test/normalize/cdx-1-5-components.bom.json" 34 TEST_CDX_1_5_NORMALIZE_SERVICES = "test/normalize/cdx-1-5-services.bom.json" 35 TEST_CDX_1_5_NORMALIZE_LICENSES = "test/normalize/cdx-1-5-licenses.bom.json" 36 TEST_CDX_1_5_NORMALIZE_DEPENDENCIES = "test/normalize/cdx-1-5-dependencies.bom.json" 37 TEST_CDX_1_5_NORMALIZE_EXTERNAL_REFERENCES = "test/normalize/cdx-1-5-external-references.bom.json" 38 TEST_CDX_1_5_NORMALIZE_VULNERABILITIES = "test/normalize/cdx-1-5-vulnerabilities.bom.json" 39 TEST_CDX_1_5_NORMALIZE_VULNERABILITIES_NATS_BOX = "test/normalize/cdx-1-5-vulnerabilities-container-nats-box.bom.json" 40 TEST_CDX_1_4_NORMALIZE_COMPONENTS_XXL = "test/normalize/cdx-1-4-components-xxl.bom.json" 41 TEST_CDX_1_2_NORMALIZE_COMPONENTS_PROTON = "test/normalize/cdx-1-2-components-protonmail.bom.json" 42 ) 43 44 type NormalizeTestInfo struct { 45 CommonTestInfo 46 Keys []string 47 FromPaths []string 48 Normalize bool 49 } 50 51 func (ti *NormalizeTestInfo) String() string { 52 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(ti) 53 return buffer.String() 54 } 55 56 func NewNormalizeTestInfo(inputFile string, resultExpectedError error) *NormalizeTestInfo { 57 var ti = new(NormalizeTestInfo) 58 // Set to test normalization by default 59 ti.Normalize = true 60 var pCommon = &ti.CommonTestInfo 61 pCommon.InitBasic(inputFile, FORMAT_JSON, resultExpectedError) 62 return ti 63 } 64 65 func innerTestNormalize(t *testing.T, testInfo *NormalizeTestInfo) (outputBuffer bytes.Buffer, basicTestInfo string, err error) { 66 getLogger().Tracef("TestInfo: %s", testInfo) 67 68 // Mock stdin if requested 69 if testInfo.MockStdin == true { 70 utils.GlobalFlags.PersistentFlags.InputFile = INPUT_TYPE_STDIN 71 file, err := os.Open(testInfo.InputFile) // For read access. 72 if err != nil { 73 log.Fatal(err) 74 } 75 76 // convert byte slice to io.Reader 77 savedStdIn := os.Stdin 78 // !!!Important restore stdin 79 defer func() { os.Stdin = savedStdIn }() 80 os.Stdin = file 81 } 82 83 // invoke resource list command with a byte buffer 84 outputBuffer, err = innerBufferedTestNormalize(testInfo) 85 // if the command resulted in a failure 86 if err != nil { 87 // if tests asks us to report a FAIL to the test framework 88 cti := &testInfo.CommonTestInfo 89 if cti.Autofail { 90 encodedTestInfo, _ := utils.EncodeAnyToDefaultIndentedJSONStr(testInfo) 91 t.Errorf("%s: failed: %v\n%s", cti.InputFile, err, encodedTestInfo.String()) 92 } 93 return 94 } 95 96 return 97 } 98 99 func innerBufferedTestNormalize(testInfo *NormalizeTestInfo) (outputBuffer bytes.Buffer, err error) { 100 101 // The command looks for the input & output filename in global flags struct 102 utils.GlobalFlags.PersistentFlags.InputFile = testInfo.InputFile 103 utils.GlobalFlags.PersistentFlags.OutputFile = testInfo.OutputFile 104 utils.GlobalFlags.PersistentFlags.OutputFormat = testInfo.OutputFormat 105 utils.GlobalFlags.PersistentFlags.OutputIndent = testInfo.OutputIndent 106 utils.GlobalFlags.PersistentFlags.OutputNormalize = testInfo.Normalize // NOTE: default=true 107 utils.GlobalFlags.TrimFlags.Keys = testInfo.Keys 108 utils.GlobalFlags.TrimFlags.FromPaths = testInfo.FromPaths 109 var outputWriter io.Writer 110 var outputFile *os.File 111 112 // TODO: centralize this logic to a function all Commands can use... 113 // Note: Any "Mocking" of os.Stdin/os.Stdout should be done in functions that call this one 114 if testInfo.OutputFile == "" { 115 // Declare an output outputBuffer/outputWriter to use used during tests 116 bufferedWriter := bufio.NewWriter(&outputBuffer) 117 outputWriter = bufferedWriter 118 // MUST ensure all data is written to buffer before further testing 119 defer bufferedWriter.Flush() 120 } else { 121 outputFile, outputWriter, err = createOutputFile(testInfo.OutputFile) 122 getLogger().Tracef("outputFile: `%v`; writer: `%v`", testInfo.OutputFile, outputWriter) 123 124 // use function closure to assure consistent error output based upon error type 125 defer func() { 126 // always close the output file (even if error, as long as file handle returned) 127 if outputFile != nil { 128 outputFile.Close() 129 getLogger().Infof("Closed output file: `%s`", testInfo.OutputFile) 130 } 131 }() 132 133 if err != nil { 134 return 135 } 136 } 137 138 // NOTE: We use the Trim() command to test the Normalize() functionality for now 139 // TODO: Ideally, we want a top-level command "Normalize()" with other flag options 140 // BUT, also want to allow normalization any time ANY command writes BOM as output 141 // so the Trim() command is a great first impl. towards those goals. 142 err = Trim(outputWriter, utils.GlobalFlags.PersistentFlags, utils.GlobalFlags.TrimFlags) 143 return 144 } 145 146 func TestNormalizeCdx15Components(t *testing.T) { 147 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_COMPONENTS, nil) 148 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_COMPONENTS) 149 // ti.FromPaths = []string{"components"} 150 innerTestNormalize(t, ti) 151 document, err := LoadBOMOutputFile(ti.CommonTestInfo) 152 if err != nil { 153 t.Error(err) 154 } 155 156 // Before looking for license data, fully unmarshal the SBOM into named structures 157 if err = document.UnmarshalCycloneDXBOM(); err != nil { 158 return 159 } 160 } 161 162 func TestNormalizeCdx15Services(t *testing.T) { 163 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_SERVICES, nil) 164 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_SERVICES) 165 innerTestNormalize(t, ti) 166 } 167 168 func TestNormalizeCdx15Dependencies(t *testing.T) { 169 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_DEPENDENCIES, nil) 170 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_DEPENDENCIES) 171 innerTestNormalize(t, ti) 172 } 173 174 func TestNormalizeCdx15ExternalReferences(t *testing.T) { 175 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_EXTERNAL_REFERENCES, nil) 176 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_EXTERNAL_REFERENCES) 177 innerTestNormalize(t, ti) 178 } 179 180 func TestNormalizeCdx15Vulnerabilities(t *testing.T) { 181 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_VULNERABILITIES, nil) 182 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_VULNERABILITIES) 183 innerTestNormalize(t, ti) 184 } 185 186 func TestNormalizeCdx15Licenses(t *testing.T) { 187 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_LICENSES, nil) 188 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_LICENSES) 189 innerTestNormalize(t, ti) 190 } 191 192 // XXL Sort tests 193 func TestNormalizeCdx12ComponentsProtonMail(t *testing.T) { 194 ti := NewNormalizeTestInfo(TEST_CDX_1_2_NORMALIZE_COMPONENTS_PROTON, nil) 195 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_2_NORMALIZE_COMPONENTS_PROTON) 196 innerTestNormalize(t, ti) 197 } 198 199 func TestNormalizeCdx14ComponentsXXL(t *testing.T) { 200 ti := NewNormalizeTestInfo(TEST_CDX_1_4_NORMALIZE_COMPONENTS_XXL, nil) 201 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_4_NORMALIZE_COMPONENTS_XXL) 202 innerTestNormalize(t, ti) 203 } 204 205 func TestNormalizeCdx15VulnerabilitiesNatsBox(t *testing.T) { 206 ti := NewNormalizeTestInfo(TEST_CDX_1_5_NORMALIZE_VULNERABILITIES_NATS_BOX, nil) 207 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_CDX_1_5_NORMALIZE_VULNERABILITIES_NATS_BOX) 208 innerTestNormalize(t, ti) 209 } 210 211 // EXPERIMENTAL: 212 // TODO: see if we can create a function to loop through all nested structures and 213 // report one which ones do NOT have support for the Normalizer interface. 214 // This could be used to verify always have code to normalize any structure as 215 // new ones are added release-to-release 216 // func TestNormalizeReflect(t *testing.T) { 217 // document, err := LoadBOMFile(TEST_CDX_1_5_NORMALIZE_COMPONENTS) 218 // if err != nil { 219 // return 220 // } 221 222 // if err = document.UnmarshalCycloneDXBOM(); err != nil { 223 // return 224 // } 225 226 // // Test reflect.New using an existing instance 227 // //ptrBom := reflect.New(reflect.TypeOf(*document)) 228 // //fmt.Printf("New *schema.BOM: %+v\n", ptrBom) 229 230 // // Test reflect.New using an existing (empty) instance 231 // //bom2Type := reflect.TypeOf(schema.BOM{}) 232 // //ptrBom2 := reflect.New(bom2Type) 233 // //fmt.Printf("New *schema.BOM: %+v\n", ptrBom2) 234 235 // // Assure we can 236 // ptrCdxBom := document.GetCdxBom() 237 // //fmt.Printf("*schema.CDXBom: %+v\n", ptrCdxBom) 238 239 // ListFields(ptrCdxBom) 240 // } 241 242 // func ListFields(itfc interface{}) { 243 // // NOTE: we can immediately use ValueOf() to dereference the interface{} 244 // // NOTE: Elem() will panic if reflect.ValueOf(itfc).Kind() != reflect.Ptr || reflect.Interface 245 // rvoItfc := reflect.ValueOf(itfc) 246 247 // // Deref. if needed to get the ACTUAL type we want to list fields for 248 // if rvoItfc.Kind() == reflect.Pointer || rvoItfc.Kind() == reflect.Interface { 249 // rvoItfc = reflect.ValueOf(itfc).Elem() 250 // } 251 252 // // Immediately grab the Type of the dereferenced interface{} 253 // rvoType := rvoItfc.Type() 254 // fmt.Printf("Interface: Type: `%v`, Kind: `%v`\n", rvoType.String(), rvoType.Kind()) 255 256 // if rvoType.Kind() == reflect.Struct { 257 // // Iterate over all fields of the Struct type (if any) 258 // for i := 0; i < rvoType.NumField(); i++ { 259 // field := rvoType.Field(i) 260 261 // // Indirect returns the fieldValue that v points to. 262 // // - If <input> is a nil pointer, Indirect returns a zero Value. 263 // // - If <input> is not a pointer, Indirect returns <input> (no dereference using Elem() method). 264 // fieldName := field.Name 265 // fieldValue := reflect.Indirect(rvoItfc).FieldByName(fieldName) 266 // fvKind := fieldValue.Kind() 267 // fvValueOf := reflect.ValueOf(fieldValue) 268 // // TODO: explore `field.PkgPath` 269 // fmt.Printf(">> Field(%v): `%s`, Kind: `%s`, Tags: `%s`, Value: `%v`\n", i, fieldName, fvKind.String(), field.Tag, fvValueOf) 270 271 // // TODO: explore use of isItfc := field.CanInterface() 272 // if fvKind == reflect.Ptr || fvKind == reflect.Interface { 273 // if !fieldValue.IsNil() { 274 // // NOTE: temp.Elem() could be reflect.Struct, reflect.Map, reflect.Slice, etc. 275 // ListFields(fieldValue.Interface()) 276 // } 277 // } 278 // } 279 // } else { 280 // fmt.Printf("!!! Unhandled Kind(): `%v`", rvoType.Kind()) 281 // } 282 // }