github.com/CycloneDX/sbom-utility@v0.16.0/cmd/diff_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 "fmt" 23 "testing" 24 25 "github.com/CycloneDX/sbom-utility/utils" 26 ) 27 28 const ( 29 TEST_DIFF_ARRAY_ORDER_CHANGE_BASE = "test/diff/json-array-order-change-base.json" 30 TEST_DIFF_ARRAY_ORDER_CHANGE_DELTA = "test/diff/json-array-order-change-delta.json" 31 32 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_DELETE_BASE = "test/diff/json-array-order-change-with-delete-base.json" 33 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_DELETE_DELTA = "test/diff/json-array-order-change-with-delete-delta.json" 34 35 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_BASE = "test/diff/json-array-order-change-with-add-base.json" 36 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_DELTA = "test/diff/json-array-order-change-with-add-delta.json" 37 38 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_AND_DELETE_BASE = "test/diff/json-array-order-change-with-add-and-delete-base.json" 39 TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_AND_DELETE_DELTA = "test/diff/json-array-order-change-with-add-and-delete-delta.json" 40 41 TEST_DIFF_ARRAY_ORDER_2_CHANGES_BASE = "test/diff/json-array-order-2-changes-base.json" 42 TEST_DIFF_ARRAY_ORDER_2_CHANGES_DELTA = "test/diff/json-array-order-2-changes-delta.json" 43 ) 44 45 // Test CycloneDX BOM deltas 46 const ( 47 TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_1_DELTA = "test/diff/cdx-1-4-mature-example-1-delta.json" 48 TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_2_DELTA = "test/diff/cdx-1-4-mature-example-2-delta.json" 49 50 TEST_DIFF_CDX_1_5_VULNERABILITY_BASE = "test/diff/vulnerability/cdx-1-5-vulnerabilities-base.bom.json" 51 TEST_DIFF_CDX_1_5_VULNERABILITY_ADD_1 = "test/diff/vulnerability/cdx-1-5-vulnerabilities-delta-add-1.bom.json" 52 TEST_DIFF_CDX_1_5_VULNERABILITY_REMOVE_1 = "test/diff/vulnerability/cdx-1-5-vulnerabilities-delta-remove-1.bom.json" 53 ) 54 55 // Non-standard test files 56 const ( 57 TEST_DIFF_PANIC_BASE = "test/diff/panic/nats1.json" 58 TEST_DIFF_PANIC_DELTA = "test/diff/panic/nats2.json" 59 ) 60 61 type DiffTestInfo struct { 62 CommonTestInfo 63 RevisedFilename string 64 Colorize bool 65 } 66 67 func (ti *DiffTestInfo) String() string { 68 buffer, _ := utils.EncodeAnyToDefaultIndentedJSONStr(ti) 69 return buffer.String() 70 } 71 72 func NewDiffTestInfo(inputFile string, revisedFilename string) *DiffTestInfo { 73 var ti = new(DiffTestInfo) 74 ti.RevisedFilename = revisedFilename 75 var pCommon = &ti.CommonTestInfo 76 // Note: Diff is by default "txt" format 77 pCommon.InitBasic(inputFile, FORMAT_TEXT, nil) 78 return ti 79 } 80 81 // Tests basic validation and expected errors 82 func innerDiffTest(t *testing.T, testInfo *DiffTestInfo) (actualError error) { 83 getLogger().Enter() 84 defer getLogger().Exit() 85 86 // Copy test parameters to persistent and command-specific flags 87 utils.GlobalFlags.PersistentFlags.OutputFile = testInfo.OutputFile 88 utils.GlobalFlags.PersistentFlags.OutputFormat = testInfo.OutputFormat 89 utils.GlobalFlags.PersistentFlags.InputFile = testInfo.InputFile 90 utils.GlobalFlags.DiffFlags.RevisedFile = testInfo.RevisedFilename 91 utils.GlobalFlags.DiffFlags.Colorize = testInfo.Colorize 92 93 getLogger().Tracef("baseFilename: `%s`, revisedFilename=`%s`, actualError=`%T`", 94 utils.GlobalFlags.PersistentFlags.InputFile, 95 utils.GlobalFlags.DiffFlags.RevisedFile, 96 actualError) 97 98 actualError = Diff(utils.GlobalFlags.PersistentFlags, utils.GlobalFlags.DiffFlags) 99 100 // Always compare actual against expected error (even if it is `nil`) 101 if !ErrorTypesMatch(actualError, testInfo.ResultExpectedError) { 102 switch t := actualError.(type) { 103 default: 104 fmt.Printf("unhandled error type: `%v`\n", t) 105 fmt.Printf(">> value: `%v`\n", t) 106 getLogger().Error(actualError) 107 } 108 t.Errorf("expected error type: `%T`, actual type: `%T`", testInfo.ResultExpectedError, actualError) 109 } 110 111 return 112 } 113 114 // TODO: support testing if "deltas" expressed in JSON diff records 115 // match expected output records. 116 // func debugDeltas(deltas []diff.Delta, indent string) (err error) { 117 // for _, delta := range deltas { 118 // //fmt.Printf("delta: %v\n", delta) 119 // //sim := delta.Similarity() 120 // //fmt.Printf("sim: %v\n", sim) 121 // 122 // indent2 := indent + "...." 123 // 124 // switch pointer := delta.(type) { 125 // case *diff.Object: 126 // fmt.Printf("%s[Object](%v): PostPosition(): \"%v\", # Deltas: %v\n", indent, pointer.Similarity(), ColorizeBackgroundCyan(pointer.PostPosition().String()), len(pointer.Deltas)) 127 // debugDeltas(pointer.Deltas, indent2) 128 // //deltaJson[d.Position.String()], err = f.formatObject(d.Deltas) 129 // case *diff.Array: 130 // fmt.Printf("%s[Array](%v): PostPosition(): \"%v\", # Deltas: %v\n", indent, pointer.Similarity(), ColorizeBackgroundCyan((pointer.PostPosition()).String()), len(pointer.Deltas)) 131 // debugDeltas(pointer.Deltas, indent2) 132 // //deltaJson[d.Position.String()], err = f.formatArray(d.Deltas) 133 // case *diff.Added: 134 // sValue := fmt.Sprintf("%v", pointer.Value) 135 // fmt.Printf("%s[Added](%v): Value: \"%v\", PostPosition(): \"%v\"\n", indent, pointer.Similarity(), ColorizeBackgroundGreen(sValue), ColorizeBackgroundCyan((pointer.PostPosition()).String())) 136 // //deltaJson[d.PostPosition().String()] = []interface{}{d.Value} 137 // case *diff.Modified: 138 // fmt.Printf("%s[Modified](%v): PostPosition: \"%v\", OldValue: \"%v\", NewValue: \"%v\"\n", indent, pointer.Similarity(), ColorizeBackgroundCyan((pointer.PostPosition()).String()), ColorizeBackgroundRed((pointer.OldValue).(string)), ColorizeBackgroundGreen((pointer.NewValue).(string))) 139 // //deltaJson[d.PostPosition().String()] = []interface{}{d.OldValue, d.NewValue} 140 // case *diff.TextDiff: 141 // fmt.Printf("%s[TextDiff](%v): PostPosition: \"%v\", OldValue: \"%v\", NewValue: \"%v\"\n", indent, pointer.Similarity(), ColorizeBackgroundCyan((pointer.PostPosition()).String()), ColorizeBackgroundRed((pointer.OldValue).(string)), ColorizeBackgroundGreen((pointer.NewValue).(string))) 142 // //deltaJson[d.PostPosition().String()] = []interface{}{d.DiffString(), 0, DeltaTextDiff} 143 // case *diff.Deleted: 144 // sValue := fmt.Sprintf("%v", pointer.Value) 145 // fmt.Printf("%s[Deleted](%v): Value: \"%v\", PrePosition(): \"%v\"\n", indent, pointer.Similarity(), ColorizeBackgroundRed(sValue), ColorizeBackgroundCyan(pointer.PrePosition().String())) 146 // //deltaJson[d.PrePosition().String()] = []interface{}{d.Value, 0, DeltaDelete} 147 // case *diff.Moved: 148 // sValue := fmt.Sprintf("%v", pointer.Value) 149 // fmt.Printf("%s[Moved](%v): Value: \"%v\", PrePosition(): \"%v\", PostPosition(): \"%v\"\n", indent, pointer.Similarity(), ColorizeBackgroundYellow(sValue), ColorizeBackgroundCyan(pointer.PrePosition().String()), ColorizeBackgroundCyan(pointer.PostPosition().String())) 150 // fmt.Printf("%s[ERROR] 'Move' operation NOT supported for formatting objects\n", indent) 151 // default: 152 // fmt.Printf("%sUnknown Delta type detected: \"%T\"\n", indent, delta) 153 // } 154 // } 155 // 156 // return 157 // } 158 159 // See: https://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html 160 161 // Validate value (range) 162 // func Colorize(color string, text string) (colorizedText string) { 163 // return color + text + Reset 164 // } 165 166 func TestDiffJsonArrayOrderMove2ObjectsFormatJson(t *testing.T) { 167 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_2_CHANGES_BASE, TEST_DIFF_ARRAY_ORDER_2_CHANGES_DELTA) 168 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_2_CHANGES_DELTA) 169 err := innerDiffTest(t, ti) 170 if err != nil { 171 t.Error(err) 172 } 173 } 174 175 func TestDiffJsonArrayOrderMove1ObjectFormatJson(t *testing.T) { 176 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_CHANGE_BASE, TEST_DIFF_ARRAY_ORDER_CHANGE_DELTA) 177 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_CHANGE_DELTA) 178 err := innerDiffTest(t, ti) 179 if err != nil { 180 t.Error(err) 181 } 182 } 183 184 func TestDiffJsonArrayOrderMove1ObjectFormatText(t *testing.T) { 185 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_CHANGE_BASE, TEST_DIFF_ARRAY_ORDER_CHANGE_DELTA) 186 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_CHANGE_DELTA) 187 err := innerDiffTest(t, ti) 188 if err != nil { 189 t.Error(err) 190 } 191 } 192 193 func TestDiffJsonArrayOrderMove1ObjectWithDeleteFormatText(t *testing.T) { 194 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_DELETE_BASE, TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_DELETE_DELTA) 195 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_DELETE_DELTA) 196 err := innerDiffTest(t, ti) 197 if err != nil { 198 t.Error(err) 199 } 200 } 201 202 func TestDiffJsonArrayOrderMove1ObjectWithAddFormatText(t *testing.T) { 203 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_BASE, TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_DELTA) 204 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_DELTA) 205 err := innerDiffTest(t, ti) 206 if err != nil { 207 t.Error(err) 208 } 209 } 210 211 func TestDiffJsonArrayOrderMove1ObjectWithAddAndDeleteFormatText(t *testing.T) { 212 ti := NewDiffTestInfo(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_AND_DELETE_BASE, TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_AND_DELETE_DELTA) 213 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_ARRAY_ORDER_CHANGE_WITH_ADD_AND_DELETE_DELTA) 214 err := innerDiffTest(t, ti) 215 if err != nil { 216 t.Error(err) 217 } 218 } 219 220 // ===================================================== 221 // CycloneDX BOM variant tests 222 // ===================================================== 223 224 func TestDiffCdx14MatureDelta1Text(t *testing.T) { 225 ti := NewDiffTestInfo(TEST_CDX_1_4_MATURE_EXAMPLE_1_BASE, TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_1_DELTA) 226 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_1_DELTA) 227 err := innerDiffTest(t, ti) 228 if err != nil { 229 t.Error(err) 230 } 231 } 232 233 func TestDiffCdx14MatureDelta1Json(t *testing.T) { 234 ti := NewDiffTestInfo(TEST_CDX_1_4_MATURE_EXAMPLE_1_BASE, TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_1_DELTA) 235 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_1_DELTA) 236 ti.OutputFormat = FORMAT_JSON 237 err := innerDiffTest(t, ti) 238 if err != nil { 239 t.Error(err) 240 } 241 } 242 243 func TestDiffCdx14MatureDelta2(t *testing.T) { 244 ti := NewDiffTestInfo(TEST_CDX_1_4_MATURE_EXAMPLE_1_BASE, TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_2_DELTA) 245 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_CDX_1_4_MATURITY_EXAMPLE_2_DELTA) 246 err := innerDiffTest(t, ti) 247 if err != nil { 248 t.Error(err) 249 } 250 } 251 252 func TestDiffJsonVulnerabilitiesAdd1(t *testing.T) { 253 ti := NewDiffTestInfo(TEST_DIFF_CDX_1_5_VULNERABILITY_BASE, TEST_DIFF_CDX_1_5_VULNERABILITY_ADD_1) 254 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_CDX_1_5_VULNERABILITY_ADD_1) 255 err := innerDiffTest(t, ti) 256 if err != nil { 257 t.Error(err) 258 } 259 } 260 261 func TestDiffJsonVulnerabilitiesRemove1(t *testing.T) { 262 ti := NewDiffTestInfo(TEST_DIFF_CDX_1_5_VULNERABILITY_BASE, TEST_DIFF_CDX_1_5_VULNERABILITY_REMOVE_1) 263 ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_CDX_1_5_VULNERABILITY_REMOVE_1) 264 err := innerDiffTest(t, ti) 265 if err != nil { 266 t.Error(err) 267 } 268 } 269 270 // NOTE: In order to debug panic handling... here is a test 271 // Unfortunately, we cannot run it as part of function test as it "times out" 272 // TODO: Create smaller test files that cause panic in Diff command's underlying libs. 273 // func TestDiffJsonPanicNATs(t *testing.T) { 274 // ti := NewDiffTestInfo(TEST_DIFF_PANIC_BASE, TEST_DIFF_PANIC_DELTA) 275 // ti.OutputFile = ti.CreateTemporaryTestOutputFilename(TEST_DIFF_PANIC_DELTA) 276 // err := innerDiffTest(t, ti) 277 // if err != nil { 278 // t.Error(err) 279 // } 280 // }