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  }