github.com/MetalBlockchain/metalgo@v1.11.9/database/corruptabledb/db_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package corruptabledb
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	"go.uber.org/mock/gomock"
    13  
    14  	"github.com/MetalBlockchain/metalgo/database"
    15  	"github.com/MetalBlockchain/metalgo/database/memdb"
    16  )
    17  
    18  var errTest = errors.New("non-nil error")
    19  
    20  func newDB() *Database {
    21  	baseDB := memdb.New()
    22  	return New(baseDB)
    23  }
    24  
    25  func TestInterface(t *testing.T) {
    26  	for name, test := range database.Tests {
    27  		t.Run(name, func(t *testing.T) {
    28  			test(t, newDB())
    29  		})
    30  	}
    31  }
    32  
    33  func FuzzKeyValue(f *testing.F) {
    34  	database.FuzzKeyValue(f, newDB())
    35  }
    36  
    37  func FuzzNewIteratorWithPrefix(f *testing.F) {
    38  	database.FuzzNewIteratorWithPrefix(f, newDB())
    39  }
    40  
    41  func FuzzNewIteratorWithStartAndPrefix(f *testing.F) {
    42  	database.FuzzNewIteratorWithStartAndPrefix(f, newDB())
    43  }
    44  
    45  // TestCorruption tests to make sure corruptabledb wrapper works as expected.
    46  func TestCorruption(t *testing.T) {
    47  	key := []byte("hello")
    48  	value := []byte("world")
    49  	tests := map[string]func(db database.Database) error{
    50  		"corrupted has": func(db database.Database) error {
    51  			_, err := db.Has(key)
    52  			return err
    53  		},
    54  		"corrupted get": func(db database.Database) error {
    55  			_, err := db.Get(key)
    56  			return err
    57  		},
    58  		"corrupted put": func(db database.Database) error {
    59  			return db.Put(key, value)
    60  		},
    61  		"corrupted delete": func(db database.Database) error {
    62  			return db.Delete(key)
    63  		},
    64  		"corrupted batch": func(db database.Database) error {
    65  			corruptableBatch := db.NewBatch()
    66  			require.NotNil(t, corruptableBatch)
    67  
    68  			require.NoError(t, corruptableBatch.Put(key, value))
    69  
    70  			return corruptableBatch.Write()
    71  		},
    72  		"corrupted healthcheck": func(db database.Database) error {
    73  			_, err := db.HealthCheck(context.Background())
    74  			return err
    75  		},
    76  	}
    77  	corruptableDB := newDB()
    78  	_ = corruptableDB.handleError(errTest)
    79  	for name, testFn := range tests {
    80  		t.Run(name, func(tt *testing.T) {
    81  			err := testFn(corruptableDB)
    82  			require.ErrorIs(tt, err, errTest)
    83  		})
    84  	}
    85  }
    86  
    87  func TestIterator(t *testing.T) {
    88  	errIter := errors.New("iterator error")
    89  
    90  	type test struct {
    91  		name              string
    92  		databaseErrBefore error
    93  		modifyIter        func(*gomock.Controller, *iterator)
    94  		op                func(*require.Assertions, *iterator)
    95  		expectedErr       error
    96  	}
    97  
    98  	tests := []test{
    99  		{
   100  			name:              "corrupted database; Next",
   101  			databaseErrBefore: errTest,
   102  			expectedErr:       errTest,
   103  			modifyIter:        func(*gomock.Controller, *iterator) {},
   104  			op: func(require *require.Assertions, iter *iterator) {
   105  				require.False(iter.Next())
   106  			},
   107  		},
   108  		{
   109  			name:              "Next corrupts database",
   110  			databaseErrBefore: nil,
   111  			expectedErr:       errIter,
   112  			modifyIter: func(ctrl *gomock.Controller, iter *iterator) {
   113  				mockInnerIter := database.NewMockIterator(ctrl)
   114  				mockInnerIter.EXPECT().Next().Return(false)
   115  				mockInnerIter.EXPECT().Error().Return(errIter)
   116  				iter.Iterator = mockInnerIter
   117  			},
   118  			op: func(require *require.Assertions, iter *iterator) {
   119  				require.False(iter.Next())
   120  			},
   121  		},
   122  		{
   123  			name:              "corrupted database; Error",
   124  			databaseErrBefore: errTest,
   125  			expectedErr:       errTest,
   126  			modifyIter:        func(*gomock.Controller, *iterator) {},
   127  			op: func(require *require.Assertions, iter *iterator) {
   128  				err := iter.Error()
   129  				require.ErrorIs(err, errTest)
   130  			},
   131  		},
   132  		{
   133  			name:              "Error corrupts database",
   134  			databaseErrBefore: nil,
   135  			expectedErr:       errIter,
   136  			modifyIter: func(ctrl *gomock.Controller, iter *iterator) {
   137  				mockInnerIter := database.NewMockIterator(ctrl)
   138  				mockInnerIter.EXPECT().Error().Return(errIter)
   139  				iter.Iterator = mockInnerIter
   140  			},
   141  			op: func(require *require.Assertions, iter *iterator) {
   142  				err := iter.Error()
   143  				require.ErrorIs(err, errIter)
   144  			},
   145  		},
   146  		{
   147  			name:              "corrupted database; Key",
   148  			databaseErrBefore: errTest,
   149  			expectedErr:       errTest,
   150  			modifyIter:        func(*gomock.Controller, *iterator) {},
   151  			op: func(_ *require.Assertions, iter *iterator) {
   152  				_ = iter.Key()
   153  			},
   154  		},
   155  		{
   156  			name:              "corrupted database; Value",
   157  			databaseErrBefore: errTest,
   158  			expectedErr:       errTest,
   159  			modifyIter:        func(*gomock.Controller, *iterator) {},
   160  			op: func(_ *require.Assertions, iter *iterator) {
   161  				_ = iter.Value()
   162  			},
   163  		},
   164  		{
   165  			name:              "corrupted database; Release",
   166  			databaseErrBefore: errTest,
   167  			expectedErr:       errTest,
   168  			modifyIter:        func(*gomock.Controller, *iterator) {},
   169  			op: func(_ *require.Assertions, iter *iterator) {
   170  				iter.Release()
   171  			},
   172  		},
   173  	}
   174  
   175  	for _, tt := range tests {
   176  		t.Run(tt.name, func(t *testing.T) {
   177  			require := require.New(t)
   178  			ctrl := gomock.NewController(t)
   179  
   180  			// Make a database
   181  			corruptableDB := newDB()
   182  			// Put a key-value pair in the database.
   183  			require.NoError(corruptableDB.Put([]byte{0}, []byte{1}))
   184  
   185  			// Mark database as corupted, if applicable
   186  			_ = corruptableDB.handleError(tt.databaseErrBefore)
   187  
   188  			// Make an iterator
   189  			iter := &iterator{
   190  				Iterator: corruptableDB.NewIterator(),
   191  				db:       corruptableDB,
   192  			}
   193  
   194  			// Modify the iterator (optional)
   195  			tt.modifyIter(ctrl, iter)
   196  
   197  			// Do an iterator operation
   198  			tt.op(require, iter)
   199  
   200  			err := corruptableDB.corrupted()
   201  			require.ErrorIs(err, tt.expectedErr)
   202  		})
   203  	}
   204  }