Browse Source

Provisioning: Trying to fix failing test (#16800)

* Provisioning: Trying to fix test

* Use better sync strategy for test

* Lower the timeout

* Remove commented code

Co-Authored-By: aocenas <mr.ocenas@gmail.com>
Torkel Ödegaard 6 years ago
parent
commit
9b68952482

+ 8 - 4
pkg/services/provisioning/provisioning.go

@@ -2,11 +2,12 @@ package provisioning
 
 
 import (
 import (
 	"context"
 	"context"
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/pkg/errors"
 	"path"
 	"path"
 	"sync"
 	"sync"
 
 
+	"github.com/grafana/grafana/pkg/log"
+	"github.com/pkg/errors"
+
 	"github.com/grafana/grafana/pkg/registry"
 	"github.com/grafana/grafana/pkg/registry"
 	"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
 	"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
 	"github.com/grafana/grafana/pkg/services/provisioning/datasources"
 	"github.com/grafana/grafana/pkg/services/provisioning/datasources"
@@ -78,7 +79,9 @@ func (ps *provisioningServiceImpl) Run(ctx context.Context) error {
 
 
 		// Wait for unlock. This is tied to new dashboardProvisioner to be instantiated before we start polling.
 		// Wait for unlock. This is tied to new dashboardProvisioner to be instantiated before we start polling.
 		ps.mutex.Lock()
 		ps.mutex.Lock()
-		pollingContext, cancelFun := context.WithCancel(ctx)
+		// Using background here because otherwise if root context was canceled the select later on would
+		// non-deterministically take one of the route possibly going into one polling loop before exiting.
+		pollingContext, cancelFun := context.WithCancel(context.Background())
 		ps.pollingCtxCancel = cancelFun
 		ps.pollingCtxCancel = cancelFun
 		ps.dashboardProvisioner.PollChanges(pollingContext)
 		ps.dashboardProvisioner.PollChanges(pollingContext)
 		ps.mutex.Unlock()
 		ps.mutex.Unlock()
@@ -88,7 +91,8 @@ func (ps *provisioningServiceImpl) Run(ctx context.Context) error {
 			// Polling was canceled.
 			// Polling was canceled.
 			continue
 			continue
 		case <-ctx.Done():
 		case <-ctx.Done():
-			// Root server context was cancelled so just leave.
+			// Root server context was cancelled so cancel polling and leave.
+			ps.cancelPolling()
 			return ctx.Err()
 			return ctx.Err()
 		}
 		}
 	}
 	}

+ 91 - 49
pkg/services/provisioning/provisioning_test.go

@@ -3,88 +3,130 @@ package provisioning
 import (
 import (
 	"context"
 	"context"
 	"errors"
 	"errors"
+	"testing"
+	"time"
+
 	"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
 	"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
-	"testing"
-	"time"
 )
 )
 
 
 func TestProvisioningServiceImpl(t *testing.T) {
 func TestProvisioningServiceImpl(t *testing.T) {
 	t.Run("Restart dashboard provisioning and stop service", func(t *testing.T) {
 	t.Run("Restart dashboard provisioning and stop service", func(t *testing.T) {
-		service, mock := setup()
-		ctx, cancel := context.WithCancel(context.Background())
-		var serviceRunning bool
-		var serviceError error
-
-		err := service.ProvisionDashboards()
+		serviceTest := setup()
+		err := serviceTest.service.ProvisionDashboards()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		go func() {
-			serviceRunning = true
-			serviceError = service.Run(ctx)
-			serviceRunning = false
-		}()
-		time.Sleep(time.Millisecond)
-		assert.Equal(t, 1, len(mock.Calls.PollChanges), "PollChanges should have been called")
+		serviceTest.startService()
+		serviceTest.waitForPollChanges()
 
 
-		err = service.ProvisionDashboards()
+		assert.Equal(t, 1, len(serviceTest.mock.Calls.PollChanges), "PollChanges should have been called")
+
+		err = serviceTest.service.ProvisionDashboards()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		time.Sleep(time.Millisecond)
-		assert.Equal(t, 2, len(mock.Calls.PollChanges), "PollChanges should have been called 2 times")
 
 
-		pollingCtx := mock.Calls.PollChanges[0].(context.Context)
+		serviceTest.waitForPollChanges()
+		assert.Equal(t, 2, len(serviceTest.mock.Calls.PollChanges), "PollChanges should have been called 2 times")
+
+		pollingCtx := serviceTest.mock.Calls.PollChanges[0].(context.Context)
 		assert.Equal(t, context.Canceled, pollingCtx.Err(), "Polling context from first call should have been cancelled")
 		assert.Equal(t, context.Canceled, pollingCtx.Err(), "Polling context from first call should have been cancelled")
-		assert.True(t, serviceRunning, "Service should be still running")
+		assert.True(t, serviceTest.serviceRunning, "Service should be still running")
 
 
 		// Cancelling the root context and stopping the service
 		// Cancelling the root context and stopping the service
-		cancel()
-		time.Sleep(time.Millisecond)
+		serviceTest.cancel()
+		serviceTest.waitForStop()
 
 
-		assert.False(t, serviceRunning, "Service should not be running")
-		assert.Equal(t, context.Canceled, serviceError, "Service should have returned canceled error")
+		assert.False(t, serviceTest.serviceRunning, "Service should not be running")
+		assert.Equal(t, context.Canceled, serviceTest.serviceError, "Service should have returned canceled error")
 
 
 	})
 	})
 
 
 	t.Run("Failed reloading does not stop polling with old provisioned", func(t *testing.T) {
 	t.Run("Failed reloading does not stop polling with old provisioned", func(t *testing.T) {
-		service, mock := setup()
-		ctx, cancel := context.WithCancel(context.Background())
-		var serviceRunning bool
-
-		err := service.ProvisionDashboards()
+		serviceTest := setup()
+		err := serviceTest.service.ProvisionDashboards()
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		go func() {
-			serviceRunning = true
-			_ = service.Run(ctx)
-			serviceRunning = false
-		}()
-		time.Sleep(time.Millisecond)
-		assert.Equal(t, 1, len(mock.Calls.PollChanges), "PollChanges should have been called")
+		serviceTest.startService()
+		serviceTest.waitForPollChanges()
+		assert.Equal(t, 1, len(serviceTest.mock.Calls.PollChanges), "PollChanges should have been called")
 
 
-		mock.ProvisionFunc = func() error {
+		serviceTest.mock.ProvisionFunc = func() error {
 			return errors.New("Test error")
 			return errors.New("Test error")
 		}
 		}
-		err = service.ProvisionDashboards()
+		err = serviceTest.service.ProvisionDashboards()
 		assert.NotNil(t, err)
 		assert.NotNil(t, err)
-		time.Sleep(time.Millisecond)
+		serviceTest.waitForPollChanges()
+
 		// This should have been called with the old provisioner, after the last one failed.
 		// This should have been called with the old provisioner, after the last one failed.
-		assert.Equal(t, 2, len(mock.Calls.PollChanges), "PollChanges should have been called 2 times")
-		assert.True(t, serviceRunning, "Service should be still running")
+		assert.Equal(t, 2, len(serviceTest.mock.Calls.PollChanges), "PollChanges should have been called 2 times")
+		assert.True(t, serviceTest.serviceRunning, "Service should be still running")
 
 
 		// Cancelling the root context and stopping the service
 		// Cancelling the root context and stopping the service
-		cancel()
-
+		serviceTest.cancel()
 	})
 	})
 }
 }
 
 
-func setup() (*provisioningServiceImpl, *dashboards.DashboardProvisionerMock) {
-	dashMock := dashboards.NewDashboardProvisionerMock()
-	service := NewProvisioningServiceImpl(
+type serviceTestStruct struct {
+	waitForPollChanges func()
+	waitForStop        func()
+	waitTimeout        time.Duration
+
+	serviceRunning bool
+	serviceError   error
+
+	startService func()
+	cancel       func()
+
+	mock    *dashboards.DashboardProvisionerMock
+	service *provisioningServiceImpl
+}
+
+func setup() *serviceTestStruct {
+	serviceTest := &serviceTestStruct{}
+	serviceTest.waitTimeout = time.Second
+
+	pollChangesChannel := make(chan context.Context)
+	serviceStopped := make(chan interface{})
+
+	serviceTest.mock = dashboards.NewDashboardProvisionerMock()
+	serviceTest.mock.PollChangesFunc = func(ctx context.Context) {
+		pollChangesChannel <- ctx
+	}
+
+	serviceTest.service = NewProvisioningServiceImpl(
 		func(path string) (dashboards.DashboardProvisioner, error) {
 		func(path string) (dashboards.DashboardProvisioner, error) {
-			return dashMock, nil
+			return serviceTest.mock, nil
 		},
 		},
 		nil,
 		nil,
 		nil,
 		nil,
 	)
 	)
-	service.Cfg = setting.NewCfg()
-	return service, dashMock
+	serviceTest.service.Cfg = setting.NewCfg()
+
+	ctx, cancel := context.WithCancel(context.Background())
+	serviceTest.cancel = cancel
+
+	serviceTest.startService = func() {
+		go func() {
+			serviceTest.serviceRunning = true
+			serviceTest.serviceError = serviceTest.service.Run(ctx)
+			serviceTest.serviceRunning = false
+			serviceStopped <- true
+		}()
+	}
+
+	serviceTest.waitForPollChanges = func() {
+		timeoutChan := time.After(serviceTest.waitTimeout)
+		select {
+		case <-pollChangesChannel:
+		case <-timeoutChan:
+		}
+	}
+
+	serviceTest.waitForStop = func() {
+		timeoutChan := time.After(serviceTest.waitTimeout)
+		select {
+		case <-serviceStopped:
+		case <-timeoutChan:
+		}
+	}
+
+	return serviceTest
 }
 }