go.uber.org/cadence@v1.2.9/internal/workflow_shadower_activities_test.go (about)

     1  // Copyright (c) 2017-2021 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 internal
    22  
    23  import (
    24  	"context"
    25  	"math"
    26  	"math/rand"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/require"
    32  	"github.com/stretchr/testify/suite"
    33  	"go.uber.org/zap/zaptest"
    34  
    35  	"go.uber.org/cadence/.gen/go/cadence/workflowservicetest"
    36  	"go.uber.org/cadence/.gen/go/shadower"
    37  	"go.uber.org/cadence/.gen/go/shared"
    38  	"go.uber.org/cadence/internal/common"
    39  )
    40  
    41  type workflowShadowerActivitiesSuite struct {
    42  	*require.Assertions
    43  	suite.Suite
    44  	WorkflowTestSuite
    45  
    46  	controller  *gomock.Controller
    47  	mockService *workflowservicetest.MockClient
    48  
    49  	env                 *TestActivityEnvironment
    50  	testReplayer        *WorkflowReplayer
    51  	testWorkflowHistory *shared.History
    52  }
    53  
    54  func TestWorkflowShadowerActivitiesSuite(t *testing.T) {
    55  	s := new(workflowShadowerActivitiesSuite)
    56  	suite.Run(t, s)
    57  }
    58  
    59  func (s *workflowShadowerActivitiesSuite) SetupTest() {
    60  	s.Assertions = require.New(s.T())
    61  
    62  	s.controller = gomock.NewController(s.T())
    63  	s.mockService = workflowservicetest.NewMockClient(s.controller)
    64  	s.SetLogger(zaptest.NewLogger(s.T()))
    65  
    66  	s.env = s.NewTestActivityEnvironment()
    67  	s.testReplayer = NewWorkflowReplayer()
    68  	s.testReplayer.RegisterWorkflow(testReplayWorkflow)
    69  	s.testWorkflowHistory = getTestReplayWorkflowFullHistory(s.T())
    70  
    71  	activityContext := context.Background()
    72  	activityContext = context.WithValue(activityContext, serviceClientContextKey, s.mockService)
    73  	activityContext = context.WithValue(activityContext, workflowReplayerContextKey, s.testReplayer)
    74  	s.env.SetWorkerOptions(WorkerOptions{
    75  		BackgroundActivityContext: activityContext,
    76  		Logger:                    zaptest.NewLogger(s.T()),
    77  	})
    78  	s.env.RegisterActivityWithOptions(scanWorkflowActivity, RegisterActivityOptions{
    79  		Name: shadower.ScanWorkflowActivityName,
    80  	})
    81  	s.env.RegisterActivityWithOptions(replayWorkflowActivity, RegisterActivityOptions{
    82  		Name: shadower.ReplayWorkflowActivityName,
    83  	})
    84  }
    85  
    86  func (s *workflowShadowerActivitiesSuite) TearDownTest() {
    87  	s.controller.Finish()
    88  }
    89  
    90  func (s *workflowShadowerActivitiesSuite) TestScanWorkflowActivity_Succeed() {
    91  	numExecutions := 1000
    92  	s.mockService.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.ListWorkflowExecutionsResponse{
    93  		Executions:    newTestWorkflowExecutions(numExecutions),
    94  		NextPageToken: []byte{1, 2, 3},
    95  	}, nil).Times(1)
    96  
    97  	params := shadower.ScanWorkflowActivityParams{
    98  		Domain:        common.StringPtr(defaultTestDomain),
    99  		WorkflowQuery: common.StringPtr("some random workflow visibility query"),
   100  		SamplingRate:  common.Float64Ptr(0.5),
   101  	}
   102  
   103  	resultValue, err := s.env.ExecuteActivity(shadower.ScanWorkflowActivityName, params)
   104  	s.NoError(err)
   105  
   106  	var result shadower.ScanWorkflowActivityResult
   107  	s.NoError(resultValue.Get(&result))
   108  	s.NotNil(result.NextPageToken)
   109  	s.True(len(result.Executions) > 0)
   110  	s.True(len(result.Executions) < numExecutions)
   111  }
   112  
   113  func (s *workflowShadowerActivitiesSuite) TestScanWorkflowActivity_MinResultSize() {
   114  	numExecutionsPerScan := 3
   115  	s.mockService.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.ListWorkflowExecutionsResponse{
   116  		Executions:    newTestWorkflowExecutions(numExecutionsPerScan),
   117  		NextPageToken: []byte{1, 2, 3},
   118  	}, nil).Times(int(math.Ceil(float64(minScanWorkflowResultSize) / float64(numExecutionsPerScan))))
   119  
   120  	params := shadower.ScanWorkflowActivityParams{
   121  		Domain:        common.StringPtr(defaultTestDomain),
   122  		WorkflowQuery: common.StringPtr("some random workflow visibility query"),
   123  		SamplingRate:  common.Float64Ptr(1),
   124  	}
   125  
   126  	resultValue, err := s.env.ExecuteActivity(shadower.ScanWorkflowActivityName, params)
   127  	s.NoError(err)
   128  
   129  	var result shadower.ScanWorkflowActivityResult
   130  	s.NoError(resultValue.Get(&result))
   131  	s.NotNil(result.NextPageToken)
   132  	s.True(len(result.Executions) >= minScanWorkflowResultSize)
   133  }
   134  
   135  func (s *workflowShadowerActivitiesSuite) TestScanWorkflowActivity_CompletionTime() {
   136  	activityTimeoutSeconds := int32(1)
   137  	s.mockService.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.ListWorkflowExecutionsResponse{
   138  		Executions:    newTestWorkflowExecutions(1),
   139  		NextPageToken: []byte{1, 2, 3},
   140  	}, nil).MaxTimes(int(time.Duration(activityTimeoutSeconds) * time.Second / scanWorkflowWaitPeriod))
   141  
   142  	params := shadower.ScanWorkflowActivityParams{
   143  		Domain:        common.StringPtr(defaultTestDomain),
   144  		WorkflowQuery: common.StringPtr("some random workflow visibility query"),
   145  		SamplingRate:  common.Float64Ptr(0.00000001),
   146  	}
   147  
   148  	resultValue, err := s.env.impl.executeActivityWithOptions(
   149  		activityOptions{
   150  			ScheduleToCloseTimeoutSeconds: activityTimeoutSeconds,
   151  			StartToCloseTimeoutSeconds:    activityTimeoutSeconds,
   152  		},
   153  		shadower.ScanWorkflowActivityName,
   154  		params,
   155  	)
   156  	s.NoError(err)
   157  
   158  	var result shadower.ScanWorkflowActivityResult
   159  	s.NoError(resultValue.Get(&result))
   160  	s.NotNil(result.NextPageToken)
   161  	s.Empty(result.Executions)
   162  }
   163  
   164  func (s *workflowShadowerActivitiesSuite) TestScanWorkflowActivity_InvalidQuery() {
   165  	s.mockService.EXPECT().ScanWorkflowExecutions(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, &shared.BadRequestError{
   166  		Message: "invalid query",
   167  	}).Times(1)
   168  
   169  	params := shadower.ScanWorkflowActivityParams{
   170  		Domain:        common.StringPtr(defaultTestDomain),
   171  		WorkflowQuery: common.StringPtr("invalid workflow visibility query"),
   172  	}
   173  
   174  	_, err := s.env.ExecuteActivity(shadower.ScanWorkflowActivityName, params)
   175  	s.Error(err)
   176  	s.Equal(shadower.ErrReasonInvalidQuery, err.Error())
   177  }
   178  
   179  func (s *workflowShadowerActivitiesSuite) TestReplayWorkflowExecutionActivity_NoPreviousProgress() {
   180  	numExecutions := 10
   181  	s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.GetWorkflowExecutionHistoryResponse{
   182  		History: s.testWorkflowHistory,
   183  	}, nil).Times(numExecutions)
   184  
   185  	params := newTestReplayWorkflowActivityParams(numExecutions)
   186  
   187  	resultValue, err := s.env.ExecuteActivity(shadower.ReplayWorkflowActivityName, params)
   188  	s.NoError(err)
   189  
   190  	var result shadower.ReplayWorkflowActivityResult
   191  	s.NoError(resultValue.Get(&result))
   192  	s.Equal(int32(numExecutions), result.GetSucceeded())
   193  	s.Zero(result.GetSkipped())
   194  	s.Zero(result.GetFailed())
   195  }
   196  
   197  func (s *workflowShadowerActivitiesSuite) TestReplayWorkflowExecutionActivity_WithPreviousProgress() {
   198  	progress := replayWorkflowActivityProgress{
   199  		Result: shadower.ReplayWorkflowActivityResult{
   200  			Succeeded: common.Int32Ptr(3),
   201  			Skipped:   common.Int32Ptr(2),
   202  			Failed:    common.Int32Ptr(0),
   203  		},
   204  		NextExecutionIdx: 5,
   205  	}
   206  	s.env.SetHeartbeatDetails(progress)
   207  
   208  	numExecutions := 10
   209  	s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.GetWorkflowExecutionHistoryResponse{
   210  		History: s.testWorkflowHistory,
   211  	}, nil).Times(numExecutions - progress.NextExecutionIdx)
   212  
   213  	params := newTestReplayWorkflowActivityParams(numExecutions)
   214  
   215  	resultValue, err := s.env.ExecuteActivity(shadower.ReplayWorkflowActivityName, params)
   216  	s.NoError(err)
   217  
   218  	var result shadower.ReplayWorkflowActivityResult
   219  	s.NoError(resultValue.Get(&result))
   220  	s.Equal(progress.Result.GetSucceeded()+int32(numExecutions-progress.NextExecutionIdx), result.GetSucceeded())
   221  	s.Equal(progress.Result.GetSkipped(), result.GetSkipped())
   222  	s.Equal(progress.Result.GetFailed(), result.GetFailed())
   223  }
   224  
   225  func (s *workflowShadowerActivitiesSuite) TestReplayWorkflowExecutionActivity_RandomReplayResult() {
   226  	numSucceed := int32(0)
   227  	numFailed := int32(0)
   228  	numSkipped := int32(0)
   229  	numExecutions := 100
   230  
   231  	mismatchWorkflowHistory := getTestReplayWorkflowMismatchHistory(s.T())
   232  	for i := 0; i != numExecutions; i++ {
   233  		switch rand.Intn(3) {
   234  		case 0:
   235  			numSucceed++
   236  			s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.GetWorkflowExecutionHistoryResponse{
   237  				History: s.testWorkflowHistory,
   238  			}, nil).Times(1)
   239  		case 1:
   240  			numSkipped++
   241  			s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(nil, &shared.EntityNotExistsError{}).Times(1)
   242  		case 2:
   243  			numFailed++
   244  			s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.GetWorkflowExecutionHistoryResponse{
   245  				History: mismatchWorkflowHistory,
   246  			}, nil).Times(1)
   247  		}
   248  	}
   249  
   250  	params := newTestReplayWorkflowActivityParams(numExecutions)
   251  
   252  	resultValue, err := s.env.ExecuteActivity(shadower.ReplayWorkflowActivityName, params)
   253  	s.NoError(err)
   254  
   255  	var result shadower.ReplayWorkflowActivityResult
   256  	s.NoError(resultValue.Get(&result))
   257  	s.Equal(numSucceed, result.GetSucceeded())
   258  	s.Equal(numSkipped, result.GetSkipped())
   259  	s.Equal(numFailed, result.GetFailed())
   260  }
   261  
   262  func (s *workflowShadowerActivitiesSuite) TestReplayWorkflowExecutionActivity_WorkflowNotRegistered() {
   263  	s.mockService.EXPECT().GetWorkflowExecutionHistory(gomock.Any(), gomock.Any(), callOptions()...).Return(&shared.GetWorkflowExecutionHistoryResponse{
   264  		History: getTestReplayWorkflowLocalActivityHistory(s.T()), // this workflow type is not registered
   265  	}, nil).Times(1)
   266  
   267  	params := newTestReplayWorkflowActivityParams(5)
   268  
   269  	_, err := s.env.ExecuteActivity(shadower.ReplayWorkflowActivityName, params)
   270  	s.Error(err)
   271  	s.Equal(shadower.ErrReasonWorkflowTypeNotRegistered, err.Error())
   272  }
   273  
   274  func newTestReplayWorkflowActivityParams(numExecutions int) shadower.ReplayWorkflowActivityParams {
   275  	params := shadower.ReplayWorkflowActivityParams{
   276  		Domain:     common.StringPtr(defaultTestDomain),
   277  		Executions: make([]*shared.WorkflowExecution, 0, numExecutions),
   278  	}
   279  	for i := 0; i != numExecutions; i++ {
   280  		params.Executions = append(params.Executions, &shared.WorkflowExecution{})
   281  	}
   282  	return params
   283  }