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  }