agones.dev/agones@v1.53.0/pkg/sdkserver/localsdk_test.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package sdkserver
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"os"
    22  	"strconv"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/pkg/errors"
    29  	"github.com/stretchr/testify/assert"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  	"google.golang.org/protobuf/types/known/fieldmaskpb"
    32  	"google.golang.org/protobuf/types/known/wrapperspb"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/util/wait"
    35  
    36  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    37  	"agones.dev/agones/pkg/sdk"
    38  	"agones.dev/agones/pkg/sdk/alpha"
    39  	"agones.dev/agones/pkg/sdk/beta"
    40  	"agones.dev/agones/pkg/util/runtime"
    41  )
    42  
    43  func TestLocal(t *testing.T) {
    44  	ctx := context.Background()
    45  	e := &sdk.Empty{}
    46  	l, err := NewLocalSDKServer("", "")
    47  	assert.Nil(t, err)
    48  
    49  	_, err = l.Ready(ctx, e)
    50  	assert.Nil(t, err, "Ready should not error")
    51  
    52  	_, err = l.Shutdown(ctx, e)
    53  	assert.Nil(t, err, "Shutdown should not error")
    54  
    55  	wg := sync.WaitGroup{}
    56  	wg.Add(1)
    57  	stream := newEmptyMockStream()
    58  
    59  	go func() {
    60  		err = l.Health(stream)
    61  		assert.Nil(t, err)
    62  		wg.Done()
    63  	}()
    64  
    65  	stream.msgs <- &sdk.Empty{}
    66  	close(stream.msgs)
    67  
    68  	wg.Wait()
    69  
    70  	gs, err := l.GetGameServer(ctx, e)
    71  	assert.Nil(t, err)
    72  
    73  	defaultGameServer := defaultGs()
    74  	// do this to adjust for any time differences.
    75  	// we only care about all the other values to be compared.
    76  	defaultGameServer.ObjectMeta.CreationTimestamp = gs.GetObjectMeta().CreationTimestamp
    77  
    78  	assert.Equal(t, defaultGameServer.GetObjectMeta(), gs.GetObjectMeta())
    79  	assert.Equal(t, defaultGameServer.GetSpec(), gs.GetSpec())
    80  	gsStatus := defaultGameServer.GetStatus()
    81  	gsStatus.State = "Shutdown"
    82  	assert.Equal(t, gsStatus, gs.GetStatus())
    83  }
    84  
    85  func TestLocalSDKWithTestMode(t *testing.T) {
    86  	l, err := NewLocalSDKServer("", "")
    87  	assert.NoError(t, err, "Should be able to create local SDK server")
    88  	a := []string{"ready", "allocate", "setlabel", "setannotation", "gameserver", "health", "shutdown", "watch"}
    89  	b := []string{"ready", "health", "ready", "watch", "allocate", "gameserver", "setlabel", "setannotation", "health", "health", "shutdown"}
    90  	assert.True(t, l.EqualSets(a, a))
    91  	assert.True(t, l.EqualSets(a, b))
    92  	assert.True(t, l.EqualSets(b, a))
    93  	assert.True(t, l.EqualSets(b, b))
    94  	a[0] = "rady"
    95  	assert.False(t, l.EqualSets(a, b))
    96  	assert.False(t, l.EqualSets(b, a))
    97  	a[0] = "ready"
    98  	b[1] = "halth"
    99  	assert.False(t, l.EqualSets(a, b))
   100  	assert.False(t, l.EqualSets(b, a))
   101  }
   102  
   103  func TestLocalSDKWithGameServer(t *testing.T) {
   104  	ctx := context.Background()
   105  	e := &sdk.Empty{}
   106  
   107  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "stuff"}}
   108  	path, err := gsToTmpFile(fixture.DeepCopy())
   109  	assert.Nil(t, err)
   110  
   111  	l, err := NewLocalSDKServer(path, "")
   112  	assert.Nil(t, err)
   113  
   114  	gs, err := l.GetGameServer(ctx, e)
   115  	assert.Nil(t, err)
   116  
   117  	assert.Equal(t, fixture.ObjectMeta.Name, gs.ObjectMeta.Name)
   118  }
   119  
   120  // nolint:dupl
   121  func TestLocalSDKWithLogLevel(t *testing.T) {
   122  	ctx := context.Background()
   123  	e := &sdk.Empty{}
   124  
   125  	fixture := &agonesv1.GameServer{
   126  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   127  		Spec: agonesv1.GameServerSpec{
   128  			SdkServer: agonesv1.SdkServer{LogLevel: "debug"},
   129  		},
   130  	}
   131  	path, err := gsToTmpFile(fixture.DeepCopy())
   132  	assert.Nil(t, err)
   133  
   134  	l, err := NewLocalSDKServer(path, "test")
   135  	assert.Nil(t, err)
   136  
   137  	_, err = l.GetGameServer(ctx, e)
   138  	assert.Nil(t, err)
   139  
   140  	// Check if the LocalSDKServer's logger.LogLevel equal fixture's
   141  	assert.Equal(t, string(fixture.Spec.SdkServer.LogLevel), l.logger.Logger.Level.String())
   142  }
   143  
   144  // nolint:dupl
   145  func TestLocalSDKServerSetLabel(t *testing.T) {
   146  	t.Parallel()
   147  
   148  	fixtures := map[string]struct {
   149  		gs *agonesv1.GameServer
   150  	}{
   151  		"default": {
   152  			gs: nil,
   153  		},
   154  		"no labels": {
   155  			gs: &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "empty"}},
   156  		},
   157  		"empty": {
   158  			gs: &agonesv1.GameServer{},
   159  		},
   160  	}
   161  
   162  	for k, v := range fixtures {
   163  		t.Run(k, func(t *testing.T) {
   164  			ctx := context.Background()
   165  			e := &sdk.Empty{}
   166  			path, err := gsToTmpFile(v.gs)
   167  			assert.Nil(t, err)
   168  
   169  			l, err := NewLocalSDKServer(path, "")
   170  			assert.Nil(t, err)
   171  			kv := &sdk.KeyValue{Key: "foo", Value: "bar"}
   172  
   173  			stream := newGameServerMockStream()
   174  			wg := sync.WaitGroup{}
   175  			wg.Add(1)
   176  			go func() {
   177  				defer wg.Done()
   178  				err := l.WatchGameServer(e, stream)
   179  				assert.Nil(t, err)
   180  			}()
   181  			assertInitialWatchUpdate(t, stream)
   182  
   183  			// make sure length of l.updateObservers is at least 1
   184  			err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   185  				ret := false
   186  				l.updateObservers.Range(func(_, _ interface{}) bool {
   187  					ret = true
   188  					return false
   189  				})
   190  
   191  				return ret, nil
   192  			})
   193  			assert.Nil(t, err)
   194  
   195  			_, err = l.SetLabel(ctx, kv)
   196  			assert.Nil(t, err)
   197  
   198  			gs, err := l.GetGameServer(ctx, e)
   199  			assert.Nil(t, err)
   200  			assert.Equal(t, gs.ObjectMeta.Labels[metadataPrefix+"foo"], "bar")
   201  
   202  			assertWatchUpdate(t, stream, "bar", func(gs *sdk.GameServer) interface{} {
   203  				return gs.ObjectMeta.Labels[metadataPrefix+"foo"]
   204  			})
   205  
   206  			l.Close()
   207  			wg.Wait()
   208  		})
   209  	}
   210  }
   211  
   212  // nolint:dupl
   213  func TestLocalSDKServerSetAnnotation(t *testing.T) {
   214  	t.Parallel()
   215  
   216  	fixtures := map[string]struct {
   217  		gs *agonesv1.GameServer
   218  	}{
   219  		"default": {
   220  			gs: nil,
   221  		},
   222  		"no annotation": {
   223  			gs: &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "empty"}},
   224  		},
   225  		"empty": {
   226  			gs: &agonesv1.GameServer{},
   227  		},
   228  	}
   229  
   230  	for k, v := range fixtures {
   231  		t.Run(k, func(t *testing.T) {
   232  			ctx := context.Background()
   233  			e := &sdk.Empty{}
   234  			path, err := gsToTmpFile(v.gs)
   235  			assert.Nil(t, err)
   236  
   237  			l, err := NewLocalSDKServer(path, "")
   238  			assert.Nil(t, err)
   239  
   240  			kv := &sdk.KeyValue{Key: "bar", Value: "foo"}
   241  
   242  			stream := newGameServerMockStream()
   243  			wg := sync.WaitGroup{}
   244  			wg.Add(1)
   245  			go func() {
   246  				defer wg.Done()
   247  				err := l.WatchGameServer(e, stream)
   248  				assert.Nil(t, err)
   249  			}()
   250  			assertInitialWatchUpdate(t, stream)
   251  
   252  			// make sure length of l.updateObservers is at least 1
   253  			err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   254  				ret := false
   255  				l.updateObservers.Range(func(_, _ interface{}) bool {
   256  					ret = true
   257  					return false
   258  				})
   259  
   260  				return ret, nil
   261  			})
   262  			assert.Nil(t, err)
   263  
   264  			_, err = l.SetAnnotation(ctx, kv)
   265  			assert.Nil(t, err)
   266  
   267  			gs, err := l.GetGameServer(ctx, e)
   268  			assert.Nil(t, err)
   269  			assert.Equal(t, gs.ObjectMeta.Annotations[metadataPrefix+"bar"], "foo")
   270  
   271  			assertWatchUpdate(t, stream, "foo", func(gs *sdk.GameServer) interface{} {
   272  				return gs.ObjectMeta.Annotations[metadataPrefix+"bar"]
   273  			})
   274  
   275  			l.Close()
   276  			wg.Wait()
   277  		})
   278  	}
   279  }
   280  
   281  func TestLocalSDKServerWatchGameServer(t *testing.T) {
   282  	t.Parallel()
   283  
   284  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "stuff"}}
   285  	path, err := gsToTmpFile(fixture)
   286  	assert.Nil(t, err)
   287  
   288  	e := &sdk.Empty{}
   289  	l, err := NewLocalSDKServer(path, "")
   290  	assert.Nil(t, err)
   291  
   292  	stream := newGameServerMockStream()
   293  	go func() {
   294  		err := l.WatchGameServer(e, stream)
   295  		assert.Nil(t, err)
   296  	}()
   297  	assertInitialWatchUpdate(t, stream)
   298  
   299  	// wait for watching to begin
   300  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   301  		found := false
   302  		l.updateObservers.Range(func(_, _ interface{}) bool {
   303  			found = true
   304  			return false
   305  		})
   306  		return found, nil
   307  	})
   308  	assert.NoError(t, err)
   309  
   310  	assertNoWatchUpdate(t, stream)
   311  	fixture.ObjectMeta.Annotations = map[string]string{"foo": "bar"}
   312  	j, err := json.Marshal(fixture)
   313  	assert.Nil(t, err)
   314  
   315  	err = os.WriteFile(path, j, os.ModeDevice)
   316  	assert.Nil(t, err)
   317  
   318  	assertWatchUpdate(t, stream, "bar", func(gs *sdk.GameServer) interface{} {
   319  		return gs.ObjectMeta.Annotations["foo"]
   320  	})
   321  }
   322  
   323  func TestLocalSDKServerPlayerCapacity(t *testing.T) {
   324  	t.Parallel()
   325  
   326  	runtime.FeatureTestMutex.Lock()
   327  	defer runtime.FeatureTestMutex.Unlock()
   328  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeaturePlayerTracking)+"=true"))
   329  
   330  	fixture := &agonesv1.GameServer{ObjectMeta: metav1.ObjectMeta{Name: "stuff"}}
   331  
   332  	e := &alpha.Empty{}
   333  	path, err := gsToTmpFile(fixture)
   334  	assert.NoError(t, err)
   335  	l, err := NewLocalSDKServer(path, "")
   336  	assert.Nil(t, err)
   337  
   338  	stream := newGameServerMockStream()
   339  	go func() {
   340  		err := l.WatchGameServer(&sdk.Empty{}, stream)
   341  		assert.Nil(t, err)
   342  	}()
   343  	assertInitialWatchUpdate(t, stream)
   344  
   345  	// wait for watching to begin
   346  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   347  		found := false
   348  		l.updateObservers.Range(func(_, _ interface{}) bool {
   349  			found = true
   350  			return false
   351  		})
   352  		return found, nil
   353  	})
   354  	assert.NoError(t, err)
   355  
   356  	c, err := l.GetPlayerCapacity(context.Background(), e)
   357  	assert.NoError(t, err)
   358  	assert.Equal(t, int64(0), c.Count)
   359  
   360  	_, err = l.SetPlayerCapacity(context.Background(), &alpha.Count{Count: 10})
   361  	assert.NoError(t, err)
   362  
   363  	select {
   364  	case msg := <-stream.msgs:
   365  		assert.Equal(t, int64(10), msg.Status.Players.Capacity)
   366  	case <-time.After(10 * time.Second):
   367  		assert.Fail(t, "timeout getting watch")
   368  	}
   369  
   370  	c, err = l.GetPlayerCapacity(context.Background(), e)
   371  	assert.NoError(t, err)
   372  	assert.Equal(t, int64(10), c.Count)
   373  
   374  	gs, err := l.GetGameServer(context.Background(), &sdk.Empty{})
   375  	assert.NoError(t, err)
   376  	assert.Equal(t, int64(10), gs.Status.Players.Capacity)
   377  }
   378  
   379  func TestLocalSDKServerPlayerConnectAndDisconnectWithoutPlayerTracking(t *testing.T) {
   380  	t.Parallel()
   381  	runtime.FeatureTestMutex.Lock()
   382  	defer runtime.FeatureTestMutex.Unlock()
   383  
   384  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeaturePlayerTracking)+"=false"))
   385  
   386  	l, err := NewLocalSDKServer("", "")
   387  	assert.Nil(t, err)
   388  
   389  	e := &alpha.Empty{}
   390  	capacity, err := l.GetPlayerCapacity(context.Background(), e)
   391  	assert.Nil(t, capacity)
   392  	assert.Error(t, err)
   393  
   394  	count, err := l.GetPlayerCount(context.Background(), e)
   395  	assert.Error(t, err)
   396  	assert.Nil(t, count)
   397  
   398  	list, err := l.GetConnectedPlayers(context.Background(), e)
   399  	assert.Error(t, err)
   400  	assert.Nil(t, list)
   401  
   402  	id := &alpha.PlayerID{PlayerID: "test-player"}
   403  
   404  	ok, err := l.PlayerConnect(context.Background(), id)
   405  	assert.Error(t, err)
   406  	assert.False(t, ok.Bool)
   407  
   408  	ok, err = l.IsPlayerConnected(context.Background(), id)
   409  	assert.Error(t, err)
   410  	assert.False(t, ok.Bool)
   411  
   412  	ok, err = l.PlayerDisconnect(context.Background(), id)
   413  	assert.Error(t, err)
   414  	assert.False(t, ok.Bool)
   415  }
   416  
   417  func TestLocalSDKServerPlayerConnectAndDisconnect(t *testing.T) {
   418  	t.Parallel()
   419  
   420  	runtime.FeatureTestMutex.Lock()
   421  	defer runtime.FeatureTestMutex.Unlock()
   422  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeaturePlayerTracking)+"=true"))
   423  
   424  	gs := func() *agonesv1.GameServer {
   425  		return &agonesv1.GameServer{
   426  			ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   427  			Status: agonesv1.GameServerStatus{
   428  				Players: &agonesv1.PlayerStatus{
   429  					Capacity: 1,
   430  				},
   431  			}}
   432  	}
   433  
   434  	e := &alpha.Empty{}
   435  
   436  	fixtures := map[string]struct {
   437  		testMode bool
   438  		gs       *agonesv1.GameServer
   439  		useFile  bool
   440  	}{
   441  		"test mode on, gs with Status.Players": {
   442  			testMode: true,
   443  			gs:       gs(),
   444  			useFile:  true,
   445  		},
   446  		"test mode off, gs with Status.Players": {
   447  			testMode: false,
   448  			gs:       gs(),
   449  			useFile:  true,
   450  		},
   451  		"test mode on, gs without Status.Players": {
   452  			testMode: true,
   453  			useFile:  true,
   454  		},
   455  		"test mode off, gs without Status.Players": {
   456  			testMode: false,
   457  			useFile:  true,
   458  		},
   459  		"test mode on, no filePath": {
   460  			testMode: true,
   461  			useFile:  false,
   462  		},
   463  		"test mode off, no filePath": {
   464  			testMode: false,
   465  			useFile:  false,
   466  		},
   467  	}
   468  
   469  	for k, v := range fixtures {
   470  		t.Run(k, func(t *testing.T) {
   471  			var l *LocalSDKServer
   472  			var err error
   473  			if v.useFile {
   474  				path, pathErr := gsToTmpFile(v.gs)
   475  				assert.NoError(t, pathErr)
   476  				l, err = NewLocalSDKServer(path, "")
   477  			} else {
   478  				l, err = NewLocalSDKServer("", "")
   479  			}
   480  			assert.Nil(t, err)
   481  			l.SetTestMode(v.testMode)
   482  
   483  			stream := newGameServerMockStream()
   484  			go func() {
   485  				err := l.WatchGameServer(&sdk.Empty{}, stream)
   486  				assert.Nil(t, err)
   487  			}()
   488  			assertInitialWatchUpdate(t, stream)
   489  
   490  			// wait for watching to begin
   491  			err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   492  				found := false
   493  				l.updateObservers.Range(func(_, _ interface{}) bool {
   494  					found = true
   495  					return false
   496  				})
   497  				return found, nil
   498  			})
   499  			assert.NoError(t, err)
   500  
   501  			if !v.useFile || v.gs == nil {
   502  				_, err := l.SetPlayerCapacity(context.Background(), &alpha.Count{
   503  					Count: 1,
   504  				})
   505  				assert.NoError(t, err)
   506  				expected := &sdk.GameServer_Status_PlayerStatus{
   507  					Capacity: 1,
   508  				}
   509  				assertWatchUpdate(t, stream, expected, func(gs *sdk.GameServer) interface{} {
   510  					return gs.Status.Players
   511  				})
   512  			}
   513  
   514  			id := &alpha.PlayerID{PlayerID: "one"}
   515  			ok, err := l.IsPlayerConnected(context.Background(), id)
   516  			assert.NoError(t, err)
   517  			if assert.NotNil(t, ok) {
   518  				assert.False(t, ok.Bool, "player should not be connected")
   519  			}
   520  
   521  			count, err := l.GetPlayerCount(context.Background(), e)
   522  			assert.NoError(t, err)
   523  			assert.Equal(t, int64(0), count.Count)
   524  
   525  			list, err := l.GetConnectedPlayers(context.Background(), e)
   526  			assert.NoError(t, err)
   527  			assert.Empty(t, list.List)
   528  
   529  			// connect a player
   530  			ok, err = l.PlayerConnect(context.Background(), id)
   531  			assert.NoError(t, err)
   532  			assert.True(t, ok.Bool, "Player should not exist yet")
   533  
   534  			count, err = l.GetPlayerCount(context.Background(), e)
   535  			assert.NoError(t, err)
   536  			assert.Equal(t, int64(1), count.Count)
   537  
   538  			expected := &sdk.GameServer_Status_PlayerStatus{
   539  				Count:    1,
   540  				Capacity: 1,
   541  				Ids:      []string{id.PlayerID},
   542  			}
   543  			assertWatchUpdate(t, stream, expected, func(gs *sdk.GameServer) interface{} {
   544  				return gs.Status.Players
   545  			})
   546  
   547  			ok, err = l.IsPlayerConnected(context.Background(), id)
   548  			assert.NoError(t, err)
   549  			assert.True(t, ok.Bool, "player should be connected")
   550  
   551  			list, err = l.GetConnectedPlayers(context.Background(), e)
   552  			assert.NoError(t, err)
   553  			assert.Equal(t, []string{id.PlayerID}, list.List)
   554  
   555  			// add same player
   556  			ok, err = l.PlayerConnect(context.Background(), id)
   557  			assert.NoError(t, err)
   558  			assert.False(t, ok.Bool, "Player already exists")
   559  
   560  			count, err = l.GetPlayerCount(context.Background(), e)
   561  			assert.NoError(t, err)
   562  			assert.Equal(t, int64(1), count.Count)
   563  			assertNoWatchUpdate(t, stream)
   564  
   565  			list, err = l.GetConnectedPlayers(context.Background(), e)
   566  			assert.NoError(t, err)
   567  			assert.Equal(t, []string{id.PlayerID}, list.List)
   568  
   569  			// should return an error if we try to add another, since we're at capacity
   570  			nopePlayer := &alpha.PlayerID{PlayerID: "nope"}
   571  			_, err = l.PlayerConnect(context.Background(), nopePlayer)
   572  			assert.EqualError(t, err, "Players are already at capacity")
   573  
   574  			ok, err = l.IsPlayerConnected(context.Background(), nopePlayer)
   575  			assert.NoError(t, err)
   576  			assert.False(t, ok.Bool)
   577  
   578  			// disconnect a player
   579  			ok, err = l.PlayerDisconnect(context.Background(), id)
   580  			assert.NoError(t, err)
   581  			assert.True(t, ok.Bool, "Player should be removed")
   582  			count, err = l.GetPlayerCount(context.Background(), e)
   583  			assert.NoError(t, err)
   584  			assert.Equal(t, int64(0), count.Count)
   585  
   586  			expected = &sdk.GameServer_Status_PlayerStatus{
   587  				Count:    0,
   588  				Capacity: 1,
   589  				Ids:      []string{},
   590  			}
   591  			assertWatchUpdate(t, stream, expected, func(gs *sdk.GameServer) interface{} {
   592  				return gs.Status.Players
   593  			})
   594  
   595  			list, err = l.GetConnectedPlayers(context.Background(), e)
   596  			assert.NoError(t, err)
   597  			assert.Empty(t, list.List)
   598  
   599  			// remove same player
   600  			ok, err = l.PlayerDisconnect(context.Background(), id)
   601  			assert.NoError(t, err)
   602  			assert.False(t, ok.Bool, "Player already be gone")
   603  			count, err = l.GetPlayerCount(context.Background(), e)
   604  			assert.NoError(t, err)
   605  			assert.Equal(t, int64(0), count.Count)
   606  			assertNoWatchUpdate(t, stream)
   607  		})
   608  	}
   609  }
   610  
   611  func TestLocalSDKServerGetCounter(t *testing.T) {
   612  	t.Parallel()
   613  
   614  	runtime.FeatureTestMutex.Lock()
   615  	defer runtime.FeatureTestMutex.Unlock()
   616  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
   617  
   618  	counters := map[string]agonesv1.CounterStatus{
   619  		"sessions": {Count: int64(1), Capacity: int64(100)},
   620  	}
   621  	fixture := &agonesv1.GameServer{
   622  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   623  		Status: agonesv1.GameServerStatus{
   624  			Counters: counters,
   625  		},
   626  	}
   627  
   628  	path, err := gsToTmpFile(fixture)
   629  	assert.NoError(t, err)
   630  	l, err := NewLocalSDKServer(path, "")
   631  	assert.Nil(t, err)
   632  
   633  	stream := newGameServerMockStream()
   634  	go func() {
   635  		err := l.WatchGameServer(&sdk.Empty{}, stream)
   636  		assert.Nil(t, err)
   637  	}()
   638  	assertInitialWatchUpdate(t, stream)
   639  
   640  	// wait for watching to begin
   641  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   642  		found := false
   643  		l.updateObservers.Range(func(_, _ interface{}) bool {
   644  			found = true
   645  			return false
   646  		})
   647  		return found, nil
   648  	})
   649  	assert.NoError(t, err)
   650  
   651  	testScenarios := map[string]struct {
   652  		name    string
   653  		want    *beta.Counter
   654  		wantErr error
   655  	}{
   656  		"Counter exists": {
   657  			name: "sessions",
   658  			want: &beta.Counter{Name: "sessions", Count: int64(1), Capacity: int64(100)},
   659  		},
   660  		"Counter does not exist": {
   661  			name:    "noName",
   662  			wantErr: errors.Errorf("not found. %s Counter not found", "noName"),
   663  		},
   664  	}
   665  
   666  	for testName, testScenario := range testScenarios {
   667  		t.Run(testName, func(t *testing.T) {
   668  			got, err := l.GetCounter(context.Background(), &beta.GetCounterRequest{Name: testScenario.name})
   669  			// Check tests expecting non-errors
   670  			if testScenario.want != nil {
   671  				assert.NoError(t, err)
   672  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
   673  					t.Errorf("Unexpected difference:\n%v", diff)
   674  				}
   675  			} else {
   676  				// Check tests expecting errors
   677  				assert.EqualError(t, err, testScenario.wantErr.Error())
   678  			}
   679  		})
   680  	}
   681  }
   682  
   683  func TestLocalSDKServerUpdateCounter(t *testing.T) {
   684  	t.Parallel()
   685  
   686  	runtime.FeatureTestMutex.Lock()
   687  	defer runtime.FeatureTestMutex.Unlock()
   688  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
   689  
   690  	counters := map[string]agonesv1.CounterStatus{
   691  		"sessions": {Count: 1, Capacity: 100},
   692  		"players":  {Count: 100, Capacity: 100},
   693  		"lobbies":  {Count: 0, Capacity: 0},
   694  		"games":    {Count: 5, Capacity: 10},
   695  		"npcs":     {Count: 6, Capacity: 10},
   696  	}
   697  	fixture := &agonesv1.GameServer{
   698  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   699  		Status: agonesv1.GameServerStatus{
   700  			Counters: counters,
   701  		},
   702  	}
   703  
   704  	path, err := gsToTmpFile(fixture)
   705  	assert.NoError(t, err)
   706  	l, err := NewLocalSDKServer(path, "")
   707  	assert.Nil(t, err)
   708  
   709  	stream := newGameServerMockStream()
   710  	go func() {
   711  		err := l.WatchGameServer(&sdk.Empty{}, stream)
   712  		assert.Nil(t, err)
   713  	}()
   714  	assertInitialWatchUpdate(t, stream)
   715  
   716  	// wait for watching to begin
   717  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   718  		found := false
   719  		l.updateObservers.Range(func(_, _ interface{}) bool {
   720  			found = true
   721  			return false
   722  		})
   723  		return found, nil
   724  	})
   725  	assert.NoError(t, err)
   726  
   727  	testScenarios := map[string]struct {
   728  		updateRequest *beta.UpdateCounterRequest
   729  		want          *beta.Counter
   730  		wantErr       error
   731  	}{
   732  		"Set Counter Capacity": {
   733  			updateRequest: &beta.UpdateCounterRequest{
   734  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   735  					Name:     "lobbies",
   736  					Capacity: wrapperspb.Int64(10),
   737  				}},
   738  			want: &beta.Counter{
   739  				Name: "lobbies", Count: 0, Capacity: 10,
   740  			},
   741  		},
   742  		"Set Counter Count": {
   743  			updateRequest: &beta.UpdateCounterRequest{
   744  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   745  					Name:  "npcs",
   746  					Count: wrapperspb.Int64(10),
   747  				}},
   748  			want: &beta.Counter{
   749  				Name: "npcs", Count: 10, Capacity: 10,
   750  			},
   751  		},
   752  		"Decrement Counter Count": {
   753  			updateRequest: &beta.UpdateCounterRequest{
   754  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   755  					Name:      "games",
   756  					CountDiff: -5,
   757  				}},
   758  			want: &beta.Counter{
   759  				Name: "games", Count: 0, Capacity: 10,
   760  			},
   761  		},
   762  		"Cannot Decrement Counter": {
   763  			updateRequest: &beta.UpdateCounterRequest{
   764  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   765  					Name:      "sessions",
   766  					CountDiff: -2,
   767  				}},
   768  			wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", -1, 100),
   769  		},
   770  		"Cannot Increment Counter": {
   771  			updateRequest: &beta.UpdateCounterRequest{
   772  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   773  					Name:      "players",
   774  					CountDiff: 1,
   775  				}},
   776  			wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", 101, 100),
   777  		},
   778  		"Counter does not exist": {
   779  			updateRequest: &beta.UpdateCounterRequest{
   780  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   781  					Name:      "dragons",
   782  					CountDiff: 1,
   783  				}},
   784  			wantErr: errors.Errorf("not found. %s Counter not found", "dragons"),
   785  		},
   786  		"request Counter is nil": {
   787  			updateRequest: &beta.UpdateCounterRequest{
   788  				CounterUpdateRequest: nil,
   789  			},
   790  			wantErr: errors.Errorf("invalid argument. CounterUpdateRequest cannot be nil"),
   791  		},
   792  		"capacity is less than zero": {
   793  			updateRequest: &beta.UpdateCounterRequest{
   794  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   795  					Name:     "lobbies",
   796  					Capacity: wrapperspb.Int64(-1),
   797  				}},
   798  			wantErr: errors.Errorf("out of range. Capacity must be greater than or equal to 0. Found Capacity: %d", -1),
   799  		},
   800  		"count is less than zero": {
   801  			updateRequest: &beta.UpdateCounterRequest{
   802  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   803  					Name:  "players",
   804  					Count: wrapperspb.Int64(-1),
   805  				}},
   806  			wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", -1, 100),
   807  		},
   808  		"count is greater than capacity": {
   809  			updateRequest: &beta.UpdateCounterRequest{
   810  				CounterUpdateRequest: &beta.CounterUpdateRequest{
   811  					Name:  "players",
   812  					Count: wrapperspb.Int64(101),
   813  				}},
   814  			wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", 101, 100),
   815  		},
   816  	}
   817  
   818  	for testName, testScenario := range testScenarios {
   819  		t.Run(testName, func(t *testing.T) {
   820  			got, err := l.UpdateCounter(context.Background(), testScenario.updateRequest)
   821  			// Check tests expecting non-errors
   822  			if testScenario.want != nil {
   823  				assert.NoError(t, err)
   824  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
   825  					t.Errorf("Unexpected difference:\n%v", diff)
   826  				}
   827  			} else {
   828  				// Check tests expecting errors
   829  				assert.ErrorContains(t, err, testScenario.wantErr.Error())
   830  			}
   831  		})
   832  	}
   833  }
   834  
   835  func TestLocalSDKServerGetList(t *testing.T) {
   836  	t.Parallel()
   837  
   838  	runtime.FeatureTestMutex.Lock()
   839  	defer runtime.FeatureTestMutex.Unlock()
   840  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
   841  
   842  	lists := map[string]agonesv1.ListStatus{
   843  		"games": {Capacity: int64(100), Values: []string{"game1", "game2"}},
   844  	}
   845  	fixture := &agonesv1.GameServer{
   846  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   847  		Status: agonesv1.GameServerStatus{
   848  			Lists: lists,
   849  		},
   850  	}
   851  
   852  	path, err := gsToTmpFile(fixture)
   853  	assert.NoError(t, err)
   854  	l, err := NewLocalSDKServer(path, "")
   855  	assert.Nil(t, err)
   856  
   857  	stream := newGameServerMockStream()
   858  	go func() {
   859  		err := l.WatchGameServer(&sdk.Empty{}, stream)
   860  		assert.Nil(t, err)
   861  	}()
   862  	assertInitialWatchUpdate(t, stream)
   863  
   864  	// wait for watching to begin
   865  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   866  		found := false
   867  		l.updateObservers.Range(func(_, _ interface{}) bool {
   868  			found = true
   869  			return false
   870  		})
   871  		return found, nil
   872  	})
   873  	assert.NoError(t, err)
   874  
   875  	testScenarios := map[string]struct {
   876  		name    string
   877  		want    *beta.List
   878  		wantErr error
   879  	}{
   880  		"List exists": {
   881  			name: "games",
   882  			want: &beta.List{Name: "games", Capacity: int64(100), Values: []string{"game1", "game2"}},
   883  		},
   884  		"List does not exist": {
   885  			name:    "noName",
   886  			wantErr: errors.Errorf("not found. %s List not found", "noName"),
   887  		},
   888  	}
   889  
   890  	for testName, testScenario := range testScenarios {
   891  		t.Run(testName, func(t *testing.T) {
   892  			got, err := l.GetList(context.Background(), &beta.GetListRequest{Name: testScenario.name})
   893  			// Check tests expecting non-errors
   894  			if testScenario.want != nil {
   895  				assert.NoError(t, err)
   896  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
   897  					t.Errorf("Unexpected difference:\n%v", diff)
   898  				}
   899  			} else {
   900  				// Check tests expecting errors
   901  				assert.EqualError(t, err, testScenario.wantErr.Error())
   902  			}
   903  		})
   904  	}
   905  }
   906  
   907  func TestLocalSDKServerUpdateList(t *testing.T) {
   908  	t.Parallel()
   909  
   910  	runtime.FeatureTestMutex.Lock()
   911  	defer runtime.FeatureTestMutex.Unlock()
   912  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
   913  
   914  	lists := map[string]agonesv1.ListStatus{
   915  		"games":    {Capacity: 100, Values: []string{"game1", "game2"}},
   916  		"unicorns": {Capacity: 1000, Values: []string{"unicorn1", "unicorn2"}},
   917  		"clients":  {Capacity: 10, Values: []string{}},
   918  		"assets":   {Capacity: 1, Values: []string{"asset1"}},
   919  		"models":   {Capacity: 11, Values: []string{"model1", "model2"}},
   920  	}
   921  	fixture := &agonesv1.GameServer{
   922  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
   923  		Status: agonesv1.GameServerStatus{
   924  			Lists: lists,
   925  		},
   926  	}
   927  
   928  	path, err := gsToTmpFile(fixture)
   929  	assert.NoError(t, err)
   930  	l, err := NewLocalSDKServer(path, "")
   931  	assert.Nil(t, err)
   932  
   933  	stream := newGameServerMockStream()
   934  	go func() {
   935  		err := l.WatchGameServer(&sdk.Empty{}, stream)
   936  		assert.Nil(t, err)
   937  	}()
   938  	assertInitialWatchUpdate(t, stream)
   939  
   940  	// wait for watching to begin
   941  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
   942  		found := false
   943  		l.updateObservers.Range(func(_, _ interface{}) bool {
   944  			found = true
   945  			return false
   946  		})
   947  		return found, nil
   948  	})
   949  	assert.NoError(t, err)
   950  
   951  	testScenarios := map[string]struct {
   952  		updateRequest *beta.UpdateListRequest
   953  		want          *beta.List
   954  		wantErr       error
   955  	}{
   956  		"only updates fields in the FieldMask": {
   957  			updateRequest: &beta.UpdateListRequest{
   958  				List: &beta.List{
   959  					Name:     "games",
   960  					Capacity: int64(999),
   961  					Values:   []string{"game3"},
   962  				},
   963  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
   964  			},
   965  			want: &beta.List{
   966  				Name:     "games",
   967  				Capacity: int64(999),
   968  				Values:   []string{"game1", "game2"},
   969  			},
   970  		},
   971  		"updates both fields in the FieldMask": {
   972  			updateRequest: &beta.UpdateListRequest{
   973  				List: &beta.List{
   974  					Name:     "unicorns",
   975  					Capacity: int64(42),
   976  					Values:   []string{"unicorn0"},
   977  				},
   978  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"values", "capacity"}},
   979  			},
   980  			want: &beta.List{
   981  				Name:     "unicorns",
   982  				Capacity: int64(42),
   983  				Values:   []string{"unicorn0"},
   984  			},
   985  		},
   986  		"default value for Capacity applied": {
   987  			updateRequest: &beta.UpdateListRequest{
   988  				List: &beta.List{
   989  					Name: "clients",
   990  				},
   991  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
   992  			},
   993  			want: &beta.List{
   994  				Name:     "clients",
   995  				Capacity: int64(0),
   996  				Values:   []string{},
   997  			},
   998  		},
   999  		"default value for Values applied": {
  1000  			updateRequest: &beta.UpdateListRequest{
  1001  				List: &beta.List{
  1002  					Name: "assets",
  1003  				},
  1004  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"values"}},
  1005  			},
  1006  			want: &beta.List{
  1007  				Name:     "assets",
  1008  				Capacity: int64(1),
  1009  				Values:   []string{},
  1010  			},
  1011  		},
  1012  		"List does not exist": {
  1013  			updateRequest: &beta.UpdateListRequest{
  1014  				List: &beta.List{
  1015  					Name: "dragons",
  1016  				},
  1017  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
  1018  			},
  1019  			wantErr: errors.Errorf("not found. %s List not found", "dragons"),
  1020  		},
  1021  		"request List is nil": {
  1022  			updateRequest: &beta.UpdateListRequest{
  1023  				List:       nil,
  1024  				UpdateMask: &fieldmaskpb.FieldMask{},
  1025  			},
  1026  			wantErr: errors.Errorf("invalid argument. List: %v and UpdateMask %v cannot be nil", nil, &fieldmaskpb.FieldMask{}),
  1027  		},
  1028  		"request UpdateMask is nil": {
  1029  			updateRequest: &beta.UpdateListRequest{
  1030  				List:       &beta.List{},
  1031  				UpdateMask: nil,
  1032  			},
  1033  			wantErr: errors.Errorf("invalid argument. List: %v and UpdateMask %v cannot be nil", &beta.List{}, nil),
  1034  		},
  1035  		"updateMask contains invalid path": {
  1036  			updateRequest: &beta.UpdateListRequest{
  1037  				List: &beta.List{
  1038  					Name: "assets",
  1039  				},
  1040  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo"}},
  1041  			},
  1042  			wantErr: errors.Errorf("invalid argument. Field Mask Path(s): [foo] are invalid for List. Use valid field name(s): "),
  1043  		},
  1044  		"updateMask is empty": {
  1045  			updateRequest: &beta.UpdateListRequest{
  1046  				List: &beta.List{
  1047  					Name: "unicorns",
  1048  				},
  1049  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{""}},
  1050  			},
  1051  			wantErr: errors.Errorf("invalid argument. Field Mask Path(s): [] are invalid for List. Use valid field name(s): "),
  1052  		},
  1053  		"capacity is less than zero": {
  1054  			updateRequest: &beta.UpdateListRequest{
  1055  				List: &beta.List{
  1056  					Name:     "clients",
  1057  					Capacity: -1,
  1058  				},
  1059  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
  1060  			},
  1061  			wantErr: errors.Errorf("out of range. Capacity must be within range [0,1000]. Found Capacity: %d", -1),
  1062  		},
  1063  		"capacity greater than max capacity (1000)": {
  1064  			updateRequest: &beta.UpdateListRequest{
  1065  				List: &beta.List{
  1066  					Name:     "clients",
  1067  					Capacity: 1001,
  1068  				},
  1069  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
  1070  			},
  1071  			wantErr: errors.Errorf("out of range. Capacity must be within range [0,1000]. Found Capacity: %d", 1001),
  1072  		},
  1073  		"capacity is less than List length": {
  1074  			updateRequest: &beta.UpdateListRequest{
  1075  				List: &beta.List{
  1076  					Name:     "models",
  1077  					Capacity: 1,
  1078  				},
  1079  				UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}},
  1080  			},
  1081  			want: &beta.List{
  1082  				Name:     "models",
  1083  				Capacity: int64(1),
  1084  				Values:   []string{"model1"},
  1085  			},
  1086  		},
  1087  	}
  1088  
  1089  	for testName, testScenario := range testScenarios {
  1090  		t.Run(testName, func(t *testing.T) {
  1091  			got, err := l.UpdateList(context.Background(), testScenario.updateRequest)
  1092  			// Check tests expecting non-errors
  1093  			if testScenario.want != nil {
  1094  				assert.NoError(t, err)
  1095  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
  1096  					t.Errorf("Unexpected difference:\n%v", diff)
  1097  				}
  1098  			} else {
  1099  				// Check tests expecting errors
  1100  				assert.ErrorContains(t, err, testScenario.wantErr.Error())
  1101  			}
  1102  		})
  1103  	}
  1104  }
  1105  
  1106  func TestLocalSDKServerAddListValue(t *testing.T) {
  1107  	t.Parallel()
  1108  
  1109  	runtime.FeatureTestMutex.Lock()
  1110  	defer runtime.FeatureTestMutex.Unlock()
  1111  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
  1112  
  1113  	lists := map[string]agonesv1.ListStatus{
  1114  		"lemmings": {Capacity: int64(100), Values: []string{"lemming1", "lemming2"}},
  1115  		"hacks":    {Capacity: int64(2), Values: []string{"hack1", "hack2"}},
  1116  	}
  1117  	fixture := &agonesv1.GameServer{
  1118  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
  1119  		Status: agonesv1.GameServerStatus{
  1120  			Lists: lists,
  1121  		},
  1122  	}
  1123  
  1124  	path, err := gsToTmpFile(fixture)
  1125  	assert.NoError(t, err)
  1126  	l, err := NewLocalSDKServer(path, "")
  1127  	assert.Nil(t, err)
  1128  
  1129  	stream := newGameServerMockStream()
  1130  	go func() {
  1131  		err := l.WatchGameServer(&sdk.Empty{}, stream)
  1132  		assert.Nil(t, err)
  1133  	}()
  1134  	assertInitialWatchUpdate(t, stream)
  1135  
  1136  	// wait for watching to begin
  1137  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
  1138  		found := false
  1139  		l.updateObservers.Range(func(_, _ interface{}) bool {
  1140  			found = true
  1141  			return false
  1142  		})
  1143  		return found, nil
  1144  	})
  1145  	assert.NoError(t, err)
  1146  
  1147  	testScenarios := map[string]struct {
  1148  		addRequest *beta.AddListValueRequest
  1149  		want       *beta.List
  1150  		wantErr    error
  1151  	}{
  1152  		"add List value": {
  1153  			addRequest: &beta.AddListValueRequest{
  1154  				Name:  "lemmings",
  1155  				Value: "lemming3",
  1156  			},
  1157  			want: &beta.List{Name: "lemmings", Capacity: int64(100), Values: []string{"lemming1", "lemming2", "lemming3"}},
  1158  		},
  1159  		"List does not exist": {
  1160  			addRequest: &beta.AddListValueRequest{
  1161  				Name: "dragons",
  1162  			},
  1163  			wantErr: errors.Errorf("not found. %s List not found", "dragons"),
  1164  		},
  1165  		"add more values than capacity": {
  1166  			addRequest: &beta.AddListValueRequest{
  1167  				Name:  "hacks",
  1168  				Value: "hack3",
  1169  			},
  1170  			wantErr: errors.Errorf("out of range. No available capacity. Current Capacity: %d, List Size: %d", int64(2), int64(2)),
  1171  		},
  1172  		"add existing value": {
  1173  			addRequest: &beta.AddListValueRequest{
  1174  				Name:  "lemmings",
  1175  				Value: "lemming1",
  1176  			},
  1177  			wantErr: errors.Errorf("already exists. Value: %s already in List: %s", "lemming1", "lemmings"),
  1178  		},
  1179  	}
  1180  
  1181  	for testName, testScenario := range testScenarios {
  1182  		t.Run(testName, func(t *testing.T) {
  1183  			got, err := l.AddListValue(context.Background(), testScenario.addRequest)
  1184  			// Check tests expecting non-errors
  1185  			if testScenario.want != nil {
  1186  				assert.NoError(t, err)
  1187  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
  1188  					t.Errorf("Unexpected difference:\n%v", diff)
  1189  				}
  1190  			} else {
  1191  				// Check tests expecting errors
  1192  				assert.ErrorContains(t, err, testScenario.wantErr.Error())
  1193  			}
  1194  		})
  1195  	}
  1196  }
  1197  
  1198  func TestLocalSDKServerRemoveListValue(t *testing.T) {
  1199  	t.Parallel()
  1200  
  1201  	runtime.FeatureTestMutex.Lock()
  1202  	defer runtime.FeatureTestMutex.Unlock()
  1203  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true"))
  1204  
  1205  	lists := map[string]agonesv1.ListStatus{
  1206  		"players": {Capacity: int64(100), Values: []string{"player1", "player2"}},
  1207  		"items":   {Capacity: int64(1000), Values: []string{"item1", "item2"}},
  1208  	}
  1209  	fixture := &agonesv1.GameServer{
  1210  		ObjectMeta: metav1.ObjectMeta{Name: "stuff"},
  1211  		Status: agonesv1.GameServerStatus{
  1212  			Lists: lists,
  1213  		},
  1214  	}
  1215  
  1216  	path, err := gsToTmpFile(fixture)
  1217  	assert.NoError(t, err)
  1218  	l, err := NewLocalSDKServer(path, "")
  1219  	assert.Nil(t, err)
  1220  
  1221  	stream := newGameServerMockStream()
  1222  	go func() {
  1223  		err := l.WatchGameServer(&sdk.Empty{}, stream)
  1224  		assert.Nil(t, err)
  1225  	}()
  1226  	assertInitialWatchUpdate(t, stream)
  1227  
  1228  	// wait for watching to begin
  1229  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(_ context.Context) (bool, error) {
  1230  		found := false
  1231  		l.updateObservers.Range(func(_, _ interface{}) bool {
  1232  			found = true
  1233  			return false
  1234  		})
  1235  		return found, nil
  1236  	})
  1237  	assert.NoError(t, err)
  1238  
  1239  	testScenarios := map[string]struct {
  1240  		removeRequest *beta.RemoveListValueRequest
  1241  		want          *beta.List
  1242  		wantErr       error
  1243  	}{
  1244  		"remove List value": {
  1245  			removeRequest: &beta.RemoveListValueRequest{
  1246  				Name:  "players",
  1247  				Value: "player1",
  1248  			},
  1249  			want: &beta.List{Name: "players", Capacity: int64(100), Values: []string{"player2"}},
  1250  		},
  1251  		"List does not exist": {
  1252  			removeRequest: &beta.RemoveListValueRequest{
  1253  				Name: "dragons",
  1254  			},
  1255  			wantErr: errors.Errorf("not found. %s List not found", "dragons"),
  1256  		},
  1257  		"value does not exist": {
  1258  			removeRequest: &beta.RemoveListValueRequest{
  1259  				Name:  "items",
  1260  				Value: "item3",
  1261  			},
  1262  			wantErr: errors.Errorf("not found. Value: %s not found in List: %s", "item3", "items"),
  1263  		},
  1264  	}
  1265  
  1266  	for testName, testScenario := range testScenarios {
  1267  		t.Run(testName, func(t *testing.T) {
  1268  			got, err := l.RemoveListValue(context.Background(), testScenario.removeRequest)
  1269  			// Check tests expecting non-errors
  1270  			if testScenario.want != nil {
  1271  				assert.NoError(t, err)
  1272  				if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" {
  1273  					t.Errorf("Unexpected difference:\n%v", diff)
  1274  				}
  1275  			} else {
  1276  				// Check tests expecting errors
  1277  				assert.ErrorContains(t, err, testScenario.wantErr.Error())
  1278  			}
  1279  		})
  1280  	}
  1281  }
  1282  
  1283  // TestLocalSDKServerStateUpdates verify that SDK functions changes the state of the
  1284  // GameServer object
  1285  func TestLocalSDKServerStateUpdates(t *testing.T) {
  1286  	t.Parallel()
  1287  	l, err := NewLocalSDKServer("", "")
  1288  	assert.Nil(t, err)
  1289  
  1290  	ctx := context.Background()
  1291  	e := &sdk.Empty{}
  1292  	_, err = l.Ready(ctx, e)
  1293  	assert.Nil(t, err)
  1294  
  1295  	gs, err := l.GetGameServer(ctx, e)
  1296  	assert.Nil(t, err)
  1297  	assert.Equal(t, gs.Status.State, string(agonesv1.GameServerStateReady))
  1298  
  1299  	seconds := &sdk.Duration{Seconds: 2}
  1300  	_, err = l.Reserve(ctx, seconds)
  1301  	assert.Nil(t, err)
  1302  
  1303  	gs, err = l.GetGameServer(ctx, e)
  1304  	assert.Nil(t, err)
  1305  	assert.Equal(t, gs.Status.State, string(agonesv1.GameServerStateReserved))
  1306  
  1307  	_, err = l.Allocate(ctx, e)
  1308  	assert.Nil(t, err)
  1309  
  1310  	gs, err = l.GetGameServer(ctx, e)
  1311  	assert.Nil(t, err)
  1312  	assert.Equal(t, gs.Status.State, string(agonesv1.GameServerStateAllocated))
  1313  
  1314  	_, err = l.Shutdown(ctx, e)
  1315  	assert.Nil(t, err)
  1316  
  1317  	gs, err = l.GetGameServer(ctx, e)
  1318  	assert.Nil(t, err)
  1319  	assert.Equal(t, gs.Status.State, string(agonesv1.GameServerStateShutdown))
  1320  }
  1321  
  1322  // TestSDKConformanceFunctionality - run a number of record requests in parallel
  1323  func TestSDKConformanceFunctionality(t *testing.T) {
  1324  	t.Parallel()
  1325  
  1326  	l, err := NewLocalSDKServer("", "")
  1327  	assert.Nil(t, err)
  1328  	l.testMode = true
  1329  	l.recordRequest("")
  1330  	l.gs = &sdk.GameServer{ObjectMeta: &sdk.GameServer_ObjectMeta{Name: "empty"}}
  1331  	exampleUID := "052fb0f4-3d50-11e5-b066-42010af0d7b6"
  1332  	// field which is tested
  1333  	setAnnotation := "setannotation"
  1334  	l.gs.ObjectMeta.Uid = exampleUID
  1335  
  1336  	var expected []string
  1337  	expected = append(expected, "", setAnnotation)
  1338  
  1339  	wg := sync.WaitGroup{}
  1340  	for i := 0; i < 20; i++ {
  1341  		wg.Add(1)
  1342  		str := fmt.Sprintf("%d", i)
  1343  		expected = append(expected, str)
  1344  
  1345  		go func() {
  1346  			l.recordRequest(str)
  1347  			l.recordRequestWithValue(setAnnotation, exampleUID, "UID")
  1348  			wg.Done()
  1349  		}()
  1350  	}
  1351  	wg.Wait()
  1352  
  1353  	l.SetExpectedSequence(expected)
  1354  	b := l.EqualSets(l.expectedSequence, l.requestSequence)
  1355  	assert.True(t, b, "we should receive strings from all go routines %v %v", l.expectedSequence, l.requestSequence)
  1356  }
  1357  
  1358  func TestAlphaSDKConformanceFunctionality(t *testing.T) {
  1359  	t.Parallel()
  1360  	lStable, err := NewLocalSDKServer("", "")
  1361  	assert.Nil(t, err)
  1362  	v := int64(0)
  1363  	lStable.recordRequestWithValue("setplayercapacity", strconv.FormatInt(v, 10), "PlayerCapacity")
  1364  	lStable.recordRequestWithValue("isplayerconnected", "", "PlayerIDs")
  1365  
  1366  	runtime.FeatureTestMutex.Lock()
  1367  	defer runtime.FeatureTestMutex.Unlock()
  1368  
  1369  	assert.NoError(t, runtime.ParseFeatures(string(runtime.FeaturePlayerTracking)+"=true"))
  1370  	l, err := NewLocalSDKServer("", "")
  1371  	assert.Nil(t, err)
  1372  	l.testMode = true
  1373  	l.recordRequestWithValue("setplayercapacity", strconv.FormatInt(v, 10), "PlayerCapacity")
  1374  	l.recordRequestWithValue("isplayerconnected", "", "PlayerIDs")
  1375  
  1376  }
  1377  
  1378  func gsToTmpFile(gs *agonesv1.GameServer) (string, error) {
  1379  	file, err := os.CreateTemp(os.TempDir(), "gameserver-")
  1380  	if err != nil {
  1381  		return file.Name(), err
  1382  	}
  1383  
  1384  	err = json.NewEncoder(file).Encode(gs)
  1385  	return file.Name(), err
  1386  }
  1387  
  1388  // assertWatchUpdate checks the values of an update message when a GameServer value has been changed
  1389  func assertWatchUpdate(t *testing.T, stream *gameServerMockStream, expected interface{}, actual func(gs *sdk.GameServer) interface{}) {
  1390  	select {
  1391  	case msg := <-stream.msgs:
  1392  		assert.Equal(t, expected, actual(msg))
  1393  	case <-time.After(20 * time.Second):
  1394  		assert.Fail(t, "timeout on receiving messages")
  1395  	}
  1396  }
  1397  
  1398  // assertNoWatchUpdate checks that no update message has been sent for changes to the GameServer
  1399  func assertNoWatchUpdate(t *testing.T, stream *gameServerMockStream) {
  1400  	select {
  1401  	case <-stream.msgs:
  1402  		assert.Fail(t, "should not get a message")
  1403  	case <-time.After(time.Second):
  1404  	}
  1405  }
  1406  
  1407  // assertInitialWatchUpdate checks that the initial GameServer state is sent immediately after WatchGameServer
  1408  func assertInitialWatchUpdate(t *testing.T, stream *gameServerMockStream) {
  1409  	select {
  1410  	case <-stream.msgs:
  1411  	case <-time.After(time.Second):
  1412  		assert.Fail(t, "timeout on receiving initial message")
  1413  	}
  1414  }