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  }