github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/snowman/job/scheduler_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 job 5 6 import ( 7 "context" 8 "errors" 9 "testing" 10 11 "github.com/stretchr/testify/require" 12 ) 13 14 const ( 15 depToResolve = iota 16 depToNeglect 17 ) 18 19 var errDuplicateExecution = errors.New("job already executed") 20 21 type testJob struct { 22 calledExecute bool 23 fulfilled []int 24 abandoned []int 25 } 26 27 func (j *testJob) Execute(_ context.Context, fulfilled []int, abandoned []int) error { 28 if j.calledExecute { 29 return errDuplicateExecution 30 } 31 j.calledExecute = true 32 j.fulfilled = fulfilled 33 j.abandoned = abandoned 34 return nil 35 } 36 37 func (j *testJob) reset() { 38 j.calledExecute = false 39 j.fulfilled = nil 40 j.abandoned = nil 41 } 42 43 func newSchedulerWithJob[T comparable]( 44 t *testing.T, 45 job Job[T], 46 dependencies []T, 47 fulfilled []T, 48 abandoned []T, 49 ) *Scheduler[T] { 50 s := NewScheduler[T]() 51 require.NoError(t, s.Schedule(context.Background(), job, dependencies...)) 52 for _, d := range fulfilled { 53 require.NoError(t, s.Fulfill(context.Background(), d)) 54 } 55 for _, d := range abandoned { 56 require.NoError(t, s.Abandon(context.Background(), d)) 57 } 58 return s 59 } 60 61 func TestScheduler_Schedule(t *testing.T) { 62 userJob := &testJob{} 63 tests := []struct { 64 name string 65 scheduler *Scheduler[int] 66 dependencies []int 67 expectedExecuted bool 68 expectedNumDependencies int 69 expectedScheduler *Scheduler[int] 70 }{ 71 { 72 name: "no dependencies", 73 scheduler: NewScheduler[int](), 74 dependencies: nil, 75 expectedExecuted: true, 76 expectedNumDependencies: 0, 77 expectedScheduler: NewScheduler[int](), 78 }, 79 { 80 name: "one dependency", 81 scheduler: NewScheduler[int](), 82 dependencies: []int{depToResolve}, 83 expectedExecuted: false, 84 expectedNumDependencies: 1, 85 expectedScheduler: &Scheduler[int]{ 86 dependents: map[int][]*job[int]{ 87 depToResolve: { 88 { 89 numUnresolved: 1, 90 fulfilled: nil, 91 abandoned: nil, 92 job: userJob, 93 }, 94 }, 95 }, 96 }, 97 }, 98 { 99 name: "two dependencies", 100 scheduler: NewScheduler[int](), 101 dependencies: []int{depToResolve, depToNeglect}, 102 expectedExecuted: false, 103 expectedNumDependencies: 2, 104 expectedScheduler: &Scheduler[int]{ 105 dependents: map[int][]*job[int]{ 106 depToResolve: { 107 { 108 numUnresolved: 2, 109 fulfilled: nil, 110 abandoned: nil, 111 job: userJob, 112 }, 113 }, 114 depToNeglect: { 115 { 116 numUnresolved: 2, 117 fulfilled: nil, 118 abandoned: nil, 119 job: userJob, 120 }, 121 }, 122 }, 123 }, 124 }, 125 { 126 name: "additional dependency", 127 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve}, nil, nil), 128 dependencies: []int{depToResolve}, 129 expectedExecuted: false, 130 expectedNumDependencies: 1, 131 expectedScheduler: &Scheduler[int]{ 132 dependents: map[int][]*job[int]{ 133 depToResolve: { 134 { 135 numUnresolved: 1, 136 fulfilled: nil, 137 abandoned: nil, 138 job: userJob, 139 }, 140 { 141 numUnresolved: 1, 142 fulfilled: nil, 143 abandoned: nil, 144 job: userJob, 145 }, 146 }, 147 }, 148 }, 149 }, 150 } 151 for _, test := range tests { 152 t.Run(test.name, func(t *testing.T) { 153 require := require.New(t) 154 155 // Reset the variable between tests 156 userJob.reset() 157 158 require.NoError(test.scheduler.Schedule(context.Background(), userJob, test.dependencies...)) 159 require.Equal(test.expectedNumDependencies, test.scheduler.NumDependencies()) 160 require.Equal(test.expectedExecuted, userJob.calledExecute) 161 require.Empty(userJob.fulfilled) 162 require.Empty(userJob.abandoned) 163 require.Equal(test.expectedScheduler, test.scheduler) 164 }) 165 } 166 } 167 168 func TestScheduler_Fulfill(t *testing.T) { 169 userJob := &testJob{} 170 tests := []struct { 171 name string 172 scheduler *Scheduler[int] 173 expectedExecuted bool 174 expectedFulfilled []int 175 expectedAbandoned []int 176 expectedScheduler *Scheduler[int] 177 }{ 178 { 179 name: "no jobs", 180 scheduler: NewScheduler[int](), 181 expectedExecuted: false, 182 expectedFulfilled: nil, 183 expectedAbandoned: nil, 184 expectedScheduler: NewScheduler[int](), 185 }, 186 { 187 name: "single dependency", 188 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve}, nil, nil), 189 expectedExecuted: true, 190 expectedFulfilled: []int{depToResolve}, 191 expectedAbandoned: nil, 192 expectedScheduler: NewScheduler[int](), 193 }, 194 { 195 name: "non-existent dependency", 196 scheduler: newSchedulerWithJob(t, userJob, []int{depToNeglect}, nil, nil), 197 expectedExecuted: false, 198 expectedFulfilled: nil, 199 expectedAbandoned: nil, 200 expectedScheduler: newSchedulerWithJob(t, userJob, []int{depToNeglect}, nil, nil), 201 }, 202 { 203 name: "incomplete dependencies", 204 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToNeglect}, nil, nil), 205 expectedExecuted: false, 206 expectedFulfilled: nil, 207 expectedAbandoned: nil, 208 expectedScheduler: &Scheduler[int]{ 209 dependents: map[int][]*job[int]{ 210 depToNeglect: { 211 { 212 numUnresolved: 1, 213 fulfilled: []int{depToResolve}, 214 abandoned: nil, 215 job: userJob, 216 }, 217 }, 218 }, 219 }, 220 }, 221 { 222 name: "duplicate dependency", 223 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToResolve}, nil, nil), 224 expectedExecuted: true, 225 expectedFulfilled: []int{depToResolve, depToResolve}, 226 expectedAbandoned: nil, 227 expectedScheduler: NewScheduler[int](), 228 }, 229 { 230 name: "previously abandoned", 231 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToNeglect}, nil, []int{depToNeglect}), 232 expectedExecuted: true, 233 expectedFulfilled: []int{depToResolve}, 234 expectedAbandoned: []int{depToNeglect}, 235 expectedScheduler: NewScheduler[int](), 236 }, 237 } 238 for _, test := range tests { 239 t.Run(test.name, func(t *testing.T) { 240 require := require.New(t) 241 242 // Reset the variable between tests 243 userJob.reset() 244 245 require.NoError(test.scheduler.Fulfill(context.Background(), depToResolve)) 246 require.Equal(test.expectedExecuted, userJob.calledExecute) 247 require.Equal(test.expectedFulfilled, userJob.fulfilled) 248 require.Equal(test.expectedAbandoned, userJob.abandoned) 249 require.Equal(test.expectedScheduler, test.scheduler) 250 }) 251 } 252 } 253 254 func TestScheduler_Abandon(t *testing.T) { 255 userJob := &testJob{} 256 tests := []struct { 257 name string 258 scheduler *Scheduler[int] 259 expectedExecuted bool 260 expectedFulfilled []int 261 expectedAbandoned []int 262 expectedScheduler *Scheduler[int] 263 }{ 264 { 265 name: "no jobs", 266 scheduler: NewScheduler[int](), 267 expectedExecuted: false, 268 expectedFulfilled: nil, 269 expectedAbandoned: nil, 270 expectedScheduler: NewScheduler[int](), 271 }, 272 { 273 name: "single dependency", 274 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve}, nil, nil), 275 expectedExecuted: true, 276 expectedFulfilled: nil, 277 expectedAbandoned: []int{depToResolve}, 278 expectedScheduler: NewScheduler[int](), 279 }, 280 { 281 name: "non-existent dependency", 282 scheduler: newSchedulerWithJob(t, userJob, []int{depToNeglect}, nil, nil), 283 expectedExecuted: false, 284 expectedFulfilled: nil, 285 expectedAbandoned: nil, 286 expectedScheduler: newSchedulerWithJob(t, userJob, []int{depToNeglect}, nil, nil), 287 }, 288 { 289 name: "incomplete dependencies", 290 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToNeglect}, nil, nil), 291 expectedExecuted: false, 292 expectedFulfilled: nil, 293 expectedAbandoned: nil, 294 expectedScheduler: &Scheduler[int]{ 295 dependents: map[int][]*job[int]{ 296 depToNeglect: { 297 { 298 numUnresolved: 1, 299 fulfilled: nil, 300 abandoned: []int{depToResolve}, 301 job: userJob, 302 }, 303 }, 304 }, 305 }, 306 }, 307 { 308 name: "duplicate dependency", 309 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToResolve}, nil, nil), 310 expectedExecuted: true, 311 expectedFulfilled: nil, 312 expectedAbandoned: []int{depToResolve, depToResolve}, 313 expectedScheduler: NewScheduler[int](), 314 }, 315 { 316 name: "previously fulfilled", 317 scheduler: newSchedulerWithJob(t, userJob, []int{depToResolve, depToNeglect}, []int{depToNeglect}, nil), 318 expectedExecuted: true, 319 expectedFulfilled: []int{depToNeglect}, 320 expectedAbandoned: []int{depToResolve}, 321 expectedScheduler: NewScheduler[int](), 322 }, 323 } 324 for _, test := range tests { 325 t.Run(test.name, func(t *testing.T) { 326 require := require.New(t) 327 328 // Reset the variable between tests 329 userJob.reset() 330 331 require.NoError(test.scheduler.Abandon(context.Background(), depToResolve)) 332 require.Equal(test.expectedExecuted, userJob.calledExecute) 333 require.Equal(test.expectedFulfilled, userJob.fulfilled) 334 require.Equal(test.expectedAbandoned, userJob.abandoned) 335 require.Equal(test.expectedScheduler, test.scheduler) 336 }) 337 } 338 }