agones.dev/agones@v1.54.0/pkg/util/workerqueue/workerqueue_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 workerqueue
    16  
    17  import (
    18  	"context"
    19  	"io"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/heptiolabs/healthcheck"
    26  	"github.com/pkg/errors"
    27  	"github.com/sirupsen/logrus"
    28  	"github.com/stretchr/testify/assert"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	"k8s.io/client-go/tools/cache"
    31  )
    32  
    33  func TestWorkerQueueRun(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	received := make(chan string)
    37  	defer close(received)
    38  
    39  	syncHandler := func(_ context.Context, name string) error {
    40  		assert.Equal(t, "default/test", name)
    41  		received <- name
    42  		return nil
    43  	}
    44  
    45  	wq := NewWorkerQueue(syncHandler, logrus.WithField("source", "test"), "testKey", "test")
    46  	stop := make(chan struct{})
    47  	defer close(stop)
    48  
    49  	go wq.Run(context.Background(), 1)
    50  
    51  	// no change, should be no value
    52  	select {
    53  	case <-received:
    54  		assert.Fail(t, "should not have received value")
    55  	case <-time.After(1 * time.Second):
    56  	}
    57  
    58  	wq.Enqueue(cache.ExplicitKey("default/test"))
    59  
    60  	select {
    61  	case <-received:
    62  	case <-time.After(5 * time.Second):
    63  		assert.Fail(t, "should have received value")
    64  	}
    65  }
    66  
    67  func TestWorkerQueueHealthy(t *testing.T) {
    68  	t.Parallel()
    69  
    70  	done := make(chan struct{})
    71  	handler := func(context.Context, string) error {
    72  		<-done
    73  		return nil
    74  	}
    75  	wq := NewWorkerQueue(handler, logrus.WithField("source", "test"), "testKey", "test")
    76  	wq.Enqueue(cache.ExplicitKey("default/test"))
    77  
    78  	ctx, cancel := context.WithCancel(context.Background())
    79  	go wq.Run(ctx, 1)
    80  
    81  	// Yield to the scheduler to ensure the worker queue goroutine can run.
    82  	err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 3*time.Second, true, func(_ context.Context) (done bool, err error) {
    83  		if (wq.RunCount() == 1) && wq.Healthy() == nil {
    84  			return true, nil
    85  		}
    86  
    87  		return false, nil
    88  	})
    89  	assert.Nil(t, err)
    90  
    91  	close(done) // Ensure the handler no longer blocks.
    92  	cancel()    // Stop the worker queue.
    93  
    94  	// Yield to the scheduler again to ensure the worker queue goroutine can
    95  	// finish.
    96  	err = wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 3*time.Second, true, func(_ context.Context) (done bool, err error) {
    97  		if (wq.RunCount() == 0) && wq.Healthy() != nil {
    98  			return true, nil
    99  		}
   100  
   101  		return false, nil
   102  	})
   103  	assert.Nil(t, err)
   104  }
   105  
   106  func TestWorkQueueHealthCheck(t *testing.T) {
   107  	t.Parallel()
   108  
   109  	health := healthcheck.NewHandler()
   110  	handler := func(context.Context, string) error {
   111  		return nil
   112  	}
   113  	wq := NewWorkerQueue(handler, logrus.WithField("source", "test"), "testKey", "test")
   114  	health.AddLivenessCheck("test", wq.Healthy)
   115  
   116  	server := httptest.NewServer(health)
   117  	defer server.Close()
   118  
   119  	const workersCount = 1
   120  	ctx, cancel := context.WithCancel(context.Background())
   121  	go wq.Run(ctx, workersCount)
   122  
   123  	// Wait for worker to actually start
   124  	err := wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 5*time.Second, true, func(_ context.Context) (bool, error) {
   125  		rc := wq.RunCount()
   126  		logrus.WithField("runcount", rc).Info("Checking run count before liveness check")
   127  		return rc == workersCount, nil
   128  	})
   129  	assert.Nil(t, err)
   130  
   131  	f := func(t *testing.T, url string, status int) {
   132  		// sometimes the http server takes a bit to start up
   133  		err := wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Second, true, func(_ context.Context) (done bool, err error) {
   134  			resp, err := http.Get(url)
   135  			assert.Nil(t, err)
   136  			defer resp.Body.Close() // nolint: errcheck
   137  
   138  			if status != resp.StatusCode {
   139  				return false, nil
   140  			}
   141  
   142  			body, err := io.ReadAll(resp.Body)
   143  			assert.Nil(t, err)
   144  			assert.Equal(t, status, resp.StatusCode)
   145  			assert.Equal(t, []byte("{}\n"), body)
   146  
   147  			return true, nil
   148  		})
   149  
   150  		assert.Nil(t, err)
   151  	}
   152  
   153  	url := server.URL + "/live"
   154  	f(t, url, http.StatusOK)
   155  
   156  	cancel()
   157  	// closing can take a short while
   158  	err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Second, true, func(_ context.Context) (bool, error) {
   159  		rc := wq.RunCount()
   160  		logrus.WithField("runcount", rc).Info("Checking run count")
   161  		return rc == 0, nil
   162  	})
   163  	assert.Nil(t, err)
   164  
   165  	// gate
   166  	assert.Error(t, wq.Healthy())
   167  	f(t, url, http.StatusServiceUnavailable)
   168  }
   169  
   170  func TestWorkerQueueEnqueueAfter(t *testing.T) {
   171  	t.Parallel()
   172  
   173  	updated := make(chan bool)
   174  	syncHandler := func(_ context.Context, _ string) error {
   175  		updated <- true
   176  		return nil
   177  	}
   178  	wq := NewWorkerQueue(syncHandler, logrus.WithField("source", "test"), "testKey", "test")
   179  	stop := make(chan struct{})
   180  	defer close(stop)
   181  
   182  	go wq.Run(context.Background(), 1)
   183  
   184  	wq.EnqueueAfter(cache.ExplicitKey("default/test"), 2*time.Second)
   185  
   186  	select {
   187  	case <-updated:
   188  		assert.FailNow(t, "should not be a result in queue yet")
   189  	case <-time.After(time.Second):
   190  	}
   191  
   192  	select {
   193  	case <-updated:
   194  	case <-time.After(2 * time.Second):
   195  		assert.Fail(t, "should have got a queue'd message by now")
   196  	}
   197  }
   198  
   199  func TestDebugError(t *testing.T) {
   200  	err := errors.New("not a debug error")
   201  	assert.False(t, isTraceError(err))
   202  
   203  	err = NewTraceError(err)
   204  	assert.True(t, isTraceError(err))
   205  	assert.EqualError(t, err, "not a debug error")
   206  
   207  	err = NewTraceError(nil)
   208  	assert.True(t, isTraceError(err))
   209  	assert.EqualError(t, err, "<nil>")
   210  }