github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/network/trace_test.go (about)

     1  package network
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  
    14  	"gitlab.com/gitlab-org/gitlab-runner/common"
    15  )
    16  
    17  var (
    18  	jobConfig      = common.RunnerConfig{}
    19  	jobCredentials = &common.JobCredentials{ID: -1}
    20  	jobOutputLimit = common.RunnerConfig{OutputLimit: 1}
    21  
    22  	noTrace *string
    23  )
    24  
    25  func matchJobState(jobInfo common.UpdateJobInfo, id int, state common.JobState, failureReason common.JobFailureReason) bool {
    26  	if jobInfo.ID != id {
    27  		return false
    28  	}
    29  	if jobInfo.State != state {
    30  		return false
    31  	}
    32  	if jobInfo.FailureReason != failureReason {
    33  		return false
    34  	}
    35  	return true
    36  }
    37  
    38  func generateJobInfoMatcher(id int, state common.JobState, trace *string, failureReason common.JobFailureReason) interface{} {
    39  	return mock.MatchedBy(func(jobInfo common.UpdateJobInfo) bool {
    40  		if jobInfo.Trace == nil && trace != nil {
    41  			return false
    42  		}
    43  		if jobInfo.Trace != nil && trace == nil {
    44  			return false
    45  		}
    46  		if jobInfo.Trace != nil && trace != nil && *jobInfo.Trace != *trace {
    47  			return false
    48  		}
    49  		return matchJobState(jobInfo, id, state, failureReason)
    50  	})
    51  }
    52  
    53  func generateJobInfoMatcherWithAnyTrace(id int, state common.JobState, failureReason common.JobFailureReason) interface{} {
    54  	return mock.MatchedBy(func(jobInfo common.UpdateJobInfo) bool {
    55  		return matchJobState(jobInfo, id, state, failureReason)
    56  	})
    57  }
    58  
    59  func TestJobTraceUpdateSucceeded(t *testing.T) {
    60  	traceMessage := "test content"
    61  	patchTraceMatcher := mock.MatchedBy(func(tracePatch common.JobTracePatch) bool {
    62  		return tracePatch.Offset() == 0 && string(tracePatch.Patch()) == traceMessage
    63  	})
    64  
    65  	tests := []struct {
    66  		name     string
    67  		jobState common.JobState
    68  	}{
    69  		{name: "Success", jobState: common.Success},
    70  		{name: "Fail", jobState: common.Failed},
    71  	}
    72  
    73  	for idx, test := range tests {
    74  		t.Run(test.name, func(t *testing.T) {
    75  			var wg sync.WaitGroup
    76  			jobCredentials := &common.JobCredentials{ID: idx}
    77  
    78  			net := new(common.MockNetwork)
    79  
    80  			firstUpdateMatcher := generateJobInfoMatcher(idx, common.Running, noTrace, common.NoneFailure)
    81  			net.On("UpdateJob", jobConfig, jobCredentials, firstUpdateMatcher).Return(common.UpdateSucceeded).Once()
    82  			net.On("PatchTrace", jobConfig, jobCredentials, patchTraceMatcher).Return(common.UpdateSucceeded).Run(func(_ mock.Arguments) { wg.Done() })
    83  
    84  			var expectedFailureReason common.JobFailureReason
    85  			switch test.jobState {
    86  			case common.Success:
    87  				expectedFailureReason = common.JobFailureReason("")
    88  			case common.Failed:
    89  				expectedFailureReason = common.JobFailureReason("script_failure")
    90  			}
    91  			secondUpdateMatcher := generateJobInfoMatcher(idx, test.jobState, &traceMessage, expectedFailureReason)
    92  			net.On("UpdateJob", jobConfig, jobCredentials, secondUpdateMatcher).Return(common.UpdateSucceeded)
    93  
    94  			b := newJobTrace(net, jobConfig, jobCredentials)
    95  			// speed up execution time
    96  			b.updateInterval = 10 * time.Millisecond
    97  			wg.Add(1)
    98  
    99  			b.start()
   100  			fmt.Fprint(b, traceMessage)
   101  			wg.Wait()
   102  
   103  			switch test.jobState {
   104  			case common.Success:
   105  				b.Success()
   106  			case common.Failed:
   107  				b.Fail(errors.New("test"), "script_failure")
   108  			}
   109  
   110  			net.AssertExpectations(t)
   111  		})
   112  	}
   113  }
   114  
   115  func TestIgnoreStatusChange(t *testing.T) {
   116  	jobInfoMatcher := generateJobInfoMatcherWithAnyTrace(jobCredentials.ID, common.Success, common.NoneFailure)
   117  
   118  	net := new(common.MockNetwork)
   119  	net.On("UpdateJob", jobConfig, jobCredentials, jobInfoMatcher).Return(common.UpdateSucceeded)
   120  
   121  	b := newJobTrace(net, jobConfig, jobCredentials)
   122  	// prevent any UpdateJob before `b.Success()` call
   123  	b.updateInterval = 25 * time.Second
   124  
   125  	b.start()
   126  	b.Success()
   127  	b.Fail(errors.New("test"), "script_failure")
   128  
   129  	net.AssertExpectations(t)
   130  }
   131  
   132  func TestJobAbort(t *testing.T) {
   133  	ctx, cancel := context.WithCancel(context.Background())
   134  	defer cancel()
   135  
   136  	firstUpdateMatcher := generateJobInfoMatcher(jobCredentials.ID, common.Running, noTrace, common.NoneFailure)
   137  	secondUpdateMatcher := generateJobInfoMatcherWithAnyTrace(jobCredentials.ID, common.Success, common.NoneFailure)
   138  
   139  	net := new(common.MockNetwork)
   140  	net.On("UpdateJob", jobConfig, jobCredentials, firstUpdateMatcher).Return(common.UpdateAbort)
   141  	net.On("PatchTrace", jobConfig, jobCredentials, mock.AnythingOfType("*network.tracePatch")).Return(common.UpdateAbort)
   142  	net.On("UpdateJob", jobConfig, jobCredentials, secondUpdateMatcher).Return(common.UpdateAbort)
   143  
   144  	b := newJobTrace(net, jobConfig, jobCredentials)
   145  	// force immediate call to `UpdateJob`
   146  	b.updateInterval = 0
   147  	b.SetCancelFunc(cancel)
   148  
   149  	b.start()
   150  	assert.NotNil(t, <-ctx.Done(), "should abort the job")
   151  	b.Success()
   152  
   153  	net.AssertExpectations(t)
   154  }
   155  
   156  func TestJobOutputLimit(t *testing.T) {
   157  	assert := assert.New(t)
   158  	traceMessage := "abcde"
   159  
   160  	net := new(common.MockNetwork)
   161  	b := newJobTrace(net, jobOutputLimit, jobCredentials)
   162  	// prevent any UpdateJob before `b.Success()` call
   163  	b.updateInterval = 25 * time.Second
   164  
   165  	updateMatcher := generateJobInfoMatcherWithAnyTrace(jobCredentials.ID, common.Success, common.NoneFailure)
   166  	net.On("UpdateJob", jobOutputLimit, jobCredentials, updateMatcher).Return(common.UpdateSucceeded).Run(func(args mock.Arguments) {
   167  		if updateInfo, ok := args.Get(2).(common.UpdateJobInfo); ok {
   168  			trace := updateInfo.Trace
   169  
   170  			expectedLogLimitExceededMsg := b.limitExceededMessage()
   171  			bytesLimit := b.bytesLimit + len(expectedLogLimitExceededMsg)
   172  			traceSize := len(*trace)
   173  
   174  			assert.Equal(bytesLimit, traceSize, "the trace should be exaclty %v bytes", bytesLimit)
   175  			assert.Contains(*trace, traceMessage)
   176  			assert.Contains(*trace, b.limitExceededMessage())
   177  		} else {
   178  			assert.FailNow("Unexpected type on UpdateJob jobInfo parameter")
   179  		}
   180  	})
   181  
   182  	b.start()
   183  	// Write 5k to the buffer
   184  	for i := 0; i < 1024; i++ {
   185  		fmt.Fprint(b, traceMessage)
   186  	}
   187  	b.Success()
   188  
   189  	net.AssertExpectations(t)
   190  }
   191  
   192  func TestJobFinishRetry(t *testing.T) {
   193  	updateMatcher := generateJobInfoMatcherWithAnyTrace(jobCredentials.ID, common.Success, common.NoneFailure)
   194  
   195  	net := new(common.MockNetwork)
   196  	net.On("UpdateJob", jobConfig, jobCredentials, updateMatcher).Return(common.UpdateFailed).Times(5)
   197  	net.On("UpdateJob", jobConfig, jobCredentials, updateMatcher).Return(common.UpdateSucceeded).Once()
   198  
   199  	b := newJobTrace(net, jobConfig, jobCredentials)
   200  	b.finishRetryInterval = time.Microsecond
   201  
   202  	b.start()
   203  	b.Success()
   204  
   205  	net.AssertExpectations(t)
   206  }
   207  
   208  func TestJobForceSend(t *testing.T) {
   209  	var wg sync.WaitGroup
   210  	traceMessage := "test content"
   211  	firstPatchMatcher := mock.MatchedBy(func(tracePatch common.JobTracePatch) bool {
   212  		return tracePatch.Offset() == 0 && string(tracePatch.Patch()) == traceMessage
   213  	})
   214  	nextEmptyPatchMatcher := mock.MatchedBy(func(tracePatch common.JobTracePatch) bool {
   215  		return tracePatch.Offset() == len(traceMessage) && string(tracePatch.Patch()) == ""
   216  	})
   217  	firstUpdateMatcher := generateJobInfoMatcher(jobCredentials.ID, common.Running, noTrace, common.NoneFailure)
   218  	secondUpdateMatcher := generateJobInfoMatcherWithAnyTrace(jobCredentials.ID, common.Success, common.NoneFailure)
   219  
   220  	wg.Add(1)
   221  
   222  	net := new(common.MockNetwork)
   223  	net.On("UpdateJob", jobConfig, jobCredentials, firstUpdateMatcher).Return(common.UpdateSucceeded).Once()
   224  	net.On("PatchTrace", jobConfig, jobCredentials, firstPatchMatcher).Return(common.UpdateSucceeded).Once()
   225  	net.On("PatchTrace", jobConfig, jobCredentials, nextEmptyPatchMatcher).Return(common.UpdateSucceeded).Run(func(_ mock.Arguments) { wg.Done() })
   226  	net.On("UpdateJob", jobConfig, jobCredentials, secondUpdateMatcher).Return(common.UpdateSucceeded).Once()
   227  	defer net.AssertExpectations(t)
   228  
   229  	b := newJobTrace(net, jobConfig, jobCredentials)
   230  
   231  	b.updateInterval = 500 * time.Microsecond
   232  	b.forceSendInterval = 4 * b.updateInterval
   233  	b.start()
   234  	defer b.Success()
   235  
   236  	fmt.Fprint(b, traceMessage)
   237  	wg.Wait()
   238  }