github.com/lino-network/lino@v0.6.11/x/global/manager/manager_test.go (about)

     1  package manager
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  	"time"
     9  
    10  	wire "github.com/cosmos/cosmos-sdk/codec"
    11  	sdk "github.com/cosmos/cosmos-sdk/types"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	mparam "github.com/lino-network/lino/param/mocks"
    16  	"github.com/lino-network/lino/testsuites"
    17  	"github.com/lino-network/lino/testutils"
    18  	linotypes "github.com/lino-network/lino/types"
    19  	mapp "github.com/lino-network/lino/x/global/manager/mocks"
    20  	"github.com/lino-network/lino/x/global/model"
    21  )
    22  
    23  type testEvent struct {
    24  	Id int64 `json:"id"`
    25  }
    26  
    27  type testBadEvent struct {
    28  	Id int64 `json:"id"`
    29  }
    30  
    31  type testIgnoredEvent struct {
    32  	Id int64 `json:"id"`
    33  }
    34  
    35  func regTestCodec(c *wire.Codec) {
    36  	c.RegisterInterface((*linotypes.Event)(nil), nil)
    37  	c.RegisterConcrete(testEvent{}, "lino/testevent", nil)
    38  	c.RegisterConcrete(testBadEvent{}, "lino/testbadevent", nil)
    39  	c.RegisterConcrete(testIgnoredEvent{}, "lino/testignoredevent", nil)
    40  }
    41  
    42  func testCodec() *wire.Codec {
    43  	c := wire.New()
    44  	regTestCodec(c)
    45  	return c
    46  }
    47  
    48  var (
    49  	storeKeyStr = "testGlobalStore"
    50  	kvStoreKey  = sdk.NewKVStoreKey(storeKeyStr)
    51  )
    52  
    53  type storeDumper struct{}
    54  
    55  func (dumper storeDumper) NewDumper() *testutils.Dumper {
    56  	return model.NewDumper(model.NewGlobalStorage(kvStoreKey, testCodec()), regTestCodec)
    57  }
    58  
    59  type globalManagerTestSuite struct {
    60  	testsuites.GoldenTestSuite
    61  	global       GlobalManager
    62  	mParamKeeper *mparam.ParamKeeper
    63  	mApp         *mapp.FakeApp
    64  }
    65  
    66  func NewGlobalManagerTestSuite() *globalManagerTestSuite {
    67  	return &globalManagerTestSuite{
    68  		GoldenTestSuite: testsuites.NewGoldenTestSuite(storeDumper{}, kvStoreKey),
    69  	}
    70  }
    71  
    72  func (suite *globalManagerTestSuite) SetupTest() {
    73  	suite.mParamKeeper = new(mparam.ParamKeeper)
    74  	suite.mApp = new(mapp.FakeApp)
    75  	suite.SetupCtx(0, time.Unix(0, 0), kvStoreKey)
    76  	suite.global = NewGlobalManager(kvStoreKey, suite.mParamKeeper, testCodec(),
    77  		suite.mApp.Hourly,
    78  		suite.mApp.Daily,
    79  		suite.mApp.Monthly,
    80  		suite.mApp.Yearly)
    81  }
    82  
    83  func TestGlobalManagerSuite(t *testing.T) {
    84  	suite.Run(t, NewGlobalManagerTestSuite())
    85  }
    86  
    87  func (suite *globalManagerTestSuite) TestInitGenesis() {
    88  	init := int64(123456)
    89  	suite.NextBlock(time.Unix(init, 0))
    90  	suite.global.InitGenesis(suite.Ctx)
    91  	suite.Equal(init, suite.global.GetLastBlockTime(suite.Ctx))
    92  	suite.Golden()
    93  }
    94  
    95  func (suite *globalManagerTestSuite) TestGetPastDay() {
    96  	init := int64(123456)
    97  	suite.NextBlock(time.Unix(init, 0))
    98  	suite.global.InitGenesis(suite.Ctx)
    99  
   100  	suite.Equal(int64(0), suite.global.GetPastDay(suite.Ctx, init+3600*24-1))
   101  	suite.Equal(int64(1), suite.global.GetPastDay(suite.Ctx, init+3600*24))
   102  	suite.Equal(int64(1), suite.global.GetPastDay(suite.Ctx, init+3600*24*2-1))
   103  	suite.Equal(int64(2), suite.global.GetPastDay(suite.Ctx, init+3600*24*2))
   104  }
   105  
   106  func (suite *globalManagerTestSuite) TestOnBeginBlockExecuted() {
   107  	init := int64(123456)
   108  	suite.NextBlock(time.Unix(init, 0))
   109  	suite.global.InitGenesis(suite.Ctx)
   110  
   111  	suite.mApp.On("Hourly", mock.Anything).Return(nil).Times(linotypes.HoursPerYear)
   112  	suite.mApp.On("Daily", mock.Anything).Return(nil).Times(linotypes.HoursPerYear / 24)
   113  	suite.mApp.On("Monthly", mock.Anything).Return(nil).Times(12)
   114  	suite.mApp.On("Yearly", mock.Anything).Return(nil).Times(1)
   115  	for i := init + 3600; i <= init+linotypes.MinutesPerYear*60; i += 3600 {
   116  		suite.NextBlock(time.Unix(i, 0))
   117  		suite.global.OnBeginBlock(suite.Ctx)
   118  		suite.global.OnEndBlock(suite.Ctx)
   119  	}
   120  	suite.mApp.AssertExpectations(suite.T())
   121  }
   122  
   123  func (suite *globalManagerTestSuite) TestOnBeginBlockErrLogged() {
   124  	init := int64(123456)
   125  	suite.NextBlock(time.Unix(init, 0))
   126  	suite.global.InitGenesis(suite.Ctx)
   127  
   128  	suite.mApp.On("Hourly", mock.Anything).Return(nil).Times(linotypes.HoursPerYear)
   129  	suite.mApp.On("Daily", mock.Anything).Return(nil).Times(linotypes.HoursPerYear / 24)
   130  	suite.mApp.On("Yearly", mock.Anything).Return(nil).Times(1)
   131  
   132  	last := int64(0)
   133  	for i := init + 3600; i <= init+linotypes.MinutesPerYear*60; i += 3600 {
   134  		suite.NextBlock(time.Unix(i, 0))
   135  		if (i-init)/60/linotypes.MinutesPerMonth > last {
   136  			last = (i - init) / 60 / linotypes.MinutesPerMonth
   137  			suite.mApp.On("Monthly", mock.Anything).Return([]linotypes.BCEventErr{
   138  				linotypes.NewBCEventErr(suite.Ctx, linotypes.ErrTestDummyError(), "test"),
   139  			}).Once()
   140  		}
   141  		suite.global.OnBeginBlock(suite.Ctx)
   142  		suite.global.OnEndBlock(suite.Ctx)
   143  	}
   144  	suite.mApp.AssertExpectations(suite.T())
   145  	suite.Equal(12, len(suite.global.GetBCEventErrors(suite.Ctx)))
   146  	suite.Golden()
   147  }
   148  
   149  func (suite *globalManagerTestSuite) TestRegisterEventsAndExec() {
   150  	init := int64(123456)
   151  	suite.NextBlock(time.Unix(init, 0))
   152  	suite.global.InitGenesis(suite.Ctx)
   153  	suite.mApp.On("Hourly", mock.Anything).Return(nil)
   154  	suite.mApp.On("Daily", mock.Anything).Return(nil)
   155  	suite.mApp.On("Monthly", mock.Anything).Return(nil)
   156  	suite.mApp.On("Yearly", mock.Anything).Return(nil)
   157  
   158  	// a simple counter event
   159  	nExecuted := int64(0)
   160  	exec := func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   161  		e := event.(testEvent)
   162  		nExecuted++
   163  		if e.Id%7 == 0 {
   164  			return linotypes.ErrTestDummyError()
   165  		}
   166  		return nil
   167  	}
   168  
   169  	// register event of past time.
   170  	suite.NotNil(suite.global.RegisterEventAtTime(suite.Ctx, init-1, testEvent{}))
   171  
   172  	// events will be executed starting from an hour later
   173  	nScheduled := int64(0)
   174  	for i := init + 3; i <= init+7*3600; i += 600 {
   175  		err := suite.global.RegisterEventAtTime(suite.Ctx, i, testEvent{Id: nScheduled})
   176  		suite.Nil(err)
   177  		nScheduled++
   178  	}
   179  	// events can be scheduled at same time
   180  	for i := init + 3 + 600*4; i <= init+7*3600; i += 600 {
   181  		err := suite.global.RegisterEventAtTime(suite.Ctx, i, testEvent{Id: nScheduled})
   182  		suite.Nil(err)
   183  		nScheduled++
   184  	}
   185  
   186  	// while execution, new events are scheduled, but not executed
   187  	for i := init + 1; i <= init+10*3600; i += 577 {
   188  		suite.NextBlock(time.Unix(i, 0))
   189  		suite.global.OnBeginBlock(suite.Ctx)
   190  		suite.global.ExecuteEvents(suite.Ctx, exec)
   191  		err := suite.global.RegisterEventAtTime(suite.Ctx, i+10000000000, testEvent{Id: -1})
   192  		suite.Nil(err)
   193  		suite.global.OnEndBlock(suite.Ctx)
   194  	}
   195  
   196  	suite.Equal(nScheduled, nExecuted)
   197  	suite.Golden()
   198  }
   199  
   200  func (suite *globalManagerTestSuite) TestEventOKWrite() {
   201  	init := int64(123456)
   202  	suite.NextBlock(time.Unix(init, 0))
   203  	suite.global.InitGenesis(suite.Ctx)
   204  	suite.mApp.On("Hourly", mock.Anything).Return(nil)
   205  	suite.mApp.On("Daily", mock.Anything).Return(nil)
   206  	suite.mApp.On("Monthly", mock.Anything).Return(nil)
   207  	suite.mApp.On("Yearly", mock.Anything).Return(nil)
   208  
   209  	bad := func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   210  		// this bad exec will reset global time to zero while return an error.
   211  		suite.global.storage.SetGlobalTime(ctx, &model.GlobalTime{
   212  			ChainStartTime: 999,
   213  		})
   214  		return nil
   215  	}
   216  
   217  	err := suite.global.RegisterEventAtTime(suite.Ctx, init+30, testEvent{})
   218  	suite.Nil(err)
   219  
   220  	suite.NextBlock(time.Unix(init+30+10, 0))
   221  	suite.global.ExecuteEvents(suite.Ctx, bad)
   222  	suite.Equal(int64(999),
   223  		suite.global.GetGlobalTime(suite.Ctx).ChainStartTime) // state not changed
   224  	// suite.Golden()
   225  }
   226  
   227  func (suite *globalManagerTestSuite) TestEventErrIsolation() {
   228  	init := int64(123456)
   229  	suite.NextBlock(time.Unix(init, 0))
   230  	suite.global.InitGenesis(suite.Ctx)
   231  	suite.mApp.On("Hourly", mock.Anything).Return(nil)
   232  	suite.mApp.On("Daily", mock.Anything).Return(nil)
   233  	suite.mApp.On("Monthly", mock.Anything).Return(nil)
   234  	suite.mApp.On("Yearly", mock.Anything).Return(nil)
   235  
   236  	bad := func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   237  		// this bad exec will reset global time to zero while return an error.
   238  		suite.global.storage.SetGlobalTime(ctx, &model.GlobalTime{})
   239  		return linotypes.ErrTestDummyError()
   240  	}
   241  
   242  	err := suite.global.RegisterEventAtTime(suite.Ctx, init+30, testEvent{})
   243  	suite.Nil(err)
   244  
   245  	suite.NextBlock(time.Unix(init+30+10, 0))
   246  	suite.global.ExecuteEvents(suite.Ctx, bad)
   247  	suite.Equal(1, len(suite.global.GetEventErrors(suite.Ctx)))             // err logged
   248  	suite.Equal(init, suite.global.GetGlobalTime(suite.Ctx).ChainStartTime) // state not changed
   249  	suite.Golden()
   250  }
   251  
   252  func (suite *globalManagerTestSuite) TestExecFutureEvents() {
   253  	suite.NextBlock(time.Unix(0, 0))
   254  	suite.global.InitGenesis(suite.Ctx)
   255  
   256  	init := int64(123456)
   257  	t0 := init - 1
   258  	t1 := init + 1
   259  	t2 := init + 30
   260  
   261  	id := int64(0)
   262  	for _, t := range []int64{t0, t1, t2} {
   263  		err := suite.global.RegisterEventAtTime(suite.Ctx, t, testEvent{Id: id})
   264  		suite.Nil(err)
   265  		err = suite.global.RegisterEventAtTime(suite.Ctx, t, testEvent{Id: id})
   266  		suite.Nil(err)
   267  		err = suite.global.RegisterEventAtTime(suite.Ctx, t, testBadEvent{Id: id})
   268  		suite.Nil(err)
   269  		err = suite.global.RegisterEventAtTime(suite.Ctx, t, testIgnoredEvent{Id: id})
   270  		suite.Nil(err)
   271  
   272  	}
   273  
   274  	suite.NextBlock(time.Unix(init, 0))
   275  
   276  	count := 0
   277  	filter := func(ts int64, event linotypes.Event) bool {
   278  		if ts < init {
   279  			return false
   280  		}
   281  		switch event.(type) {
   282  		case testEvent, testBadEvent:
   283  			return true
   284  		default:
   285  			return false
   286  		}
   287  	}
   288  	suite.global.execFutureEvents(suite.Ctx,
   289  		func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   290  			switch event.(type) {
   291  			case testEvent:
   292  				count++
   293  				return nil
   294  			case testBadEvent:
   295  				return linotypes.ErrTestDummyError()
   296  			default:
   297  				suite.FailNow("executing events that should be ignored")
   298  				return nil
   299  			}
   300  		},
   301  		filter,
   302  	)
   303  	suite.Equal(4, count)
   304  
   305  	// bad is no longer bad.
   306  	suite.global.execFutureEvents(suite.Ctx,
   307  		func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   308  			switch event.(type) {
   309  			case testEvent, testBadEvent:
   310  				count++
   311  				return nil
   312  			default:
   313  				suite.FailNow("executing events that should be ignored")
   314  				return nil
   315  			}
   316  		},
   317  		filter,
   318  	)
   319  	suite.Equal(6, count)
   320  
   321  	// ignored is not ignored.
   322  	suite.global.execFutureEvents(suite.Ctx,
   323  		func(ctx sdk.Context, event linotypes.Event) sdk.Error {
   324  			count++
   325  			return nil
   326  		},
   327  		func(ts int64, event linotypes.Event) bool { return ts > init },
   328  	)
   329  	suite.Equal(8, count)
   330  }
   331  
   332  func (suite *globalManagerTestSuite) TestImportExport() {
   333  	init := int64(123456)
   334  	suite.NextBlock(time.Unix(init, 0))
   335  	suite.global.InitGenesis(suite.Ctx)
   336  
   337  	err := suite.global.RegisterEventAtTime(suite.Ctx, init+30, testEvent{Id: 1})
   338  	suite.Nil(err)
   339  	err = suite.global.RegisterEventAtTime(suite.Ctx, init+30, testEvent{Id: 2})
   340  	suite.Nil(err)
   341  	err = suite.global.RegisterEventAtTime(suite.Ctx, init+45, testEvent{Id: 3})
   342  	suite.Nil(err)
   343  
   344  	cdc := testCodec()
   345  
   346  	dir, err2 := ioutil.TempDir("", "test")
   347  	suite.Require().Nil(err2)
   348  	defer os.RemoveAll(dir) // clean up
   349  
   350  	tmpfn := filepath.Join(dir, "tmpfile")
   351  	err2 = suite.global.ExportToFile(suite.Ctx, cdc, tmpfn)
   352  	suite.Nil(err2)
   353  
   354  	// reset state
   355  	suite.SetupTest()
   356  	err2 = suite.global.ImportFromFile(suite.Ctx, cdc, tmpfn)
   357  	suite.Nil(err2)
   358  
   359  	suite.Golden()
   360  }