github.com/secure-build/gitlab-runner@v12.5.0+incompatible/commands/multi_test.go (about) 1 package commands 2 3 import ( 4 "io/ioutil" 5 "os" 6 "strings" 7 "sync" 8 "sync/atomic" 9 "testing" 10 "time" 11 12 "github.com/gofrs/flock" 13 "github.com/sirupsen/logrus" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 18 "gitlab.com/gitlab-org/gitlab-runner/common" 19 "gitlab.com/gitlab-org/gitlab-runner/helpers" 20 "gitlab.com/gitlab-org/gitlab-runner/log/test" 21 ) 22 23 func TestProcessRunner_BuildLimit(t *testing.T) { 24 hook, cleanup := test.NewHook() 25 defer cleanup() 26 27 logrus.SetLevel(logrus.DebugLevel) 28 logrus.SetOutput(ioutil.Discard) 29 30 cfg := common.RunnerConfig{ 31 Limit: 2, 32 RequestConcurrency: 10, 33 RunnerSettings: common.RunnerSettings{ 34 Executor: "multi-runner-build-limit", 35 }, 36 } 37 38 jobData := common.JobResponse{ 39 ID: 1, 40 Steps: []common.Step{ 41 { 42 Name: "sleep", 43 Script: common.StepScript{"sleep 10"}, 44 Timeout: 15, 45 When: "", 46 AllowFailure: false, 47 }, 48 }, 49 } 50 51 mJobTrace := common.MockJobTrace{} 52 defer mJobTrace.AssertExpectations(t) 53 mJobTrace.On("SetFailuresCollector", mock.Anything) 54 mJobTrace.On("Write", mock.Anything).Return(0, nil) 55 mJobTrace.On("IsStdout").Return(false) 56 mJobTrace.On("SetCancelFunc", mock.Anything) 57 mJobTrace.On("SetMasked", mock.Anything) 58 mJobTrace.On("Success") 59 mJobTrace.On("Fail", mock.Anything, mock.Anything) 60 61 mNetwork := common.MockNetwork{} 62 defer mNetwork.AssertExpectations(t) 63 mNetwork.On("RequestJob", mock.Anything, mock.Anything).Return(&jobData, true) 64 mNetwork.On("ProcessJob", mock.Anything, mock.Anything).Return(&mJobTrace, nil) 65 66 var runningBuilds uint32 67 e := common.MockExecutor{} 68 defer e.AssertExpectations(t) 69 e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil) 70 e.On("Cleanup").Maybe().Return() 71 e.On("Shell").Return(&common.ShellScriptInfo{Shell: "script-shell"}) 72 e.On("Finish", mock.Anything).Return(nil).Maybe() 73 e.On("Run", mock.Anything).Run(func(args mock.Arguments) { 74 atomic.AddUint32(&runningBuilds, 1) 75 76 // Simulate work to fill up build queue. 77 time.Sleep(1 * time.Second) 78 }).Return(nil) 79 80 p := common.MockExecutorProvider{} 81 defer p.AssertExpectations(t) 82 p.On("Acquire", mock.Anything).Return(nil, nil) 83 p.On("Release", mock.Anything, mock.Anything).Return(nil).Maybe() 84 p.On("CanCreate").Return(true).Once() 85 p.On("GetDefaultShell").Return("bash").Once() 86 p.On("GetFeatures", mock.Anything).Return(nil) 87 p.On("Create").Return(&e) 88 89 common.RegisterExecutor("multi-runner-build-limit", &p) 90 91 cmd := RunCommand{ 92 network: &mNetwork, 93 buildsHelper: newBuildsHelper(), 94 configOptionsWithListenAddress: configOptionsWithListenAddress{ 95 configOptions: configOptions{ 96 config: &common.Config{ 97 User: "git", 98 }, 99 }, 100 }, 101 } 102 103 runners := make(chan *common.RunnerConfig) 104 105 // Start 2 builds. 106 wg := sync.WaitGroup{} 107 wg.Add(3) 108 for i := 0; i < 3; i++ { 109 go func(i int) { 110 defer wg.Done() 111 112 err := cmd.processRunner(i, &cfg, runners) 113 assert.NoError(t, err) 114 }(i) 115 } 116 117 // Wait until at least two builds have started. 118 for atomic.LoadUint32(&runningBuilds) < 2 { 119 time.Sleep(10 * time.Millisecond) 120 } 121 122 // Wait for all builds to finish. 123 wg.Wait() 124 125 limitMetCount := 0 126 for _, entry := range hook.AllEntries() { 127 if strings.Contains(entry.Message, "runner limit met") { 128 limitMetCount++ 129 } 130 } 131 132 assert.Equal(t, 1, limitMetCount) 133 } 134 135 func runFileLockingCmd(t *testing.T, wg *sync.WaitGroup, started chan bool, stop chan bool, filePath string, shouldPanic bool) { 136 cmd := &RunCommand{ 137 configOptionsWithListenAddress: configOptionsWithListenAddress{ 138 configOptions: configOptions{ 139 ConfigFile: filePath, 140 config: &common.Config{ 141 Concurrent: 5, 142 }, 143 }, 144 }, 145 stopSignals: make(chan os.Signal), 146 reloadSignal: make(chan os.Signal, 1), 147 runFinished: make(chan bool, 1), 148 } 149 150 go func() { 151 if shouldPanic { 152 assert.Panics(t, cmd.RunWithLock, "Expected the Runner to create a new lock") 153 } else { 154 assert.NotPanics(t, cmd.RunWithLock, "Expected the Runner to reject creating a new lock") 155 } 156 wg.Done() 157 }() 158 159 close(started) 160 161 <-stop 162 cmd.stopSignal = os.Kill 163 } 164 165 func TestMulti_RunWithLock(t *testing.T) { 166 defer helpers.MakeFatalToPanic()() 167 168 file, err := ioutil.TempFile("", "config.toml") 169 require.NoError(t, err) 170 171 err = file.Close() 172 require.NoError(t, err) 173 174 filePath := file.Name() 175 176 wg := new(sync.WaitGroup) 177 stop := make(chan bool) 178 179 wg.Add(2) 180 181 started := make(chan bool) 182 go runFileLockingCmd(t, wg, started, stop, filePath, false) 183 <-started 184 185 time.Sleep(1 * time.Second) 186 187 started = make(chan bool) 188 go runFileLockingCmd(t, wg, started, stop, filePath, true) 189 <-started 190 191 time.Sleep(1 * time.Second) 192 193 close(stop) 194 wg.Wait() 195 196 // Try to lock the file to check if it was properly unlocked while 197 // finishing cmd.RunWithLock() call 198 fl := flock.New(filePath) 199 locked, err := fl.TryLock() 200 defer fl.Unlock() 201 202 assert.True(t, locked, "File was not unlocked!") 203 assert.NoError(t, err) 204 }