github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/job/mem_broker_test.go (about)

     1  package job_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"strconv"
     6  	"strings"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/cozy/cozy-stack/model/job"
    12  	"github.com/cozy/cozy-stack/pkg/config/config"
    13  	"github.com/cozy/cozy-stack/pkg/limits"
    14  	"github.com/cozy/cozy-stack/pkg/prefixer"
    15  	"github.com/cozy/cozy-stack/tests/testutils"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  func TestMemBroker(t *testing.T) {
    20  	if testing.Short() {
    21  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    22  	}
    23  
    24  	config.UseTestFile(t)
    25  	setup := testutils.NewSetup(t, t.Name())
    26  	testInstance := setup.GetTestInstance()
    27  
    28  	t.Run("ProperSerial", func(t *testing.T) {
    29  		j := job.NewJob(prefixer.NewPrefixer(0, "cozy.localhost:8080", "cozy.localhost:8080"),
    30  			&job.JobRequest{
    31  				WorkerType: "",
    32  			})
    33  		assert.NoError(t, j.Create())
    34  		assert.NoError(t, j.AckConsumed())
    35  		job2, err := job.Get(j, j.ID())
    36  		assert.NoError(t, err)
    37  		assert.Equal(t, job.Running, job2.State)
    38  	})
    39  
    40  	t.Run("MessageMarshalling", func(t *testing.T) {
    41  		data := []byte(`{"Data": "InZhbHVlIgo=", "Type": "json"}`)
    42  		var m job.Message
    43  		assert.NoError(t, json.Unmarshal(data, &m))
    44  		var s string
    45  		assert.NoError(t, m.Unmarshal(&s))
    46  		assert.Equal(t, "value", s)
    47  
    48  		data = []byte(`"value2"`)
    49  		assert.NoError(t, json.Unmarshal(data, &m))
    50  		assert.NoError(t, m.Unmarshal(&s))
    51  		assert.Equal(t, "value2", s)
    52  
    53  		data = []byte(`{
    54  		"domain": "cozy.local",
    55  		"worker": "foo",
    56  		"message": {"Data": "InZhbHVlIgo=", "Type": "json"}
    57  }`)
    58  
    59  		var j job.Job
    60  		assert.NoError(t, json.Unmarshal(data, &j))
    61  		assert.Equal(t, "cozy.local", j.Domain)
    62  		assert.Equal(t, "foo", j.WorkerType)
    63  		assert.EqualValues(t, []byte(`"value"`), j.Message)
    64  
    65  		var err error
    66  		var j2 job.Job
    67  		data, err = json.Marshal(j)
    68  		assert.NoError(t, err)
    69  		assert.NoError(t, json.Unmarshal(data, &j2))
    70  		assert.Equal(t, "cozy.local", j2.Domain)
    71  		assert.Equal(t, "foo", j2.WorkerType)
    72  		assert.EqualValues(t, []byte(`"value"`), j2.Message)
    73  
    74  		assert.EqualValues(t, &j2, j2.Clone())
    75  	})
    76  
    77  	t.Run("InMemoryJobs", func(t *testing.T) {
    78  		n := 10
    79  		v := 100
    80  
    81  		var w sync.WaitGroup
    82  
    83  		workersTestList := job.WorkersList{
    84  			{
    85  				WorkerType:  "test",
    86  				Concurrency: 4,
    87  				WorkerFunc: func(ctx *job.TaskContext) error {
    88  					var msg string
    89  					err := ctx.UnmarshalMessage(&msg)
    90  					if !assert.NoError(t, err) {
    91  						return err
    92  					}
    93  					if strings.HasPrefix(msg, "a-") {
    94  						_, err := strconv.Atoi(msg[len("a-"):])
    95  						assert.NoError(t, err)
    96  					} else if strings.HasPrefix(msg, "b-") {
    97  						_, err := strconv.Atoi(msg[len("b-"):])
    98  						assert.NoError(t, err)
    99  					} else {
   100  						t.Fatal()
   101  					}
   102  					w.Done()
   103  					return nil
   104  				},
   105  			},
   106  		}
   107  
   108  		broker1 := job.NewMemBroker()
   109  		broker2 := job.NewMemBroker()
   110  		assert.NoError(t, broker1.StartWorkers(workersTestList))
   111  		assert.NoError(t, broker2.StartWorkers(workersTestList))
   112  		w.Add(2)
   113  
   114  		go func() {
   115  			for i := 0; i < n; i++ {
   116  				w.Add(1)
   117  				msg, _ := job.NewMessage("a-" + strconv.Itoa(i+1))
   118  				_, err := broker1.PushJob(testInstance, &job.JobRequest{
   119  					WorkerType: "test",
   120  					Message:    msg,
   121  				})
   122  				assert.NoError(t, err)
   123  				time.Sleep(randomMicro(0, v))
   124  			}
   125  			w.Done()
   126  		}()
   127  
   128  		go func() {
   129  			for i := 0; i < n; i++ {
   130  				w.Add(1)
   131  				msg, _ := job.NewMessage("b-" + strconv.Itoa(i+1))
   132  				_, err := broker2.PushJob(testInstance, &job.JobRequest{
   133  					WorkerType: "test",
   134  					Message:    msg,
   135  				})
   136  				assert.NoError(t, err)
   137  				time.Sleep(randomMicro(0, v))
   138  			}
   139  			w.Done()
   140  		}()
   141  
   142  		w.Wait()
   143  	})
   144  
   145  	t.Run("UnknownWorkerError", func(t *testing.T) {
   146  		broker := job.NewMemBroker()
   147  		assert.NoError(t, broker.StartWorkers(job.WorkersList{}))
   148  		_, err := broker.PushJob(testInstance, &job.JobRequest{
   149  			WorkerType: "nope",
   150  			Message:    nil,
   151  		})
   152  		assert.Error(t, err)
   153  		assert.Equal(t, job.ErrUnknownWorker, err)
   154  	})
   155  
   156  	t.Run("UnknownMessageType", func(t *testing.T) {
   157  		var w sync.WaitGroup
   158  
   159  		broker := job.NewMemBroker()
   160  		assert.NoError(t, broker.StartWorkers(job.WorkersList{
   161  			{
   162  				WorkerType:  "test",
   163  				Concurrency: 4,
   164  				WorkerFunc: func(ctx *job.TaskContext) error {
   165  					var msg string
   166  					err := ctx.UnmarshalMessage(&msg)
   167  					assert.Error(t, err)
   168  					assert.Equal(t, job.ErrMessageNil, err)
   169  					w.Done()
   170  					return nil
   171  				},
   172  			},
   173  		}))
   174  
   175  		w.Add(1)
   176  		_, err := broker.PushJob(testInstance, &job.JobRequest{
   177  			WorkerType: "test",
   178  			Message:    nil,
   179  		})
   180  
   181  		assert.NoError(t, err)
   182  		w.Wait()
   183  	})
   184  
   185  	t.Run("Timeout", func(t *testing.T) {
   186  		var w sync.WaitGroup
   187  
   188  		broker := job.NewMemBroker()
   189  		assert.NoError(t, broker.StartWorkers(job.WorkersList{
   190  			{
   191  				WorkerType:   "timeout",
   192  				Concurrency:  1,
   193  				MaxExecCount: 1,
   194  				Timeout:      1 * time.Millisecond,
   195  				WorkerFunc: func(ctx *job.TaskContext) error {
   196  					<-ctx.Done()
   197  					w.Done()
   198  					return ctx.Err()
   199  				},
   200  			},
   201  		}))
   202  
   203  		w.Add(1)
   204  		_, err := broker.PushJob(testInstance, &job.JobRequest{
   205  			WorkerType: "timeout",
   206  			Message:    nil,
   207  		})
   208  
   209  		assert.NoError(t, err)
   210  		w.Wait()
   211  	})
   212  
   213  	t.Run("Retry", func(t *testing.T) {
   214  		var w sync.WaitGroup
   215  
   216  		maxExecCount := 4
   217  
   218  		var count int
   219  		broker := job.NewMemBroker()
   220  		assert.NoError(t, broker.StartWorkers(job.WorkersList{
   221  			{
   222  				WorkerType:   "test",
   223  				Concurrency:  1,
   224  				MaxExecCount: maxExecCount,
   225  				Timeout:      1 * time.Millisecond,
   226  				RetryDelay:   1 * time.Millisecond,
   227  				WorkerFunc: func(ctx *job.TaskContext) error {
   228  					<-ctx.Done()
   229  					w.Done()
   230  					count++
   231  					if count < maxExecCount {
   232  						return ctx.Err()
   233  					}
   234  					return nil
   235  				},
   236  			},
   237  		}))
   238  
   239  		w.Add(maxExecCount)
   240  		_, err := broker.PushJob(testInstance, &job.JobRequest{
   241  			WorkerType: "test",
   242  			Message:    nil,
   243  		})
   244  
   245  		assert.NoError(t, err)
   246  		w.Wait()
   247  	})
   248  
   249  	t.Run("PanicRetried", func(t *testing.T) {
   250  		var w sync.WaitGroup
   251  
   252  		maxExecCount := 4
   253  
   254  		broker := job.NewMemBroker()
   255  		assert.NoError(t, broker.StartWorkers(job.WorkersList{
   256  			{
   257  				WorkerType:   "panic",
   258  				Concurrency:  1,
   259  				MaxExecCount: maxExecCount,
   260  				RetryDelay:   1 * time.Millisecond,
   261  				WorkerFunc: func(ctx *job.TaskContext) error {
   262  					w.Done()
   263  					panic("oops")
   264  				},
   265  			},
   266  		}))
   267  
   268  		w.Add(maxExecCount)
   269  		_, err := broker.PushJob(testInstance, &job.JobRequest{
   270  			WorkerType: "panic",
   271  			Message:    nil,
   272  		})
   273  
   274  		assert.NoError(t, err)
   275  		w.Wait()
   276  	})
   277  
   278  	t.Run("Panic", func(t *testing.T) {
   279  		var w sync.WaitGroup
   280  
   281  		even, _ := job.NewMessage(0)
   282  		odd, _ := job.NewMessage(1)
   283  
   284  		broker := job.NewMemBroker()
   285  		assert.NoError(t, broker.StartWorkers(job.WorkersList{
   286  			{
   287  				WorkerType:   "panic2",
   288  				Concurrency:  1,
   289  				MaxExecCount: 1,
   290  				RetryDelay:   1 * time.Millisecond,
   291  				WorkerFunc: func(ctx *job.TaskContext) error {
   292  					var i int
   293  					if err := ctx.UnmarshalMessage(&i); err != nil {
   294  						return err
   295  					}
   296  					if i%2 != 0 {
   297  						panic("oops")
   298  					}
   299  					w.Done()
   300  					return nil
   301  				},
   302  			},
   303  		}))
   304  		w.Add(2)
   305  		var err error
   306  		_, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: odd})
   307  		assert.NoError(t, err)
   308  		_, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: even})
   309  		assert.NoError(t, err)
   310  		_, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: odd})
   311  		assert.NoError(t, err)
   312  		_, err = broker.PushJob(testInstance, &job.JobRequest{WorkerType: "panic2", Message: even})
   313  		assert.NoError(t, err)
   314  		w.Wait()
   315  	})
   316  
   317  	t.Run("MemAddJobRateLimitExceeded", func(t *testing.T) {
   318  		workersTestList := job.WorkersList{
   319  			{
   320  				WorkerType:  "thumbnail",
   321  				Concurrency: 4,
   322  				WorkerFunc: func(ctx *job.TaskContext) error {
   323  					return nil
   324  				},
   325  			},
   326  		}
   327  		broker := job.NewMemBroker()
   328  		err := broker.StartWorkers(workersTestList)
   329  		assert.NoError(t, err)
   330  
   331  		msg, _ := job.NewMessage("z-0")
   332  		j, err := broker.PushJob(testInstance, &job.JobRequest{
   333  			WorkerType: "thumbnail",
   334  			Message:    msg,
   335  		})
   336  
   337  		assert.NoError(t, err)
   338  		assert.NotNil(t, j)
   339  
   340  		ct := limits.JobThumbnailType
   341  		limits.SetMaximumLimit(ct, 10)
   342  		maxLimit := limits.GetMaximumLimit(ct)
   343  		// Blocking the job push
   344  		for i := int64(0); i < maxLimit-1; i++ {
   345  			j, err := broker.PushJob(testInstance, &job.JobRequest{
   346  				WorkerType: "thumbnail",
   347  				Message:    msg,
   348  			})
   349  			assert.NoError(t, err)
   350  			assert.NotNil(t, j)
   351  		}
   352  
   353  		j, err = broker.PushJob(testInstance, &job.JobRequest{
   354  			WorkerType: "thumbnail",
   355  			Message:    msg,
   356  		})
   357  		assert.Error(t, err)
   358  		assert.Nil(t, j)
   359  		assert.ErrorIs(t, err, limits.ErrRateLimitReached)
   360  
   361  		j, err = broker.PushJob(testInstance, &job.JobRequest{
   362  			WorkerType: "thumbnail",
   363  			Message:    msg,
   364  		})
   365  		assert.Error(t, err)
   366  		assert.Nil(t, j)
   367  	})
   368  }