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 }