github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/ledgerutil/compare_test.go (about) 1 /* 2 Copyright hechain. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package ledgerutil 8 9 import ( 10 "bytes" 11 "crypto/sha256" 12 "fmt" 13 "hash" 14 "io/ioutil" 15 "os" 16 "path/filepath" 17 "testing" 18 19 "github.com/hechain20/hechain/common/ledger/util" 20 "github.com/hechain20/hechain/core/ledger/kvledger" 21 "github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/privacyenabledstate" 22 "github.com/hechain20/hechain/internal/fileutil" 23 "github.com/stretchr/testify/require" 24 ) 25 26 var testNewHashFunc = func() (hash.Hash, error) { 27 return sha256.New(), nil 28 } 29 30 type testRecord struct { 31 namespace string 32 key string 33 value string 34 blockNum uint64 35 txNum uint64 36 metadata string 37 } 38 39 func TestCompare(t *testing.T) { 40 // Each list of testRecords represents the records of a single snapshot 41 sampleRecords1 := []*testRecord{ 42 { 43 namespace: "ns1", key: "k1", value: "v1", 44 blockNum: 1, txNum: 1, metadata: "md1", 45 }, 46 { 47 namespace: "ns1", key: "k2", value: "v2", 48 blockNum: 1, txNum: 1, metadata: "md2", 49 }, 50 { 51 namespace: "ns2", key: "k1", value: "v3", 52 blockNum: 1, txNum: 2, metadata: "md3", 53 }, 54 { 55 namespace: "ns3", key: "k1", value: "v4", 56 blockNum: 2, txNum: 0, metadata: "md4", 57 }, 58 } 59 60 sampleRecords2 := []*testRecord{ 61 { 62 namespace: "ns1", key: "k1", value: "v1", 63 blockNum: 1, txNum: 1, metadata: "md1", 64 }, 65 { 66 namespace: "ns1", key: "k2", value: "v2", 67 blockNum: 1, txNum: 1, metadata: "md2", 68 }, 69 { 70 namespace: "ns2", key: "k1", value: "v4", 71 blockNum: 1, txNum: 2, metadata: "md3", 72 }, 73 { 74 namespace: "ns3", key: "k1", value: "v4", 75 blockNum: 2, txNum: 0, metadata: "md4", 76 }, 77 } 78 79 sampleRecords3 := []*testRecord{ 80 { 81 namespace: "ns1", key: "k1", value: "v1", 82 blockNum: 1, txNum: 1, metadata: "md1", 83 }, 84 { 85 namespace: "ns2", key: "k1", value: "v3", 86 blockNum: 1, txNum: 2, metadata: "md3", 87 }, 88 { 89 namespace: "ns3", key: "k1", value: "v4", 90 blockNum: 2, txNum: 0, metadata: "md4", 91 }, 92 } 93 94 sampleRecords4 := []*testRecord{ 95 { 96 namespace: "ns1", key: "k1", value: "v1", 97 blockNum: 1, txNum: 1, metadata: "md1", 98 }, 99 { 100 namespace: "ns1", key: "k2", value: "v2", 101 blockNum: 1, txNum: 1, metadata: "md2", 102 }, 103 } 104 105 sampleRecords5 := []*testRecord{ 106 { 107 namespace: "ns1", key: "k1", value: "v1", 108 blockNum: 1, txNum: 1, metadata: "md1", 109 }, 110 { 111 namespace: "ns1", key: "k2", value: "v3", 112 blockNum: 1, txNum: 1, metadata: "md2", 113 }, 114 { 115 namespace: "ns3", key: "k1", value: "v4", 116 blockNum: 2, txNum: 1, metadata: "md4", 117 }, 118 { 119 namespace: "ns3", key: "k2", value: "v5", 120 blockNum: 1, txNum: 3, metadata: "md5", 121 }, 122 } 123 124 samplePvtRecords1 := []*testRecord{ 125 { 126 namespace: "_lifecycle$$h_implicit_org_Org1MSP", key: "sk1", value: "#!", 127 blockNum: 1, txNum: 1, metadata: "md1", 128 }, 129 } 130 131 samplePvtRecords2 := []*testRecord{ 132 { 133 namespace: "_lifecycle$$h_implicit_org_Org1MSP", key: "sk1", value: "$^", 134 blockNum: 1, txNum: 1, metadata: "md1", 135 }, 136 } 137 138 // Signable metadata samples for snapshots 139 sampleSignableMetadata1 := &kvledger.SnapshotSignableMetadata{ 140 ChannelName: "testchannel", 141 LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum, 142 LastBlockHashInHex: "last_block_hash", 143 PreviousBlockHashInHex: "previous_block_hash", 144 FilesAndHashes: map[string]string{ 145 "private_state_hashes.data": "private_state_hash1", 146 "private_state_hashes.metadata": "private_state_hash1", 147 "public_state.data": "public_state_hash1", 148 "public_state.metadata": "public_state_hash1", 149 "txids.data": "txids_hash1", 150 "txids.metadata": "txids_hash1", 151 }, 152 StateDBType: "testdatabase", 153 } 154 155 sampleSignableMetadata2 := &kvledger.SnapshotSignableMetadata{ 156 ChannelName: "testchannel", 157 LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum, 158 LastBlockHashInHex: "last_block_hash", 159 PreviousBlockHashInHex: "previous_block_hash", 160 FilesAndHashes: map[string]string{ 161 "private_state_hashes.data": "private_state_hash1", 162 "private_state_hashes.metadata": "private_state_hash1", 163 "public_state.data": "public_state_hash2", 164 "public_state.metadata": "public_state_hash2", 165 "txids.data": "txids_hash2", 166 "txids.metadata": "txids_hash2", 167 }, 168 StateDBType: "testdatabase", 169 } 170 171 sampleSignableMetadata3 := &kvledger.SnapshotSignableMetadata{ 172 ChannelName: "testchannel", 173 LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum, 174 LastBlockHashInHex: "last_block_hash", 175 PreviousBlockHashInHex: "previous_block_hash", 176 FilesAndHashes: map[string]string{ 177 "private_state_hashes.data": "private_state_hash2", 178 "private_state_hashes.metadata": "private_state_hash2", 179 "public_state.data": "public_state_hash2", 180 "public_state.metadata": "public_state_hash2", 181 "txids.data": "txids_hash2", 182 "txids.metadata": "txids_hash2", 183 }, 184 StateDBType: "testdatabase2", 185 } 186 187 sampleSignableMetadata4 := &kvledger.SnapshotSignableMetadata{ 188 ChannelName: "testchannel", 189 LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum, 190 LastBlockHashInHex: "last_block_hash", 191 PreviousBlockHashInHex: "previous_block_hash", 192 FilesAndHashes: map[string]string{ 193 "private_state_hashes.data": "private_state_hash2", 194 "private_state_hashes.metadata": "private_state_hash2", 195 "public_state.data": "public_state_hash2", 196 "public_state.metadata": "public_state_hash2", 197 "txids.data": "txids_hash2", 198 "txids.metadata": "txids_hash2", 199 }, 200 StateDBType: "testdatabase", 201 } 202 203 sampleSignableMetadata5 := &kvledger.SnapshotSignableMetadata{ 204 ChannelName: "testchannel", 205 LastBlockNumber: sampleRecords1[len(sampleRecords1)-1].blockNum, 206 LastBlockHashInHex: "last_block_hash", 207 PreviousBlockHashInHex: "previous_block_hash", 208 FilesAndHashes: map[string]string{ 209 "private_state_hashes.data": "private_state_hash2", 210 "private_state_hashes.metadata": "private_state_hash2", 211 "public_state.data": "public_state_hash1", 212 "public_state.metadata": "public_state_hash1", 213 "txids.data": "txids_hash2", 214 "txids.metadata": "txids_hash2", 215 }, 216 StateDBType: "testdatabase", 217 } 218 219 // Expected outputs 220 expectedDifferenceResult := `[ 221 { 222 "namespace" : "ns2", 223 "key" : "k1", 224 "snapshotrecord1" : { 225 "value" : "v3", 226 "blockNum" : 1, 227 "txNum" : 2 228 }, 229 "snapshotrecord2" : { 230 "value" : "v4", 231 "blockNum" : 1, 232 "txNum" : 2 233 } 234 } 235 ]` 236 expectedMissingResult1 := `[ 237 { 238 "namespace" : "ns1", 239 "key" : "k2", 240 "snapshotrecord1" : { 241 "value" : "v2", 242 "blockNum" : 1, 243 "txNum" : 1 244 }, 245 "snapshotrecord2" : null 246 } 247 ]` 248 expectedMissingResult2 := `[ 249 { 250 "namespace" : "ns1", 251 "key" : "k2", 252 "snapshotrecord1" : null, 253 "snapshotrecord2" : { 254 "value" : "v2", 255 "blockNum" : 1, 256 "txNum" : 1 257 } 258 } 259 ]` 260 expectedMissingTailResult1 := `[ 261 { 262 "namespace" : "ns2", 263 "key" : "k1", 264 "snapshotrecord1" : { 265 "value" : "v3", 266 "blockNum" : 1, 267 "txNum" : 2 268 }, 269 "snapshotrecord2" : null 270 }, 271 { 272 "namespace" : "ns3", 273 "key" : "k1", 274 "snapshotrecord1" : { 275 "value" : "v4", 276 "blockNum" : 2, 277 "txNum" : 0 278 }, 279 "snapshotrecord2" : null 280 } 281 ]` 282 expectedMissingTailResult2 := `[ 283 { 284 "namespace" : "ns2", 285 "key" : "k1", 286 "snapshotrecord1" : null, 287 "snapshotrecord2" : { 288 "value" : "v3", 289 "blockNum" : 1, 290 "txNum" : 2 291 } 292 }, 293 { 294 "namespace" : "ns3", 295 "key" : "k1", 296 "snapshotrecord1" : null, 297 "snapshotrecord2" : { 298 "value" : "v4", 299 "blockNum" : 2, 300 "txNum" : 0 301 } 302 } 303 ]` 304 expectedDiffDatabaseError := "the supplied snapshots appear to be non-comparable. State db types do not match." + 305 "\nSnapshot1 state db type: testdatabase\nSnapshot2 state db type: testdatabase2" 306 expectedFirstDiffs := `[ 307 { 308 "namespace" : "ns1", 309 "key" : "k2", 310 "snapshotrecord1" : { 311 "value" : "v2", 312 "blockNum" : 1, 313 "txNum" : 1 314 }, 315 "snapshotrecord2" : { 316 "value" : "v3", 317 "blockNum" : 1, 318 "txNum" : 1 319 } 320 }, 321 { 322 "namespace" : "ns2", 323 "key" : "k1", 324 "snapshotrecord1" : { 325 "value" : "v3", 326 "blockNum" : 1, 327 "txNum" : 2 328 }, 329 "snapshotrecord2" : null 330 }, 331 { 332 "namespace" : "ns3", 333 "key" : "k2", 334 "snapshotrecord1" : null, 335 "snapshotrecord2" : { 336 "value" : "v5", 337 "blockNum" : 1, 338 "txNum" : 3 339 } 340 } 341 ]` 342 expectedAllPubDiffs := `[ 343 { 344 "namespace" : "ns1", 345 "key" : "k2", 346 "snapshotrecord1" : { 347 "value" : "v2", 348 "blockNum" : 1, 349 "txNum" : 1 350 }, 351 "snapshotrecord2" : { 352 "value" : "v3", 353 "blockNum" : 1, 354 "txNum" : 1 355 } 356 }, 357 { 358 "namespace" : "ns2", 359 "key" : "k1", 360 "snapshotrecord1" : { 361 "value" : "v3", 362 "blockNum" : 1, 363 "txNum" : 2 364 }, 365 "snapshotrecord2" : null 366 }, 367 { 368 "namespace" : "ns3", 369 "key" : "k1", 370 "snapshotrecord1" : { 371 "value" : "v4", 372 "blockNum" : 2, 373 "txNum" : 0 374 }, 375 "snapshotrecord2" : { 376 "value" : "v4", 377 "blockNum" : 2, 378 "txNum" : 1 379 } 380 }, 381 { 382 "namespace" : "ns3", 383 "key" : "k2", 384 "snapshotrecord1" : null, 385 "snapshotrecord2" : { 386 "value" : "v5", 387 "blockNum" : 1, 388 "txNum" : 3 389 } 390 } 391 ]` 392 expectedPvtDiffResult := `[ 393 { 394 "namespace" : "_lifecycle$$h_implicit_org_Org1MSP", 395 "key" : "sk1", 396 "snapshotrecord1" : { 397 "value" : "#!", 398 "blockNum" : 1, 399 "txNum" : 1 400 }, 401 "snapshotrecord2" : { 402 "value" : "$^", 403 "blockNum" : 1, 404 "txNum" : 1 405 } 406 } 407 ]` 408 expectedEmpty := "null" 409 410 testCases := map[string]struct { 411 inputTestRecords1 []*testRecord 412 inputTestPvtRecords1 []*testRecord 413 inputSignableMetadata1 *kvledger.SnapshotSignableMetadata 414 inputTestRecords2 []*testRecord 415 inputTestPvtRecords2 []*testRecord 416 inputSignableMetadata2 *kvledger.SnapshotSignableMetadata 417 expectedOutputType string 418 expectedPubOutput string 419 expectedPvtOutput string 420 expectedFirOutput string 421 expectedError string 422 firstN int 423 expectedDiffCount int 424 }{ 425 // Snapshots have a single difference in record 426 "single-difference": { 427 inputTestRecords1: sampleRecords1, 428 inputSignableMetadata1: sampleSignableMetadata1, 429 inputTestRecords2: sampleRecords2, 430 inputSignableMetadata2: sampleSignableMetadata2, 431 expectedOutputType: "json", 432 expectedPubOutput: expectedDifferenceResult, 433 expectedPvtOutput: expectedEmpty, 434 expectedFirOutput: expectedEmpty, 435 expectedDiffCount: 1, 436 }, 437 // Second snapshot is missing a record 438 "second-missing": { 439 inputTestRecords1: sampleRecords1, 440 inputSignableMetadata1: sampleSignableMetadata1, 441 inputTestRecords2: sampleRecords3, 442 inputSignableMetadata2: sampleSignableMetadata2, 443 expectedOutputType: "json", 444 expectedPubOutput: expectedMissingResult1, 445 expectedPvtOutput: expectedEmpty, 446 expectedFirOutput: expectedEmpty, 447 expectedDiffCount: 1, 448 }, 449 // First snapshot is missing a record 450 "first-missing": { 451 inputTestRecords1: sampleRecords3, 452 inputSignableMetadata1: sampleSignableMetadata2, 453 inputTestRecords2: sampleRecords1, 454 inputSignableMetadata2: sampleSignableMetadata1, 455 expectedOutputType: "json", 456 expectedPubOutput: expectedMissingResult2, 457 expectedPvtOutput: expectedEmpty, 458 expectedFirOutput: expectedEmpty, 459 expectedDiffCount: 1, 460 }, 461 // Second snapshot is missing tailing records 462 "second-missing-tail": { 463 inputTestRecords1: sampleRecords1, 464 inputSignableMetadata1: sampleSignableMetadata1, 465 inputTestRecords2: sampleRecords4, 466 inputSignableMetadata2: sampleSignableMetadata2, 467 expectedOutputType: "json", 468 expectedPubOutput: expectedMissingTailResult1, 469 expectedPvtOutput: expectedEmpty, 470 expectedFirOutput: expectedEmpty, 471 expectedDiffCount: 2, 472 }, 473 // First snapshot is missing tailing records 474 "first-missing-tail": { 475 inputTestRecords1: sampleRecords4, 476 inputSignableMetadata1: sampleSignableMetadata2, 477 inputTestRecords2: sampleRecords1, 478 inputSignableMetadata2: sampleSignableMetadata1, 479 expectedOutputType: "json", 480 expectedPubOutput: expectedMissingTailResult2, 481 expectedPvtOutput: expectedEmpty, 482 expectedFirOutput: expectedEmpty, 483 expectedDiffCount: 2, 484 }, 485 // Snapshots contain the same public state hashes 486 "same-hash": { 487 inputTestRecords1: sampleRecords1, 488 inputSignableMetadata1: sampleSignableMetadata1, 489 inputTestRecords2: sampleRecords1, 490 inputSignableMetadata2: sampleSignableMetadata1, 491 expectedOutputType: "same-hash", 492 expectedDiffCount: -1, 493 }, 494 // Snapshots contain different metadata (different databases in this case) that makes them non-comparable 495 "different-database": { 496 inputTestRecords1: sampleRecords1, 497 inputSignableMetadata1: sampleSignableMetadata1, 498 inputTestRecords2: sampleRecords2, 499 inputSignableMetadata2: sampleSignableMetadata3, 500 expectedOutputType: "error", 501 expectedError: expectedDiffDatabaseError, 502 expectedDiffCount: 0, 503 }, 504 // Output directory file already exists 505 "output-dir-exists": { 506 inputTestRecords1: sampleRecords1, 507 inputSignableMetadata1: sampleSignableMetadata1, 508 inputTestRecords2: sampleRecords2, 509 inputSignableMetadata2: sampleSignableMetadata2, 510 expectedOutputType: "exists-error", 511 expectedDiffCount: 0, 512 }, 513 // User requested first 3 differences 514 "first-3-diffs": { 515 inputTestRecords1: sampleRecords1, 516 inputSignableMetadata1: sampleSignableMetadata1, 517 inputTestRecords2: sampleRecords5, 518 inputSignableMetadata2: sampleSignableMetadata2, 519 expectedOutputType: "json", 520 expectedPubOutput: expectedAllPubDiffs, 521 expectedPvtOutput: expectedEmpty, 522 expectedFirOutput: expectedFirstDiffs, 523 firstN: 3, 524 expectedDiffCount: 4, 525 }, 526 // Snapshots have a single difference public difference and a single private difference 527 "pub-and-pvt-difference": { 528 inputTestRecords1: sampleRecords1, 529 inputTestPvtRecords1: samplePvtRecords1, 530 inputSignableMetadata1: sampleSignableMetadata1, 531 inputTestRecords2: sampleRecords2, 532 inputTestPvtRecords2: samplePvtRecords2, 533 inputSignableMetadata2: sampleSignableMetadata4, 534 expectedOutputType: "json", 535 expectedPubOutput: expectedDifferenceResult, 536 expectedPvtOutput: expectedPvtDiffResult, 537 expectedFirOutput: expectedEmpty, 538 expectedDiffCount: 2, 539 }, 540 // Snapshots have a single private difference only 541 "pvt-difference-only": { 542 inputTestRecords1: sampleRecords1, 543 inputTestPvtRecords1: samplePvtRecords1, 544 inputSignableMetadata1: sampleSignableMetadata1, 545 inputTestRecords2: sampleRecords1, 546 inputTestPvtRecords2: samplePvtRecords2, 547 inputSignableMetadata2: sampleSignableMetadata5, 548 expectedOutputType: "json", 549 expectedPubOutput: expectedEmpty, 550 expectedPvtOutput: expectedPvtDiffResult, 551 expectedFirOutput: expectedEmpty, 552 expectedDiffCount: 1, 553 }, 554 } 555 556 // Run test cases individually 557 for testName, testCase := range testCases { 558 t.Run(testName, func(t *testing.T) { 559 // Create temporary directories for the sample snapshots and comparison results 560 snapshotDir1, err := ioutil.TempDir("", "sample-snapshot-dir1") 561 require.NoError(t, err) 562 defer os.RemoveAll(snapshotDir1) 563 snapshotDir2, err := ioutil.TempDir("", "sample-snapshot-dir2") 564 require.NoError(t, err) 565 defer os.RemoveAll(snapshotDir2) 566 resultsDir, err := ioutil.TempDir("", "results") 567 require.NoError(t, err) 568 defer os.RemoveAll(resultsDir) 569 570 // Populate temporary directories with sample snapshot data 571 err = createSnapshot(snapshotDir1, testCase.inputTestRecords1, testCase.inputTestPvtRecords1, testCase.inputSignableMetadata1) 572 require.NoError(t, err) 573 err = createSnapshot(snapshotDir2, testCase.inputTestRecords2, testCase.inputTestPvtRecords2, testCase.inputSignableMetadata2) 574 require.NoError(t, err) 575 576 // Compare snapshots and check the output 577 count, pubOut, pvtOut, firOut, err := compareSnapshots(snapshotDir1, snapshotDir2, resultsDir, testCase.firstN) 578 switch testCase.expectedOutputType { 579 case "error": 580 require.Equal(t, testCase.expectedDiffCount, count) 581 require.ErrorContains(t, err, testCase.expectedError) 582 case "exists-error": 583 require.NoError(t, err) 584 count, _, _, _, err = compareSnapshots(snapshotDir1, snapshotDir2, resultsDir, testCase.firstN) 585 require.Equal(t, testCase.expectedDiffCount, count) 586 require.ErrorContains(t, err, "testchannel_2_comparison already exists in "+resultsDir+". Choose a different location or remove the existing results. Aborting compare") 587 case "json": 588 require.Equal(t, testCase.expectedDiffCount, count) 589 require.NoError(t, err) 590 require.JSONEq(t, testCase.expectedPubOutput, pubOut) 591 require.JSONEq(t, testCase.expectedPvtOutput, pvtOut) 592 require.JSONEq(t, testCase.expectedFirOutput, firOut) 593 case "same-hash": 594 require.Equal(t, testCase.expectedDiffCount, count) 595 require.NoError(t, err) 596 default: 597 panic("unexpected code path: bug") 598 } 599 }) 600 } 601 } 602 603 // createSnapshot generates a sample snapshot based on the passed in records and metadata 604 func createSnapshot(dir string, pubStateRecords []*testRecord, pvtStateRecords []*testRecord, 605 signableMetadata *kvledger.SnapshotSignableMetadata) error { 606 // Generate public state of sample snapshot 607 pubStateWriter, err := privacyenabledstate.NewSnapshotWriter( 608 dir, 609 privacyenabledstate.PubStateDataFileName, 610 privacyenabledstate.PubStateMetadataFileName, 611 testNewHashFunc, 612 ) 613 if err != nil { 614 return err 615 } 616 defer pubStateWriter.Close() 617 618 // Add sample records to sample snapshot 619 for _, sample := range pubStateRecords { 620 err = pubStateWriter.AddData(sample.namespace, &privacyenabledstate.SnapshotRecord{ 621 Key: []byte(sample.key), 622 Value: []byte(sample.value), 623 Metadata: []byte(sample.metadata), 624 Version: toBytes(sample.blockNum, sample.txNum), 625 }) 626 if err != nil { 627 return err 628 } 629 } 630 631 _, _, err = pubStateWriter.Done() 632 if err != nil { 633 return err 634 } 635 636 // Generate private state of sample snapshot 637 pvtStateWriter, err := privacyenabledstate.NewSnapshotWriter( 638 dir, 639 privacyenabledstate.PvtStateHashesFileName, 640 privacyenabledstate.PvtStateHashesMetadataFileName, 641 testNewHashFunc, 642 ) 643 if err != nil { 644 return err 645 } 646 defer pvtStateWriter.Close() 647 648 // Add sample records to sample snapshot 649 for _, sample := range pvtStateRecords { 650 err = pvtStateWriter.AddData(sample.namespace, &privacyenabledstate.SnapshotRecord{ 651 Key: []byte(sample.key), 652 Value: []byte(sample.value), 653 Metadata: []byte(sample.metadata), 654 Version: toBytes(sample.blockNum, sample.txNum), 655 }) 656 if err != nil { 657 return err 658 } 659 } 660 661 _, _, err = pvtStateWriter.Done() 662 if err != nil { 663 return err 664 } 665 666 // Generate the signable metadata files for sample snapshot 667 signableMetadataBytes, err := signableMetadata.ToJSON() 668 if err != nil { 669 return err 670 } 671 // Populate temporary directory with signable metadata files 672 err = fileutil.CreateAndSyncFile(filepath.Join(dir, kvledger.SnapshotSignableMetadataFileName), signableMetadataBytes, 0o444) 673 if err != nil { 674 return err 675 } 676 677 return nil 678 } 679 680 // compareSnapshots calls the Compare tool and extracts the result json 681 func compareSnapshots(ss1 string, ss2 string, res string, firstN int) (int, string, string, string, error) { 682 // Run compare tool on snapshots 683 count, opath, err := Compare(ss1, ss2, res, firstN) 684 if err != nil || count == -1 { 685 return count, "", "", "", err 686 } 687 688 // Get json as a string from generated output files 689 pubStr, err := outputFileToString(AllPubDiffsByKey, opath) 690 if err != nil { 691 return 0, "", "", "", err 692 } 693 pvtStr, err := outputFileToString(AllPvtDiffsByKey, opath) 694 if err != nil { 695 return 0, "", "", "", err 696 } 697 firStr, err := outputFileToString(FirstDiffsByHeight, opath) 698 if err != nil { 699 return 0, "", "", "", err 700 } 701 702 return count, pubStr, pvtStr, firStr, nil 703 } 704 705 func outputFileToString(f string, path string) (string, error) { 706 fpath := filepath.Join(path, f) 707 exists, err := outputFileExists(fpath) 708 if err != nil { 709 return "", err 710 } 711 if exists { 712 b, err := ioutil.ReadFile(fpath) 713 if err != nil { 714 return "", err 715 } 716 out, err := ioutil.ReadAll(bytes.NewReader(b)) 717 if err != nil { 718 return "", err 719 } 720 return string(out), nil 721 } 722 return "null", nil 723 } 724 725 func outputFileExists(f string) (bool, error) { 726 _, err := os.Stat(f) 727 if err != nil { 728 if os.IsNotExist(err) { 729 return false, nil 730 } 731 return false, err 732 } 733 return true, nil 734 } 735 736 // toBytes serializes the Height 737 func toBytes(blockNum uint64, txNum uint64) []byte { 738 blockNumBytes := util.EncodeOrderPreservingVarUint64(blockNum) 739 txNumBytes := util.EncodeOrderPreservingVarUint64(txNum) 740 return append(blockNumBytes, txNumBytes...) 741 } 742 743 func TestJSONArrayFileWriter(t *testing.T) { 744 sampleDiffRecords := []diffRecord{ 745 { 746 Namespace: "abc", 747 Key: "key-52", 748 Record1: &snapshotRecord{ 749 Value: "red", 750 BlockNum: 254, 751 TxNum: 21, 752 }, 753 Record2: &snapshotRecord{ 754 Value: "blue", 755 BlockNum: 254, 756 TxNum: 21, 757 }, 758 }, 759 { 760 Namespace: "abc", 761 Key: "key-73", 762 Record1: &snapshotRecord{ 763 Value: "green", 764 BlockNum: 472, 765 TxNum: 61, 766 }, 767 Record2: nil, 768 }, 769 { 770 Namespace: "xyz", 771 Key: "key-44", 772 Record1: nil, 773 Record2: &snapshotRecord{ 774 Value: "purple", 775 BlockNum: 566, 776 TxNum: 3, 777 }, 778 }, 779 } 780 781 expectedResult := `[ 782 { 783 "namespace" : "abc", 784 "key" : "key-52", 785 "snapshotrecord1" : { 786 "value" : "red", 787 "blockNum" : 254, 788 "txNum" : 21 789 }, 790 "snapshotrecord2" : { 791 "value" : "blue", 792 "blockNum" : 254, 793 "txNum" : 21 794 } 795 }, 796 { 797 "namespace" : "abc", 798 "key" : "key-73", 799 "snapshotrecord1" : { 800 "value" : "green", 801 "blockNum" : 472, 802 "txNum" : 61 803 }, 804 "snapshotrecord2" : null 805 }, 806 { 807 "namespace" : "xyz", 808 "key" : "key-44", 809 "snapshotrecord1" : null, 810 "snapshotrecord2" : { 811 "value" : "purple", 812 "blockNum" : 566, 813 "txNum" : 3 814 } 815 } 816 ]` 817 818 // Create temporary directory for output 819 resultDir, err := ioutil.TempDir("", "result") 820 require.NoError(t, err) 821 defer os.RemoveAll(resultDir) 822 // Create the output file 823 jsonResultFile, err := newJSONFileWriter(filepath.Join(resultDir, "result.json")) 824 require.NoError(t, err) 825 // Write each sample diffRecord to the output file and close 826 for _, diffRecord := range sampleDiffRecords { 827 err = jsonResultFile.addRecord(diffRecord) 828 require.NoError(t, err) 829 } 830 err = jsonResultFile.close() 831 require.NoError(t, err) 832 833 // Read results of output and compare 834 resBytes, err := ioutil.ReadFile(filepath.Join(resultDir, "result.json")) 835 require.NoError(t, err) 836 res, err := ioutil.ReadAll(bytes.NewReader(resBytes)) 837 require.NoError(t, err) 838 839 require.JSONEq(t, expectedResult, string(res)) 840 } 841 842 func TestNSKeyCompare(t *testing.T) { 843 testCases := []struct { 844 nsKey1 nsKey 845 nsKey2 nsKey 846 expected int 847 }{ 848 { 849 nsKey1: nsKey{ 850 namespace: "xyz", 851 key: []byte("key-1"), 852 }, 853 nsKey2: nsKey{ 854 namespace: "abc", 855 key: []byte("key-2"), 856 }, 857 expected: 1, 858 }, 859 { 860 nsKey1: nsKey{ 861 namespace: "abc", 862 key: []byte("key-1"), 863 }, 864 nsKey2: nsKey{ 865 namespace: "xyz", 866 key: []byte("key-2"), 867 }, 868 expected: -1, 869 }, 870 { 871 nsKey1: nsKey{ 872 namespace: "abc", 873 key: []byte("key-1"), 874 }, 875 nsKey2: nsKey{ 876 namespace: "abc", 877 key: []byte("key-2"), 878 }, 879 expected: -1, 880 }, 881 { 882 nsKey1: nsKey{ 883 namespace: "abc", 884 key: []byte("key-2"), 885 }, 886 nsKey2: nsKey{ 887 namespace: "abc", 888 key: []byte("key-1"), 889 }, 890 expected: 1, 891 }, 892 { 893 nsKey1: nsKey{ 894 namespace: "abc", 895 key: []byte("key-1"), 896 }, 897 nsKey2: nsKey{ 898 namespace: "abc", 899 key: []byte("key-1"), 900 }, 901 expected: 0, 902 }, 903 } 904 905 for i, testCase := range testCases { 906 t.Run(fmt.Sprint(i+1), func(t *testing.T) { 907 require.Equal(t, nsKeyCompare(&testCase.nsKey1, &testCase.nsKey2), testCase.expected) 908 }) 909 } 910 }