github.com/m3db/m3@v1.5.0/src/cluster/changeset/manager_test.go (about) 1 // Copyright (c) 2016 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package changeset 22 23 import ( 24 "errors" 25 "strings" 26 "testing" 27 28 "github.com/m3db/m3/src/cluster/generated/proto/changesetpb" 29 "github.com/m3db/m3/src/cluster/generated/proto/changesettest" 30 "github.com/m3db/m3/src/cluster/kv" 31 "github.com/m3db/m3/src/cluster/kv/mem" 32 33 "github.com/golang/mock/gomock" 34 "github.com/golang/protobuf/proto" 35 "github.com/stretchr/testify/require" 36 ) 37 38 var ( 39 errBadThingsHappened = errors.New("bad things happened") 40 ) 41 42 func TestManager_ChangeEmptyInitialConfig(t *testing.T) { 43 s := newTestSuite(t) 44 defer s.finish() 45 46 var ( 47 config1 = new(configMatcher) 48 changes1 = new(changeSetMatcher) 49 changes2 = new(changeSetMatcher) 50 ) 51 52 gomock.InOrder( 53 // Get initial config - see no value and create 54 s.kv.EXPECT().Get("config").Return(nil, kv.ErrNotFound), 55 s.kv.EXPECT().SetIfNotExists("config", config1).Return(1, nil), 56 57 // Get initial changes - see no value and create 58 s.kv.EXPECT().Get("config/_changes/1").Return(nil, kv.ErrNotFound), 59 s.kv.EXPECT().SetIfNotExists("config/_changes/1", changes1).Return(1, nil), 60 s.kv.EXPECT().CheckAndSet("config/_changes/1", 1, changes2).Return(2, nil), 61 ) 62 63 require.NoError(t, s.mgr.Change(addLines("foo", "bar"))) 64 65 require.Equal(t, "", config1.config().Text) 66 67 require.Equal(t, int32(1), changes1.changeset().ForVersion) 68 require.Equal(t, changesetpb.ChangeSetState_OPEN, changes1.changeset().State) 69 require.Nil(t, changes1.changeset().Changes) 70 71 require.Equal(t, int32(1), changes2.changeset().ForVersion) 72 require.Equal(t, changesetpb.ChangeSetState_OPEN, changes2.changeset().State) 73 require.NotNil(t, changes2.changeset().Changes) 74 require.Equal(t, []string{"foo", "bar"}, changes2.changes(t).Lines) 75 } 76 77 func TestManager_ChangeInterruptOnCreateOfInitialConfig(t *testing.T) { 78 s := newTestSuite(t) 79 defer s.finish() 80 81 var ( 82 configVal = mem.NewValue(2, &changesettest.Config{}) 83 changesVal = mem.NewValue(12, s.newOpenChangeSet(2, &changesettest.Changes{})) 84 ) 85 86 gomock.InOrder( 87 // Initial attempt to create config - someone else gets there first 88 s.kv.EXPECT().Get("config").Return(nil, kv.ErrNotFound), 89 s.kv.EXPECT().SetIfNotExists("config", gomock.Any()).Return(0, kv.ErrAlreadyExists), 90 91 // Will refetch 92 s.kv.EXPECT().Get("config").Return(configVal, nil), 93 94 // Fetch corresponding changes 95 s.kv.EXPECT().Get("config/_changes/2").Return(changesVal, nil), 96 97 // ...And update 98 s.kv.EXPECT().CheckAndSet("config/_changes/2", 12, gomock.Any()).Return(13, nil), 99 ) 100 101 require.NoError(t, s.mgr.Change(addLines("foo", "bar"))) 102 103 // NB(mmihic): We only care that the expectations are met 104 } 105 106 func TestManager_ChangeInterruptOnCreateOfInitialChangeSet(t *testing.T) { 107 s := newTestSuite(t) 108 defer s.finish() 109 110 var ( 111 changesVal = mem.NewValue(12, s.newOpenChangeSet(13, &changesettest.Changes{})) 112 ) 113 114 gomock.InOrder( 115 s.kv.EXPECT().Get("config").Return(mem.NewValue(13, &changesettest.Config{}), nil), 116 117 // Initial attempt to create changes - someone else gets there first 118 s.kv.EXPECT().Get("config/_changes/13").Return(nil, kv.ErrNotFound), 119 s.kv.EXPECT().SetIfNotExists("config/_changes/13", gomock.Any()).Return(0, kv.ErrAlreadyExists), 120 121 // Will refetch 122 s.kv.EXPECT().Get("config/_changes/13").Return(changesVal, nil), 123 124 // ...And update 125 s.kv.EXPECT().CheckAndSet("config/_changes/13", 12, gomock.Any()).Return(13, nil), 126 ) 127 128 require.NoError(t, s.mgr.Change(addLines("foo", "bar"))) 129 130 // NB(mmihic): We only care that the expectations are met 131 } 132 133 func TestManager_ChangeErrorRetrievingConfig(t *testing.T) { 134 s := newTestSuite(t) 135 defer s.finish() 136 137 // Initial attempt to get changes fails 138 s.kv.EXPECT().Get("config").Return(nil, errBadThingsHappened) 139 140 err := s.mgr.Change(addLines("foo", "bar")) 141 require.Equal(t, errBadThingsHappened, err) 142 143 // NB(mmihic): We only care that the expectations are met 144 } 145 146 func TestManager_ChangeErrorRetrievingChangeSet(t *testing.T) { 147 s := newTestSuite(t) 148 defer s.finish() 149 150 gomock.InOrder( 151 s.kv.EXPECT().Get("config").Return(mem.NewValue(13, &changesettest.Config{}), nil), 152 s.kv.EXPECT().Get("config/_changes/13").Return(nil, errBadThingsHappened), 153 ) 154 155 err := s.mgr.Change(addLines("foo", "bar")) 156 require.Equal(t, errBadThingsHappened, err) 157 158 // NB(mmihic): We only care that the expectations are met 159 } 160 161 func TestManager_ChangeErrorUnmarshallingInitialChange(t *testing.T) { 162 s := newTestSuite(t) 163 defer s.finish() 164 165 gomock.InOrder( 166 s.kv.EXPECT().Get("config").Return(mem.NewValue(13, &changesettest.Config{}), nil), 167 s.kv.EXPECT().Get("config/_changes/13").Return(mem.NewValue(12, &changesetpb.ChangeSet{ 168 ForVersion: 13, 169 State: changesetpb.ChangeSetState_OPEN, 170 Changes: []byte("foo"), // Not a valid proto 171 }), nil), 172 ) 173 174 require.Error(t, s.mgr.Change(addLines("foo", "bar"))) 175 } 176 177 func TestManager_ChangeErrorUpdatingChangeSet(t *testing.T) { 178 s := newTestSuite(t) 179 defer s.finish() 180 181 var ( 182 updatedChanges = new(changeSetMatcher) 183 ) 184 185 gomock.InOrder( 186 s.kv.EXPECT().Get("config").Return(mem.NewValue(13, &changesettest.Config{}), nil), 187 s.kv.EXPECT().Get("config/_changes/13").Return(mem.NewValue(12, 188 s.newOpenChangeSet(13, &changesettest.Changes{})), nil), 189 s.kv.EXPECT().CheckAndSet("config/_changes/13", 12, updatedChanges). 190 Return(0, errBadThingsHappened), 191 ) 192 193 err := s.mgr.Change(addLines("foo", "bar")) 194 require.Equal(t, errBadThingsHappened, err) 195 } 196 197 func TestManager_ChangeVersionMismatchUpdatingChangeSet(t *testing.T) { 198 s := newTestSuite(t) 199 defer s.finish() 200 201 var ( 202 changes1 = new(changeSetMatcher) 203 changes2 = new(changeSetMatcher) 204 ) 205 206 gomock.InOrder( 207 // Version mismatch while updating changeset 208 s.kv.EXPECT().Get("config").Return(mem.NewValue(13, &changesettest.Config{}), nil), 209 s.kv.EXPECT().Get("config/_changes/13").Return(mem.NewValue(12, 210 s.newOpenChangeSet(13, &changesettest.Changes{})), nil), 211 s.kv.EXPECT().CheckAndSet("config/_changes/13", 12, changes1). 212 Return(0, kv.ErrVersionMismatch), 213 214 // Will try again 215 s.kv.EXPECT().Get("config").Return(mem.NewValue(14, &changesettest.Config{}), nil), 216 s.kv.EXPECT().Get("config/_changes/14").Return(mem.NewValue(22, 217 s.newOpenChangeSet(14, &changesettest.Changes{ 218 Lines: []string{"zed"}, 219 })), nil), 220 s.kv.EXPECT().CheckAndSet("config/_changes/14", 22, changes2).Return(23, nil), 221 ) 222 223 require.NoError(t, s.mgr.Change(addLines("foo", "bar"))) 224 require.Equal(t, []string{"zed", "foo", "bar"}, changes2.changes(t).Lines) 225 } 226 227 func TestManager_ChangeSuccess(t *testing.T) { 228 s := newTestSuite(t) 229 defer s.finish() 230 231 updatedChanges := new(changeSetMatcher) 232 gomock.InOrder( 233 s.kv.EXPECT().Get("config").Return(mem.NewValue(72, &changesettest.Config{}), nil), 234 s.kv.EXPECT().Get("config/_changes/72").Return(mem.NewValue(29, 235 s.newOpenChangeSet(72, &changesettest.Changes{ 236 Lines: []string{"ark", "bork"}, 237 })), nil), 238 s.kv.EXPECT().CheckAndSet("config/_changes/72", 29, updatedChanges).Return(23, nil), 239 ) 240 241 require.NoError(t, s.mgr.Change(addLines("foo", "bar"))) 242 require.Equal(t, updatedChanges.changes(t).Lines, []string{"ark", "bork", "foo", "bar"}) 243 } 244 245 func TestManager_ChangeOnClosedChangeSet(t *testing.T) { 246 s := newTestSuite(t) 247 defer s.finish() 248 249 gomock.InOrder( 250 s.kv.EXPECT().Get("config").Return(mem.NewValue(72, &changesettest.Config{}), nil), 251 s.kv.EXPECT().Get("config/_changes/72").Return(mem.NewValue(29, 252 s.newChangeSet(72, changesetpb.ChangeSetState_CLOSED, 253 &changesettest.Changes{ 254 Lines: []string{"ark", "bork"}, 255 })), nil), 256 ) 257 258 err := s.mgr.Change(addLines("foo", "bar")) 259 require.Equal(t, ErrChangeSetClosed, err) 260 } 261 262 func TestManager_ChangeFunctionFails(t *testing.T) { 263 s := newTestSuite(t) 264 defer s.finish() 265 266 gomock.InOrder( 267 s.kv.EXPECT().Get("config").Return(mem.NewValue(72, &changesettest.Config{}), nil), 268 s.kv.EXPECT().Get("config/_changes/72").Return(mem.NewValue(29, 269 s.newOpenChangeSet(72, &changesettest.Changes{ 270 Lines: []string{"ark", "bork"}, 271 })), nil), 272 ) 273 274 err := s.mgr.Change(func(cfg, changes proto.Message) error { 275 return errBadThingsHappened 276 }) 277 require.Equal(t, errBadThingsHappened, err) 278 } 279 280 func TestManagerCommit_Success(t *testing.T) { 281 s := newTestSuite(t) 282 defer s.finish() 283 284 var ( 285 changeSet1 = new(changeSetMatcher) 286 config1 = new(configMatcher) 287 288 committedVersion = 22 289 changeSetVersion = 17 290 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 291 ) 292 293 gomock.InOrder( 294 // Retrieve the config value 295 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 296 &changesettest.Config{ 297 Text: "shoop\nwoop\nhoop", 298 }), nil), 299 300 // Retrieve the change set 301 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 302 s.newOpenChangeSet(changeSetVersion, &changesettest.Changes{ 303 Lines: []string{"foo", "bar"}, 304 })), nil), 305 306 // Mark as committing 307 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, changeSet1). 308 Return(changeSetVersion+1, nil), 309 310 // Update the transformed confi 311 s.kv.EXPECT().CheckAndSet(s.configKey, committedVersion, config1). 312 Return(committedVersion+1, nil), 313 ) 314 315 err := s.mgr.Commit(committedVersion, commit) 316 require.NoError(t, err) 317 318 require.Equal(t, changesetpb.ChangeSetState_CLOSED, changeSet1.changeset().State) 319 require.Equal(t, "shoop\nwoop\nhoop\nfoo\nbar", config1.config().Text) 320 } 321 322 func TestManagerCommit_ConfigNotFound(t *testing.T) { 323 s := newTestSuite(t) 324 defer s.finish() 325 326 var ( 327 committedVersion = 22 328 ) 329 330 // KV service can't find config 331 s.kv.EXPECT().Get(s.configKey).Return(nil, kv.ErrNotFound) 332 333 // Commit should fail 334 err := s.mgr.Commit(committedVersion, commit) 335 require.Equal(t, kv.ErrNotFound, err) 336 } 337 338 func TestManagerCommit_ConfigGetError(t *testing.T) { 339 s := newTestSuite(t) 340 defer s.finish() 341 342 var ( 343 committedVersion = 22 344 ) 345 346 // KV service has error retrieving config 347 s.kv.EXPECT().Get(s.configKey).Return(nil, errBadThingsHappened) 348 349 // Commit should fail 350 err := s.mgr.Commit(committedVersion, commit) 351 require.Equal(t, errBadThingsHappened, err) 352 } 353 354 func TestManagerCommit_ConfigAtEarlierVersion(t *testing.T) { 355 s := newTestSuite(t) 356 defer s.finish() 357 358 var ( 359 committedVersion = 22 360 ) 361 362 // KV service returns earlier version 363 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion-1, 364 &changesettest.Config{ 365 Text: "shoop\nwoop\nhoop", 366 }), nil) 367 368 // Commit should fail 369 err := s.mgr.Commit(committedVersion, commit) 370 require.Equal(t, ErrUnknownVersion, err) 371 } 372 373 func TestManagerCommit_ConfigAtLaterVersion(t *testing.T) { 374 s := newTestSuite(t) 375 defer s.finish() 376 377 var ( 378 committedVersion = 22 379 ) 380 381 // KV service returns later version 382 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion+1, 383 &changesettest.Config{ 384 Text: "shoop\nwoop\nhoop", 385 }), nil) 386 387 // Commit should fail 388 err := s.mgr.Commit(committedVersion, commit) 389 require.Equal(t, ErrAlreadyCommitted, err) 390 } 391 392 func TestManagerCommit_ConfigUnmarshalError(t *testing.T) { 393 s := newTestSuite(t) 394 defer s.finish() 395 396 var ( 397 committedVersion = 22 398 ) 399 400 // KV service returns invalid data 401 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValueWithData(committedVersion+1, []byte("foo")), nil) 402 403 // Commit should fail 404 err := s.mgr.Commit(committedVersion, commit) 405 require.Error(t, err) 406 } 407 408 func TestManagerCommit_ChangeSetClosing(t *testing.T) { 409 s := newTestSuite(t) 410 defer s.finish() 411 412 var ( 413 config1 = new(configMatcher) 414 415 committedVersion = 22 416 changeSetVersion = 17 417 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 418 ) 419 420 gomock.InOrder( 421 // Retrieve the config value 422 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 423 &changesettest.Config{ 424 Text: "shoop\nwoop\nhoop", 425 }), nil), 426 427 // Retrieve the change set 428 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 429 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_CLOSED, &changesettest.Changes{ 430 Lines: []string{"foo", "bar"}, 431 })), nil), 432 433 // NB(mmihic): Don't re-update the change set 434 435 // Update the transformed config 436 s.kv.EXPECT().CheckAndSet(s.configKey, committedVersion, config1). 437 Return(committedVersion+1, nil), 438 ) 439 440 err := s.mgr.Commit(committedVersion, commit) 441 require.NoError(t, err) 442 } 443 444 func TestManagerCommit_ChangeSetVersionMismatchMarkingAsClosed(t *testing.T) { 445 s := newTestSuite(t) 446 defer s.finish() 447 448 var ( 449 committedVersion = 22 450 changeSetVersion = 17 451 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 452 ) 453 454 gomock.InOrder( 455 // Retrieve the config value 456 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 457 &changesettest.Config{ 458 Text: "shoop\nwoop\nhoop", 459 }), nil), 460 461 // Retrieve the change set 462 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 463 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_OPEN, &changesettest.Changes{ 464 Lines: []string{"foo", "bar"}, 465 })), nil), 466 467 // Mark as closed - fail with version mismatch 468 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, gomock.Any()). 469 Return(0, kv.ErrVersionMismatch), 470 ) 471 472 err := s.mgr.Commit(committedVersion, commit) 473 require.Equal(t, ErrCommitInProgress, err) 474 } 475 476 func TestManagerCommit_ChangeSetErrorMarkingAsClosed(t *testing.T) { 477 s := newTestSuite(t) 478 defer s.finish() 479 480 var ( 481 committedVersion = 22 482 changeSetVersion = 17 483 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 484 ) 485 486 gomock.InOrder( 487 // Retrieve the config value 488 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 489 &changesettest.Config{ 490 Text: "shoop\nwoop\nhoop", 491 }), nil), 492 493 // Retrieve the change set 494 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 495 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_OPEN, &changesettest.Changes{ 496 Lines: []string{"foo", "bar"}, 497 })), nil), 498 499 // Mark as committing - fail with version mismatch 500 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, gomock.Any()). 501 Return(0, errBadThingsHappened), 502 ) 503 504 err := s.mgr.Commit(committedVersion, commit) 505 require.Equal(t, errBadThingsHappened, err) 506 } 507 508 func TestManagerCommit_CommitFunctionError(t *testing.T) { 509 s := newTestSuite(t) 510 defer s.finish() 511 512 var ( 513 committedVersion = 22 514 changeSetVersion = 17 515 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 516 ) 517 518 gomock.InOrder( 519 // Retrieve the config value 520 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 521 &changesettest.Config{ 522 Text: "shoop\nwoop\nhoop", 523 }), nil), 524 525 // Retrieve the change set 526 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 527 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_OPEN, &changesettest.Changes{ 528 Lines: []string{"foo", "bar"}, 529 })), nil), 530 531 // Mark as closed 532 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, gomock.Any()). 533 Return(changeSetVersion+1, nil), 534 ) 535 536 err := s.mgr.Commit(committedVersion, func(cfg, changes proto.Message) error { 537 return errBadThingsHappened 538 }) 539 require.Equal(t, errBadThingsHappened, err) 540 } 541 542 func TestManagerCommit_ConfigUpdateVersionMismatch(t *testing.T) { 543 s := newTestSuite(t) 544 defer s.finish() 545 546 var ( 547 committedVersion = 22 548 changeSetVersion = 17 549 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 550 ) 551 552 gomock.InOrder( 553 // Retrieve the config value 554 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 555 &changesettest.Config{ 556 Text: "shoop\nwoop\nhoop", 557 }), nil), 558 559 // Retrieve the change set 560 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 561 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_OPEN, &changesettest.Changes{ 562 Lines: []string{"foo", "bar"}, 563 })), nil), 564 565 // Mark as closed 566 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, gomock.Any()). 567 Return(changeSetVersion+1, nil), 568 569 // Update with new config - FAIL 570 s.kv.EXPECT().CheckAndSet(s.configKey, committedVersion, gomock.Any()). 571 Return(0, kv.ErrVersionMismatch), 572 ) 573 574 err := s.mgr.Commit(committedVersion, commit) 575 require.Equal(t, ErrAlreadyCommitted, err) 576 } 577 578 func TestManagerCommit_ConfigUpdateError(t *testing.T) { 579 s := newTestSuite(t) 580 defer s.finish() 581 582 var ( 583 committedVersion = 22 584 changeSetVersion = 17 585 changeSetKey = fmtChangeSetKey(s.configKey, committedVersion) 586 ) 587 588 gomock.InOrder( 589 // Retrieve the config value 590 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(committedVersion, 591 &changesettest.Config{ 592 Text: "shoop\nwoop\nhoop", 593 }), nil), 594 595 // Retrieve the change set 596 s.kv.EXPECT().Get(changeSetKey).Return(mem.NewValue(changeSetVersion, 597 s.newChangeSet(committedVersion, changesetpb.ChangeSetState_OPEN, &changesettest.Changes{ 598 Lines: []string{"foo", "bar"}, 599 })), nil), 600 601 // Mark as closed 602 s.kv.EXPECT().CheckAndSet(changeSetKey, changeSetVersion, gomock.Any()). 603 Return(changeSetVersion+1, nil), 604 605 // Update with new config - FAIL 606 s.kv.EXPECT().CheckAndSet(s.configKey, committedVersion, gomock.Any()). 607 Return(0, errBadThingsHappened), 608 ) 609 610 err := s.mgr.Commit(committedVersion, commit) 611 require.Equal(t, errBadThingsHappened, err) 612 } 613 614 func TestManager_GetPendingChangesSuccess(t *testing.T) { 615 s := newTestSuite(t) 616 defer s.finish() 617 618 config := &changesettest.Config{ 619 Text: "foo\nbar\n", 620 } 621 changes := &changesettest.Changes{ 622 Lines: []string{"zed", "brack"}, 623 } 624 625 gomock.InOrder( 626 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(13, config), nil), 627 s.kv.EXPECT().Get(fmtChangeSetKey(s.configKey, 13)).Return(mem.NewValue(24, &changesetpb.ChangeSet{ 628 ForVersion: 13, 629 State: changesetpb.ChangeSetState_OPEN, 630 Changes: s.marshal(changes), 631 }), nil), 632 ) 633 634 vers, returnedConfig, returnedChanges, err := s.mgr.GetPendingChanges() 635 require.NoError(t, err) 636 require.Equal(t, 13, vers) 637 require.Equal(t, *config, *(returnedConfig.(*changesettest.Config))) 638 require.Equal(t, *changes, *(returnedChanges.(*changesettest.Changes))) 639 } 640 641 func TestManager_GetPendingChangesGetConfigError(t *testing.T) { 642 s := newTestSuite(t) 643 defer s.finish() 644 645 gomock.InOrder( 646 s.kv.EXPECT().Get(s.configKey).Return(nil, errBadThingsHappened), 647 ) 648 649 vers, returnedConfig, returnedChanges, err := s.mgr.GetPendingChanges() 650 require.Equal(t, errBadThingsHappened, err) 651 require.Equal(t, 0, vers) 652 require.Nil(t, returnedConfig) 653 require.Nil(t, returnedChanges) 654 } 655 656 func TestManager_GetPendingChangesConfigUnmarshalError(t *testing.T) { 657 s := newTestSuite(t) 658 defer s.finish() 659 660 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValueWithData(13, []byte("foo")), nil) 661 662 _, _, _, err := s.mgr.GetPendingChanges() 663 require.Error(t, err) 664 } 665 666 func TestManager_GetPendingChangesGetChangeSetError(t *testing.T) { 667 s := newTestSuite(t) 668 defer s.finish() 669 670 config := &changesettest.Config{ 671 Text: "foo\nbar\n", 672 } 673 674 gomock.InOrder( 675 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(13, config), nil), 676 s.kv.EXPECT().Get(fmtChangeSetKey(s.configKey, 13)).Return(nil, errBadThingsHappened), 677 ) 678 679 vers, returnedConfig, returnedChanges, err := s.mgr.GetPendingChanges() 680 require.Equal(t, errBadThingsHappened, err) 681 require.Equal(t, 0, vers) 682 require.Nil(t, returnedConfig) 683 require.Nil(t, returnedChanges) 684 } 685 686 func TestManager_GetPendingChangesChangeSetNotFound(t *testing.T) { 687 s := newTestSuite(t) 688 defer s.finish() 689 690 config := &changesettest.Config{ 691 Text: "foo\nbar\n", 692 } 693 694 gomock.InOrder( 695 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(13, config), nil), 696 s.kv.EXPECT().Get(fmtChangeSetKey(s.configKey, 13)).Return(nil, kv.ErrNotFound), 697 ) 698 699 vers, returnedConfig, returnedChanges, err := s.mgr.GetPendingChanges() 700 require.NoError(t, err) 701 require.Equal(t, 13, vers) 702 require.Equal(t, *config, *(returnedConfig.(*changesettest.Config))) 703 require.Nil(t, returnedChanges) 704 } 705 706 func TestManager_GetPendingChangesChangeSetUnmarshalError(t *testing.T) { 707 s := newTestSuite(t) 708 defer s.finish() 709 710 config := &changesettest.Config{ 711 Text: "foo\nbar\n", 712 } 713 714 gomock.InOrder( 715 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(13, config), nil), 716 s.kv.EXPECT().Get(fmtChangeSetKey(s.configKey, 13)). 717 Return(mem.NewValueWithData(24, []byte("foo")), nil), 718 ) 719 720 _, _, _, err := s.mgr.GetPendingChanges() 721 require.Error(t, err) 722 } 723 724 func TestManager_GetPendingChangesChangeUnmarshalError(t *testing.T) { 725 s := newTestSuite(t) 726 defer s.finish() 727 728 config := &changesettest.Config{ 729 Text: "foo\nbar\n", 730 } 731 732 gomock.InOrder( 733 s.kv.EXPECT().Get(s.configKey).Return(mem.NewValue(13, config), nil), 734 s.kv.EXPECT().Get(fmtChangeSetKey(s.configKey, 13)).Return(mem.NewValue(24, &changesetpb.ChangeSet{ 735 ForVersion: 13, 736 State: changesetpb.ChangeSetState_OPEN, 737 Changes: []byte("foo"), // invalid proto buf 738 }), nil), 739 ) 740 741 _, _, _, err := s.mgr.GetPendingChanges() 742 require.Error(t, err) 743 } 744 745 func TestManagerOptions_Validate(t *testing.T) { 746 tests := []struct { 747 err error 748 opts ManagerOptions 749 }{ 750 {errConfigKeyNotSet, NewManagerOptions(). 751 SetConfigType(&changesettest.Config{}). 752 SetChangesType(&changesettest.Changes{}). 753 SetKV(mem.NewStore())}, 754 755 {errConfigTypeNotSet, NewManagerOptions(). 756 SetConfigKey("foozle"). 757 SetChangesType(&changesettest.Changes{}). 758 SetKV(mem.NewStore())}, 759 760 {errChangeTypeNotSet, NewManagerOptions(). 761 SetConfigKey("bazzle"). 762 SetConfigType(&changesettest.Config{}). 763 SetKV(mem.NewStore())}, 764 765 {errKVNotSet, NewManagerOptions(). 766 SetConfigKey("muzzle"). 767 SetConfigType(&changesettest.Config{}). 768 SetChangesType(&changesettest.Changes{})}, 769 } 770 771 for _, test := range tests { 772 require.Equal(t, test.err, test.opts.Validate()) 773 } 774 } 775 776 type configMatcher struct { 777 CapturingProtoMatcher 778 } 779 780 func (m *configMatcher) config() *changesettest.Config { 781 return m.Arg.(*changesettest.Config) 782 } 783 784 type changeSetMatcher struct { 785 CapturingProtoMatcher 786 } 787 788 func (m *changeSetMatcher) changeset() *changesetpb.ChangeSet { 789 return m.Arg.(*changesetpb.ChangeSet) 790 } 791 792 func (m *changeSetMatcher) changes(t *testing.T) *changesettest.Changes { 793 changes := new(changesettest.Changes) 794 require.NoError(t, proto.Unmarshal(m.changeset().Changes, changes)) 795 return changes 796 } 797 798 func addLines(lines ...string) ChangeFn { 799 return func(cfgProto, changesProto proto.Message) error { 800 changes := changesProto.(*changesettest.Changes) 801 changes.Lines = append(changes.Lines, lines...) 802 return nil 803 } 804 } 805 806 func commit(cfgProto, changesProto proto.Message) error { 807 changes := changesProto.(*changesettest.Changes) 808 config := cfgProto.(*changesettest.Config) 809 if config.Text != "" { 810 config.Text = config.Text + "\n" 811 } 812 config.Text = config.Text + strings.Join(changes.Lines, "\n") 813 return nil 814 } 815 816 type testSuite struct { 817 t *testing.T 818 kv *kv.MockStore 819 mc *gomock.Controller 820 mgr Manager 821 configKey string 822 } 823 824 func newTestSuite(t *testing.T) *testSuite { 825 mc := gomock.NewController(t) 826 kvStore := kv.NewMockStore(mc) 827 configKey := "config" 828 mgr, err := NewManager(NewManagerOptions(). 829 SetKV(kvStore). 830 SetConfigType(&changesettest.Config{}). 831 SetChangesType(&changesettest.Changes{}). 832 SetConfigKey(configKey)) 833 834 require.NoError(t, err) 835 836 return &testSuite{ 837 t: t, 838 mc: mc, 839 kv: kvStore, 840 configKey: configKey, 841 mgr: mgr, 842 } 843 } 844 845 func (t *testSuite) finish() { 846 t.mc.Finish() 847 } 848 849 func (t *testSuite) marshal(msg proto.Message) []byte { 850 b, err := proto.Marshal(msg) 851 require.NoError(t.t, err) 852 return b 853 } 854 855 func (t *testSuite) newOpenChangeSet(forVersion int, changes proto.Message) *changesetpb.ChangeSet { 856 return t.newChangeSet(forVersion, changesetpb.ChangeSetState_OPEN, changes) 857 } 858 859 func (t *testSuite) newChangeSet(forVersion int, state changesetpb.ChangeSetState, changes proto.Message, 860 ) *changesetpb.ChangeSet { 861 changeSet := &changesetpb.ChangeSet{ 862 ForVersion: int32(forVersion), 863 State: state, 864 } 865 866 cbytes, err := proto.Marshal(changes) 867 require.NoError(t.t, err) 868 changeSet.Changes = cbytes 869 return changeSet 870 } 871 872 type CapturingProtoMatcher struct { 873 Arg proto.Message 874 } 875 876 func (m *CapturingProtoMatcher) Matches(arg interface{}) bool { 877 msg, ok := arg.(proto.Message) 878 if !ok { 879 return false 880 } 881 882 m.Arg = proto.Clone(msg) 883 return true 884 } 885 886 func (m *CapturingProtoMatcher) String() string { 887 return "proto-capture" 888 }