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 }