From 3817c33f960ef958e57a4dbfb7f79fd25e27591d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 15:53:53 +0800 Subject: [PATCH 01/32] refactor: queue pr 1 --- contracts/queue/driver.go | 18 + contracts/queue/job.go | 7 +- contracts/queue/queue.go | 14 +- contracts/queue/task.go | 2 +- queue/application.go | 44 +- queue/application_test.go | 481 ------------------ queue/config.go | 29 +- queue/driver.go | 55 ++ queue/driver_async.go | 86 ++++ queue/driver_async_test.go | 284 +++++++++++ queue/driver_machinery.go | 85 ++++ queue/{log.go => driver_machinery_log.go} | 2 + ...inery_test.go => driver_machinery_test.go} | 23 +- queue/driver_sync.go | 52 ++ queue/driver_sync_test.go | 107 ++++ queue/job.go | 71 +++ queue/machinery.go | 63 --- queue/service_provider.go | 27 +- queue/task.go | 112 +--- queue/task_test.go | 26 +- queue/utils.go | 10 +- queue/utils_test.go | 8 +- queue/worker.go | 96 ++-- 23 files changed, 950 insertions(+), 752 deletions(-) create mode 100644 contracts/queue/driver.go delete mode 100644 queue/application_test.go create mode 100644 queue/driver.go create mode 100644 queue/driver_async.go create mode 100644 queue/driver_async_test.go create mode 100644 queue/driver_machinery.go rename queue/{log.go => driver_machinery_log.go} (99%) rename queue/{machinery_test.go => driver_machinery_test.go} (76%) create mode 100644 queue/driver_sync.go create mode 100644 queue/driver_sync_test.go create mode 100644 queue/job.go delete mode 100644 queue/machinery.go diff --git a/contracts/queue/driver.go b/contracts/queue/driver.go new file mode 100644 index 000000000..29ef603bc --- /dev/null +++ b/contracts/queue/driver.go @@ -0,0 +1,18 @@ +package queue + +import "time" + +type Driver interface { + // Connection returns the connection name for the driver. + Connection() string + // Driver returns the driver name for the driver. + Driver() string + // Push pushes the job onto the queue. + Push(job Job, args []any, queue string) error + // Bulk pushes a slice of jobs onto the queue. + Bulk(jobs []Jobs, queue string) error + // Later pushes the job onto the queue after a delay. + Later(delay time.Duration, job Job, args []any, queue string) error + // Pop pops the next job off of the queue. + Pop(queue string) (Job, []any, error) +} diff --git a/contracts/queue/job.go b/contracts/queue/job.go index 9cfcf9f3c..8eff008cc 100644 --- a/contracts/queue/job.go +++ b/contracts/queue/job.go @@ -1,5 +1,7 @@ package queue +import "time" + type Job interface { // Signature set the unique signature of the job. Signature() string @@ -8,6 +10,7 @@ type Job interface { } type Jobs struct { - Job Job - Args []Arg + Job Job + Args []any + Delay time.Duration } diff --git a/contracts/queue/queue.go b/contracts/queue/queue.go index 1662509b4..1a7111429 100644 --- a/contracts/queue/queue.go +++ b/contracts/queue/queue.go @@ -1,19 +1,22 @@ package queue type Queue interface { - Worker(args ...Args) Worker + Worker(payloads ...*Args) Worker // Register register jobs - Register(jobs []Job) + Register(jobs []Job) error // GetJobs get all jobs GetJobs() []Job + // GetJob get job by signature + GetJob(signature string) (Job, error) // Job add a job to queue - Job(job Job, args []Arg) Task + Job(job Job, args []any) Task // Chain creates a chain of jobs to be processed one by one, passing Chain(jobs []Jobs) Task } type Worker interface { Run() error + Shutdown() error } type Args struct { @@ -24,8 +27,3 @@ type Args struct { // Concurrent num Concurrent int } - -type Arg struct { - Type string - Value any -} diff --git a/contracts/queue/task.go b/contracts/queue/task.go index ff7a1df5b..46ff8f2fc 100644 --- a/contracts/queue/task.go +++ b/contracts/queue/task.go @@ -10,7 +10,7 @@ type Task interface { // DispatchSync dispatches the task synchronously. DispatchSync() error // Delay dispatches the task after the given delay. - Delay(time time.Time) Task + Delay(time time.Duration) Task // OnConnection sets the connection of the task. OnConnection(connection string) Task // OnQueue sets the queue of the task. diff --git a/queue/application.go b/queue/application.go index 52764f06e..7c13c4792 100644 --- a/queue/application.go +++ b/queue/application.go @@ -2,49 +2,57 @@ package queue import ( configcontract "github.com/goravel/framework/contracts/config" - "github.com/goravel/framework/contracts/log" "github.com/goravel/framework/contracts/queue" ) type Application struct { config *Config - jobs []queue.Job - log log.Log + job *JobImpl } -func NewApplication(config configcontract.Config, log log.Log) *Application { +func NewApplication(config configcontract.Config) *Application { return &Application{ config: NewConfig(config), - log: log, + job: NewJobImpl(), } } -func (app *Application) Worker(args ...queue.Args) queue.Worker { +func (app *Application) Worker(payloads ...*queue.Args) queue.Worker { defaultConnection := app.config.DefaultConnection() - if len(args) == 0 { - return NewWorker(app.config, app.log, 1, defaultConnection, app.jobs, app.config.Queue(defaultConnection, "")) + if len(payloads) == 0 || payloads[0] == nil { + return NewWorker(app.config, 1, defaultConnection, app.config.Queue(defaultConnection, ""), app.job) } - - if args[0].Connection == "" { - args[0].Connection = defaultConnection + if payloads[0].Connection == "" { + payloads[0].Connection = defaultConnection + } + if payloads[0].Concurrent == 0 { + payloads[0].Concurrent = 1 } - return NewWorker(app.config, app.log, args[0].Concurrent, args[0].Connection, app.jobs, app.config.Queue(args[0].Connection, args[0].Queue)) + return NewWorker(app.config, payloads[0].Concurrent, payloads[0].Connection, app.config.Queue(payloads[0].Connection, payloads[0].Queue), app.job) } -func (app *Application) Register(jobs []queue.Job) { - app.jobs = append(app.jobs, jobs...) +func (app *Application) Register(jobs []queue.Job) error { + if err := app.job.Register(jobs); err != nil { + return err + } + + return nil } func (app *Application) GetJobs() []queue.Job { - return app.jobs + return app.job.GetJobs() +} + +func (app *Application) GetJob(signature string) (queue.Job, error) { + return app.job.Get(signature) } -func (app *Application) Job(job queue.Job, args []queue.Arg) queue.Task { - return NewTask(app.config, app.log, job, args) +func (app *Application) Job(job queue.Job, args []any) queue.Task { + return NewTask(app.config, job, args) } func (app *Application) Chain(jobs []queue.Jobs) queue.Task { - return NewChainTask(app.config, app.log, jobs) + return NewChainTask(app.config, jobs) } diff --git a/queue/application_test.go b/queue/application_test.go deleted file mode 100644 index 3384dd6aa..000000000 --- a/queue/application_test.go +++ /dev/null @@ -1,481 +0,0 @@ -package queue - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/spf13/cast" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/suite" - - "github.com/goravel/framework/contracts/queue" - configmock "github.com/goravel/framework/mocks/config" - logmock "github.com/goravel/framework/mocks/log" - "github.com/goravel/framework/support/carbon" - testingdocker "github.com/goravel/framework/support/docker" - "github.com/goravel/framework/support/env" -) - -var ( - testSyncJob = 0 - testAsyncJob = 0 - testAsyncJobOfDisableDebug = 0 - testDelayAsyncJob = 0 - testCustomAsyncJob = 0 - testErrorAsyncJob = 0 - testChainAsyncJob = 0 - testChainSyncJob = 0 - testChainAsyncJobError = 0 - testChainSyncJobError = 0 -) - -type QueueTestSuite struct { - suite.Suite - app *Application - mockConfig *configmock.Config - mockLog *logmock.Log - port int -} - -func TestQueueTestSuite(t *testing.T) { - if env.IsWindows() { - t.Skip("Skip test that using Docker") - } - - redisDocker := testingdocker.NewRedis() - assert.Nil(t, redisDocker.Build()) - - suite.Run(t, &QueueTestSuite{ - port: redisDocker.Config().Port, - }) - - assert.Nil(t, redisDocker.Shutdown()) -} - -func (s *QueueTestSuite) SetupTest() { - s.mockConfig = &configmock.Config{} - s.mockLog = &logmock.Log{} - s.app = NewApplication(s.mockConfig, s.mockLog) -} - -func (s *QueueTestSuite) TestSyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Once() - s.Nil(s.app.Job(&TestSyncJob{}, []queue.Arg{ - {Type: "string", Value: "TestSyncQueue"}, - {Type: "int", Value: 1}, - }).DispatchSync()) - s.Equal(1, testSyncJob) -} - -func (s *QueueTestSuite) TestDefaultAsyncQueue_EnableDebug() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(true).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.mockLog.On("Infof", "Launching a worker with the following settings:").Once() - s.mockLog.On("Infof", "- Broker: %s", "://").Once() - s.mockLog.On("Infof", "- DefaultQueue: %s", "goravel_queues:debug").Once() - s.mockLog.On("Infof", "- ResultBackend: %s", "://").Once() - s.mockLog.On("Info", "[*] Waiting for messages. To exit press CTRL+C").Once() - s.mockLog.On("Debugf", "Received new message: %s", mock.Anything).Once() - s.mockLog.On("Debugf", "Processed task %s. Results = %s", mock.Anything, mock.Anything).Once() - s.app.jobs = []queue.Job{&TestAsyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Queue: "debug", - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - time.Sleep(2 * time.Second) - s.Nil(s.app.Job(&TestAsyncJob{}, []queue.Arg{ - {Type: "string", Value: "TestDefaultAsyncQueue_EnableDebug"}, - {Type: "int", Value: 1}, - }).OnQueue("debug").Dispatch()) - time.Sleep(2 * time.Second) - s.Equal(1, testAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) - s.mockLog.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestDefaultAsyncQueue_DisableDebug() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.app.jobs = []queue.Job{&TestAsyncJobOfDisableDebug{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker().Run()) - - for range ctx.Done() { - return - } - }(ctx) - time.Sleep(2 * time.Second) - s.Nil(s.app.Job(&TestAsyncJobOfDisableDebug{}, []queue.Arg{ - {Type: "string", Value: "TestDefaultAsyncQueue_DisableDebug"}, - {Type: "int", Value: 1}, - }).Dispatch()) - time.Sleep(2 * time.Second) - s.Equal(1, testAsyncJobOfDisableDebug) - - s.mockConfig.AssertExpectations(s.T()) - s.mockLog.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestDelayAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.app.jobs = []queue.Job{&TestDelayAsyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Queue: "delay", - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - time.Sleep(2 * time.Second) - s.Nil(s.app.Job(&TestDelayAsyncJob{}, []queue.Arg{ - {Type: "string", Value: "TestDelayAsyncQueue"}, - {Type: "int", Value: 1}, - }).OnQueue("delay").Delay(carbon.Now().AddSeconds(3).StdTime()).Dispatch()) - time.Sleep(2 * time.Second) - s.Equal(0, testDelayAsyncJob) - time.Sleep(3 * time.Second) - s.Equal(1, testDelayAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestCustomAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.custom.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.app.jobs = []queue.Job{&TestCustomAsyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Connection: "custom", - Queue: "custom1", - Concurrent: 2, - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - time.Sleep(2 * time.Second) - s.Nil(s.app.Job(&TestCustomAsyncJob{}, []queue.Arg{ - {Type: "string", Value: "TestCustomAsyncQueue"}, - {Type: "int", Value: 1}, - }).OnConnection("custom").OnQueue("custom1").Dispatch()) - time.Sleep(2 * time.Second) - s.Equal(1, testCustomAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestErrorAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.app.jobs = []queue.Job{&TestErrorAsyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Queue: "error", - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - time.Sleep(2 * time.Second) - s.Nil(s.app.Job(&TestErrorAsyncJob{}, []queue.Arg{ - {Type: "string", Value: "TestErrorAsyncQueue"}, - {Type: "int", Value: 1}, - }).OnConnection("redis").OnQueue("error1").Dispatch()) - time.Sleep(2 * time.Second) - s.Equal(0, testErrorAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestChainAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.app.jobs = []queue.Job{&TestChainAsyncJob{}, &TestChainSyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Queue: "chain", - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - - time.Sleep(2 * time.Second) - s.Nil(s.app.Chain([]queue.Jobs{ - { - Job: &TestChainAsyncJob{}, - Args: []queue.Arg{ - {Type: "string", Value: "TestChainAsyncQueue"}, - {Type: "int", Value: 1}, - }, - }, - { - Job: &TestChainSyncJob{}, - Args: []queue.Arg{ - {Type: "string", Value: "TestChainSyncQueue"}, - {Type: "int", Value: 1}, - }, - }, - }).OnQueue("chain").Dispatch()) - - time.Sleep(2 * time.Second) - s.Equal(1, testChainAsyncJob) - s.Equal(1, testChainSyncJob) - - s.mockConfig.AssertExpectations(s.T()) -} - -func (s *QueueTestSuite) TestChainAsyncQueue_Error() { - s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) - s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() - s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() - s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() - s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() - s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() - s.mockLog.On("Errorf", "Failed processing task %s. Error = %v", mock.Anything, errors.New("error")).Once() - s.app.jobs = []queue.Job{&TestChainAsyncJob{}, &TestChainSyncJob{}} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - go func(ctx context.Context) { - s.Nil(s.app.Worker(queue.Args{ - Queue: "chain", - }).Run()) - - for range ctx.Done() { - return - } - }(ctx) - - time.Sleep(2 * time.Second) - s.Nil(s.app.Chain([]queue.Jobs{ - { - Job: &TestChainAsyncJob{}, - Args: []queue.Arg{ - {Type: "bool", Value: true}, - }, - }, - { - Job: &TestChainSyncJob{}, - Args: []queue.Arg{}, - }, - }).OnQueue("chain").Dispatch()) - - time.Sleep(2 * time.Second) - s.Equal(1, testChainAsyncJobError) - s.Equal(0, testChainSyncJobError) - - s.mockConfig.AssertExpectations(s.T()) - s.mockLog.AssertExpectations(s.T()) -} - -type TestAsyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestAsyncJob) Signature() string { - return "test_async_job" -} - -// Handle Execute the job. -func (receiver *TestAsyncJob) Handle(args ...any) error { - testAsyncJob++ - - return nil -} - -type TestAsyncJobOfDisableDebug struct { -} - -// Signature The name and signature of the job. -func (receiver *TestAsyncJobOfDisableDebug) Signature() string { - return "test_async_job_of_disable_debug" -} - -// Handle Execute the job. -func (receiver *TestAsyncJobOfDisableDebug) Handle(args ...any) error { - testAsyncJobOfDisableDebug++ - - return nil -} - -type TestDelayAsyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestDelayAsyncJob) Signature() string { - return "test_delay_async_job" -} - -// Handle Execute the job. -func (receiver *TestDelayAsyncJob) Handle(args ...any) error { - testDelayAsyncJob++ - - return nil -} - -type TestSyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestSyncJob) Signature() string { - return "test_sync_job" -} - -// Handle Execute the job. -func (receiver *TestSyncJob) Handle(args ...any) error { - testSyncJob++ - - return nil -} - -type TestCustomAsyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestCustomAsyncJob) Signature() string { - return "test_async_job" -} - -// Handle Execute the job. -func (receiver *TestCustomAsyncJob) Handle(args ...any) error { - testCustomAsyncJob++ - - return nil -} - -type TestErrorAsyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestErrorAsyncJob) Signature() string { - return "test_async_job" -} - -// Handle Execute the job. -func (receiver *TestErrorAsyncJob) Handle(args ...any) error { - testErrorAsyncJob++ - - return nil -} - -type TestChainAsyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestChainAsyncJob) Signature() string { - return "test_async_job" -} - -// Handle Execute the job. -func (receiver *TestChainAsyncJob) Handle(args ...any) error { - if len(args) > 0 && cast.ToBool(args[0]) { - testChainAsyncJobError++ - - return errors.New("error") - } - - testChainAsyncJob++ - - return nil -} - -type TestChainSyncJob struct { -} - -// Signature The name and signature of the job. -func (receiver *TestChainSyncJob) Signature() string { - return "test_sync_job" -} - -// Handle Execute the job. -func (receiver *TestChainSyncJob) Handle(args ...any) error { - testChainSyncJob++ - - return nil -} diff --git a/queue/config.go b/queue/config.go index f26c13b6c..b749086e5 100644 --- a/queue/config.go +++ b/queue/config.go @@ -4,6 +4,7 @@ import ( "fmt" configcontract "github.com/goravel/framework/contracts/config" + "github.com/goravel/framework/contracts/database/orm" ) type Config struct { @@ -32,17 +33,19 @@ func (r *Config) Queue(connection, queue string) string { queue = r.config.GetString(fmt.Sprintf("queue.connections.%s.queue", connection), "default") } - return fmt.Sprintf("%s_%s:%s", appName, "queues", queue) + return fmt.Sprintf("%s_queues:%s", appName, queue) } func (r *Config) Driver(connection string) string { if connection == "" { - connection = r.config.GetString("queue.default") + connection = r.DefaultConnection() } return r.config.GetString(fmt.Sprintf("queue.connections.%s.driver", connection)) } +// Redis returns the Redis configuration for a given connection. +// TODO: Will be removed in v1.17 func (r *Config) Redis(queueConnection string) (dsn string, database int, queue string) { connection := r.config.GetString(fmt.Sprintf("queue.connections.%s.connection", queueConnection)) queue = r.Queue(queueConnection, "") @@ -59,3 +62,25 @@ func (r *Config) Redis(queueConnection string) (dsn string, database int, queue return } + +func (r *Config) Size(connection string) int { + if connection == "" { + connection = r.DefaultConnection() + } + + return r.config.GetInt(fmt.Sprintf("queue.connections.%s.size", connection), 100) +} + +func (r *Config) Via(connection string) any { + if connection == "" { + connection = r.DefaultConnection() + } + + return r.config.Get(fmt.Sprintf("queue.connections.%s.via", connection)) +} + +func (r *Config) FailedJobsQuery() orm.Query { + connection := r.config.GetString("queue.failed.database") + table := r.config.GetString("queue.failed.table") + return OrmFacade.Connection(connection).Query().Table(table) +} diff --git a/queue/driver.go b/queue/driver.go new file mode 100644 index 000000000..4d1640623 --- /dev/null +++ b/queue/driver.go @@ -0,0 +1,55 @@ +package queue + +import ( + "fmt" + + "github.com/goravel/framework/contracts/queue" +) + +const DriverSync string = "sync" +const DriverASync string = "async" +const DriverMachinery string = "machinery" // TODO: Will be removed in v1.17 +const DriverCustom string = "custom" + +type Driver interface { + New(store string) (queue.Driver, error) +} + +type DriverImpl struct { + connection string + config *Config +} + +func NewDriverImpl(connection string, config *Config) *DriverImpl { + return &DriverImpl{ + connection: connection, + config: config, + } +} + +func (d *DriverImpl) New() (queue.Driver, error) { + switch d.config.Driver(d.connection) { + case DriverSync: + return NewSync(d.connection), nil + case DriverASync: + return NewASync(d.connection, d.config.Size(d.connection)), nil + case DriverMachinery: + return NewMachinery(d.connection, d.config, LogFacade), nil // TODO: Will be removed in v1.17 + case DriverCustom: + return d.custom(d.connection) + default: + return nil, fmt.Errorf("invalid driver: %s, only support sync, async, custom\n", d.connection) + } +} + +func (d *DriverImpl) custom(connection string) (queue.Driver, error) { + custom := d.config.Via(connection) + if driver, ok := custom.(queue.Driver); ok { + return driver, nil + } + if driver, ok := custom.(func() (queue.Driver, error)); ok { + return driver() + } + + return nil, fmt.Errorf("%s doesn't implement contracts/queue/driver\n", connection) +} diff --git a/queue/driver_async.go b/queue/driver_async.go new file mode 100644 index 000000000..b1ac44959 --- /dev/null +++ b/queue/driver_async.go @@ -0,0 +1,86 @@ +package queue + +import ( + "fmt" + "sync" + "time" + + contractsqueue "github.com/goravel/framework/contracts/queue" +) + +var asyncQueues sync.Map + +type ASync struct { + connection string + size int +} + +func NewASync(connection string, size int) *ASync { + return &ASync{ + connection: connection, + size: size, + } +} + +func (r *ASync) Connection() string { + return r.connection +} + +func (r *ASync) Driver() string { + return DriverASync +} + +func (r *ASync) Push(job contractsqueue.Job, args []any, queue string) error { + r.getQueue(queue) <- contractsqueue.Jobs{Job: job, Args: args} + return nil +} + +func (r *ASync) Bulk(jobs []contractsqueue.Jobs, queue string) error { + for _, job := range jobs { + if job.Delay > 0 { + go func(j contractsqueue.Jobs) { + time.Sleep(j.Delay) + r.getQueue(queue) <- j + }(job) + continue + } + + r.getQueue(queue) <- job + } + + return nil +} + +func (r *ASync) Later(delay time.Duration, job contractsqueue.Job, args []any, queue string) error { + go func() { + time.Sleep(delay) + r.getQueue(queue) <- contractsqueue.Jobs{Job: job, Args: args} + }() + + return nil +} + +func (r *ASync) Pop(queue string) (contractsqueue.Job, []any, error) { + ch, ok := asyncQueues.Load(queue) + if !ok { + return nil, nil, fmt.Errorf("no queue found: %s", queue) + } + + queueChan := ch.(chan contractsqueue.Jobs) + select { + case job := <-queueChan: + return job.Job, job.Args, nil + default: + return nil, nil, fmt.Errorf("no job found in %s queue", queue) + } +} + +func (r *ASync) getQueue(queue string) chan contractsqueue.Jobs { + ch, ok := asyncQueues.Load(queue) + if !ok { + ch = make(chan contractsqueue.Jobs, r.size) + actual, _ := asyncQueues.LoadOrStore(queue, ch) + return actual.(chan contractsqueue.Jobs) + } + return ch.(chan contractsqueue.Jobs) +} diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go new file mode 100644 index 000000000..715cca969 --- /dev/null +++ b/queue/driver_async_test.go @@ -0,0 +1,284 @@ +package queue + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/contracts/queue" + configmock "github.com/goravel/framework/mocks/config" + ormmock "github.com/goravel/framework/mocks/database/orm" + queuemock "github.com/goravel/framework/mocks/queue" +) + +var ( + testAsyncJob = 0 + testDelayAsyncJob = 0 + testCustomAsyncJob = 0 + testErrorAsyncJob = 0 + testChainAsyncJob = 0 +) + +type DriverAsyncTestSuite struct { + suite.Suite + app *Application + mockConfig *configmock.Config + mockQueue *queuemock.Queue +} + +func TestDriverAsyncTestSuite(t *testing.T) { + mockConfig := &configmock.Config{} + mockQueue := &queuemock.Queue{} + app := NewApplication(mockConfig) + + mockOrm := &ormmock.Orm{} + mockQuery := &ormmock.Query{} + mockOrm.On("Connection", "database").Return(mockOrm) + mockOrm.On("Query").Return(mockQuery) + mockQuery.On("Table", "failed_jobs").Return(mockQuery) + + OrmFacade = mockOrm + + assert.Nil(t, app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}})) + suite.Run(t, &DriverAsyncTestSuite{ + app: app, + mockConfig: mockConfig, + mockQueue: mockQueue, + }) +} + +func (s *DriverAsyncTestSuite) SetupTest() { + testAsyncJob = 0 +} + +func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) + s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() + s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + worker := s.app.Worker(nil) + s.Nil(worker.Run()) + + <-ctx.Done() + s.Nil(worker.Shutdown()) + }(ctx) + time.Sleep(1 * time.Second) + s.Nil(s.app.Job(&TestAsyncJob{}, []any{"TestDefaultAsyncQueue", 1}).Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(1, testAsyncJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockQueue.AssertExpectations(s.T()) +} + +func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() + s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + worker := s.app.Worker(&queue.Args{ + Queue: "delay", + }) + s.Nil(worker.Run()) + + <-ctx.Done() + s.Nil(worker.Shutdown()) + }(ctx) + time.Sleep(1 * time.Second) + s.Nil(s.app.Job(&TestDelayAsyncJob{}, []any{"TestDelayAsyncQueue", 1}).OnQueue("delay").Delay(3).Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(0, testDelayAsyncJob) + time.Sleep(3 * time.Second) + s.Equal(1, testDelayAsyncJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockQueue.AssertExpectations(s.T()) +} + +func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("custom").Times(4) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() + s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + worker := s.app.Worker(&queue.Args{ + Connection: "custom", + Queue: "custom1", + Concurrent: 2, + }) + s.Nil(worker.Run()) + + <-ctx.Done() + s.Nil(worker.Shutdown()) + }(ctx) + time.Sleep(1 * time.Second) + s.Nil(s.app.Job(&TestCustomAsyncJob{}, []any{"TestCustomAsyncQueue", 1}).OnConnection("custom").OnQueue("custom1").Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(1, testCustomAsyncJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockQueue.AssertExpectations(s.T()) +} + +func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Once() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Once() + s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() + s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + worker := s.app.Worker(&queue.Args{ + Queue: "error", + }) + s.Nil(worker.Run()) + + <-ctx.Done() + s.Nil(worker.Shutdown()) + }(ctx) + time.Sleep(1 * time.Second) + s.Error(s.app.Job(&TestErrorAsyncJob{}, []any{"TestErrorAsyncQueue", 1}).OnConnection("redis").OnQueue("error1").Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(0, testErrorAsyncJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockQueue.AssertExpectations(s.T()) +} + +func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() + s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + worker := s.app.Worker(&queue.Args{ + Queue: "chain", + }) + s.Nil(worker.Run()) + + <-ctx.Done() + s.Nil(worker.Shutdown()) + }(ctx) + + time.Sleep(1 * time.Second) + s.Nil(s.app.Chain([]queue.Jobs{ + { + Job: &TestChainAsyncJob{}, + Args: []any{"TestChainAsyncJob", 1}, + }, + { + Job: &TestAsyncJob{}, + Args: []any{"TestAsyncJob", 1}, + }, + }).OnQueue("chain").Dispatch()) + + time.Sleep(2 * time.Second) + s.Equal(1, testChainAsyncJob) + s.Equal(1, testAsyncJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +type TestAsyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestAsyncJob) Signature() string { + return "test_async_job" +} + +// Handle Execute the job. +func (receiver *TestAsyncJob) Handle(args ...any) error { + testAsyncJob++ + + return nil +} + +type TestDelayAsyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestDelayAsyncJob) Signature() string { + return "test_delay_async_job" +} + +// Handle Execute the job. +func (receiver *TestDelayAsyncJob) Handle(args ...any) error { + testDelayAsyncJob++ + + return nil +} + +type TestCustomAsyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestCustomAsyncJob) Signature() string { + return "test_custom_async_job" +} + +// Handle Execute the job. +func (receiver *TestCustomAsyncJob) Handle(args ...any) error { + testCustomAsyncJob++ + + return nil +} + +type TestErrorAsyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestErrorAsyncJob) Signature() string { + return "test_error_async_job" +} + +// Handle Execute the job. +func (receiver *TestErrorAsyncJob) Handle(args ...any) error { + testErrorAsyncJob++ + + return nil +} + +type TestChainAsyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestChainAsyncJob) Signature() string { + return "test_chain_async_job" +} + +// Handle Execute the job. +func (receiver *TestChainAsyncJob) Handle(args ...any) error { + testChainAsyncJob++ + + return nil +} diff --git a/queue/driver_machinery.go b/queue/driver_machinery.go new file mode 100644 index 000000000..ce78bbf52 --- /dev/null +++ b/queue/driver_machinery.go @@ -0,0 +1,85 @@ +// TODO: Will be removed in v1.17 + +package queue + +import ( + "time" + + "github.com/RichardKnop/machinery/v2" + redisbackend "github.com/RichardKnop/machinery/v2/backends/redis" + redisbroker "github.com/RichardKnop/machinery/v2/brokers/redis" + "github.com/RichardKnop/machinery/v2/config" + "github.com/RichardKnop/machinery/v2/locks/eager" + "github.com/RichardKnop/machinery/v2/log" + + logcontract "github.com/goravel/framework/contracts/log" + "github.com/goravel/framework/contracts/queue" +) + +type Machinery struct { + connection string + config *Config + log logcontract.Log +} + +func NewMachinery(connection string, config *Config, log logcontract.Log) *Machinery { + return &Machinery{ + connection: connection, + config: config, + log: log, + } +} + +func (m *Machinery) Connection() string { + return m.connection +} + +func (m *Machinery) Driver() string { + //TODO implement me + panic("implement me") +} + +func (m *Machinery) Push(job queue.Job, args []any, queue string) error { + //TODO implement me + panic("implement me") +} + +func (m *Machinery) Bulk(jobs []queue.Jobs, queue string) error { + //TODO implement me + panic("implement me") +} + +func (m *Machinery) Later(delay time.Duration, job queue.Job, args []any, queue string) error { + //TODO implement me + panic("implement me") +} + +func (m *Machinery) Pop(queue string) (queue.Job, []any, error) { + //TODO implement me + panic("implement me") +} + +func (m *Machinery) server(queue string) *machinery.Server { + redisConfig, database, defaultQueue := m.config.Redis(m.connection) + if queue == "" { + queue = defaultQueue + } + + cnf := &config.Config{ + DefaultQueue: queue, + Redis: &config.RedisConfig{}, + } + + broker := redisbroker.NewGR(cnf, []string{redisConfig}, database) + backend := redisbackend.NewGR(cnf, []string{redisConfig}, database) + lock := eager.New() + + debug := m.config.config.GetBool("app.debug") + log.DEBUG = NewDebug(debug, m.log) + log.INFO = NewInfo(debug, m.log) + log.WARNING = NewWarning(debug, m.log) + log.ERROR = NewError(debug, m.log) + log.FATAL = NewFatal(debug, m.log) + + return machinery.NewServer(cnf, broker, backend, lock) +} diff --git a/queue/log.go b/queue/driver_machinery_log.go similarity index 99% rename from queue/log.go rename to queue/driver_machinery_log.go index 898b5bb54..5d26a2d27 100644 --- a/queue/log.go +++ b/queue/driver_machinery_log.go @@ -1,3 +1,5 @@ +// TODO: Will be removed in v1.17 + package queue import ( diff --git a/queue/machinery_test.go b/queue/driver_machinery_test.go similarity index 76% rename from queue/machinery_test.go rename to queue/driver_machinery_test.go index ae59b556f..105198d50 100644 --- a/queue/machinery_test.go +++ b/queue/driver_machinery_test.go @@ -1,3 +1,5 @@ +// TODO: Will be removed in v1.17 + package queue import ( @@ -23,7 +25,6 @@ func TestMachineryTestSuite(t *testing.T) { func (s *MachineryTestSuite) SetupTest() { s.mockConfig = &configmock.Config{} s.mockLog = &logmock.Log{} - s.machinery = NewMachinery(NewConfig(s.mockConfig), s.mockLog) } func (s *MachineryTestSuite) TestServer() { @@ -35,13 +36,6 @@ func (s *MachineryTestSuite) TestServer() { expectServer bool expectErr bool }{ - { - name: "sync", - connection: "sync", - setup: func() { - s.mockConfig.On("GetString", "queue.connections.sync.driver").Return("sync").Once() - }, - }, { name: "redis", connection: "redis", @@ -58,23 +52,14 @@ func (s *MachineryTestSuite) TestServer() { }, expectServer: true, }, - { - name: "error", - connection: "custom", - setup: func() { - s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("custom").Once() - - }, - expectErr: true, - }, } for _, test := range tests { s.Run(test.name, func() { + s.machinery = NewMachinery(test.connection, NewConfig(s.mockConfig), s.mockLog) test.setup() - server, err := s.machinery.Server(test.connection, test.queue) + server := s.machinery.server(test.queue) s.Equal(test.expectServer, server != nil) - s.Equal(test.expectErr, err != nil) s.mockConfig.AssertExpectations(s.T()) }) } diff --git a/queue/driver_sync.go b/queue/driver_sync.go new file mode 100644 index 000000000..f4a43b9c6 --- /dev/null +++ b/queue/driver_sync.go @@ -0,0 +1,52 @@ +package queue + +import ( + "time" + + "github.com/goravel/framework/contracts/queue" +) + +type Sync struct { + connection string +} + +func NewSync(connection string) *Sync { + return &Sync{ + connection: connection, + } +} + +func (r *Sync) Connection() string { + return r.connection +} + +func (r *Sync) Driver() string { + return DriverSync +} + +func (r *Sync) Push(job queue.Job, args []any, _ string) error { + return job.Handle(args...) +} + +func (r *Sync) Bulk(jobs []queue.Jobs, _ string) error { + for _, job := range jobs { + if job.Delay > 0 { + time.Sleep(job.Delay) + } + if err := job.Job.Handle(job.Args...); err != nil { + return err + } + } + + return nil +} + +func (r *Sync) Later(delay time.Duration, job queue.Job, args []any, _ string) error { + time.Sleep(delay) + return job.Handle(args...) +} + +func (r *Sync) Pop(_ string) (queue.Job, []any, error) { + // sync driver does not support pop + return nil, nil, nil +} diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go new file mode 100644 index 000000000..5c407157f --- /dev/null +++ b/queue/driver_sync_test.go @@ -0,0 +1,107 @@ +package queue + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/contracts/queue" + configmock "github.com/goravel/framework/mocks/config" + queuemock "github.com/goravel/framework/mocks/queue" +) + +var ( + testSyncJob = 0 + testChainSyncJob = 0 +) + +type DriverSyncTestSuite struct { + suite.Suite + app *Application + mockConfig *configmock.Config + mockQueue *queuemock.Queue +} + +func TestDriverSyncTestSuite(t *testing.T) { + mockConfig := &configmock.Config{} + mockQueue := &queuemock.Queue{} + app := NewApplication(mockConfig) + + assert.Nil(t, app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}})) + suite.Run(t, &DriverSyncTestSuite{ + app: app, + mockConfig: mockConfig, + mockQueue: mockQueue, + }) +} + +func (s *DriverSyncTestSuite) SetupTest() { + testSyncJob = 0 + testChainSyncJob = 0 +} + +func (s *DriverSyncTestSuite) TestSyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("sync").Times(3) + s.mockConfig.On("GetString", "app.name").Return("goravel").Once() + s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() + + s.Nil(s.app.Job(&TestSyncJob{}, []any{"TestSyncQueue", 1}).DispatchSync()) + s.Equal(1, testSyncJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +func (s *DriverSyncTestSuite) TestChainSyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("sync").Times(3) + s.mockConfig.On("GetString", "app.name").Return("goravel").Twice() + s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.connections.sync.driver").Return("sync").Once() + + s.Nil(s.app.Chain([]queue.Jobs{ + { + Job: &TestChainSyncJob{}, + Args: []any{"TestChainSyncJob", 1}, + }, + { + Job: &TestSyncJob{}, + Args: []any{"TestSyncJob", 1}, + }, + }).OnQueue("chain").Dispatch()) + + time.Sleep(2 * time.Second) + s.Equal(1, testChainSyncJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +type TestSyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestSyncJob) Signature() string { + return "test_sync_job" +} + +// Handle Execute the job. +func (receiver *TestSyncJob) Handle(args ...any) error { + testSyncJob++ + + return nil +} + +type TestChainSyncJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestChainSyncJob) Signature() string { + return "test_chain_sync_job" +} + +// Handle Execute the job. +func (receiver *TestChainSyncJob) Handle(args ...any) error { + testChainSyncJob++ + + return nil +} diff --git a/queue/job.go b/queue/job.go new file mode 100644 index 000000000..6607683cb --- /dev/null +++ b/queue/job.go @@ -0,0 +1,71 @@ +package queue + +import ( + "sync" + + contractsqueue "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/support/carbon" +) + +type FailedJob struct { + ID uint `gorm:"primaryKey"` // The unique ID of the job. + Queue string `gorm:"not null"` // The name of the queue the job belongs to. + Signature string `gorm:"not null"` // The signature of the handler for this job. + Payloads []any `gorm:"not null;serializer:json"` // The arguments passed to the job. + Exception string `gorm:"not null"` // The exception that caused the job to fail. + FailedAt carbon.DateTime `gorm:"not null"` // The timestamp when the job failed. +} + +type Job interface { + Register(jobs []contractsqueue.Job) error + Call(signature string, args []any) error + Get(signature string) (contractsqueue.Job, error) + GetJobs() []contractsqueue.Job +} + +type JobImpl struct { + jobs sync.Map +} + +func NewJobImpl() *JobImpl { + return &JobImpl{} +} + +// Register registers jobs to the job manager +func (r *JobImpl) Register(jobs []contractsqueue.Job) error { + for _, job := range jobs { + r.jobs.Store(job.Signature(), job) + } + + return nil +} + +// Call calls a registered job using its signature +func (r *JobImpl) Call(signature string, args []any) error { + job, err := r.Get(signature) + if err != nil { + return err + } + + return job.Handle(args...) +} + +// Get gets a registered job using its signature +func (r *JobImpl) Get(signature string) (contractsqueue.Job, error) { + if job, ok := r.jobs.Load(signature); ok { + return job.(contractsqueue.Job), nil + } + + return nil, nil +} + +// GetJobs gets all registered jobs +func (r *JobImpl) GetJobs() []contractsqueue.Job { + var jobs []contractsqueue.Job + r.jobs.Range(func(_, value any) bool { + jobs = append(jobs, value.(contractsqueue.Job)) + return true + }) + + return jobs +} diff --git a/queue/machinery.go b/queue/machinery.go deleted file mode 100644 index df2e6bf06..000000000 --- a/queue/machinery.go +++ /dev/null @@ -1,63 +0,0 @@ -package queue - -import ( - "github.com/RichardKnop/machinery/v2" - redisbackend "github.com/RichardKnop/machinery/v2/backends/redis" - redisbroker "github.com/RichardKnop/machinery/v2/brokers/redis" - "github.com/RichardKnop/machinery/v2/config" - "github.com/RichardKnop/machinery/v2/locks/eager" - "github.com/RichardKnop/machinery/v2/log" - - logcontract "github.com/goravel/framework/contracts/log" - "github.com/goravel/framework/errors" - "github.com/goravel/framework/support/color" -) - -type Machinery struct { - config *Config - log logcontract.Log -} - -func NewMachinery(config *Config, log logcontract.Log) *Machinery { - return &Machinery{config: config, log: log} -} - -func (m *Machinery) Server(connection string, queue string) (*machinery.Server, error) { - driver := m.config.Driver(connection) - - switch driver { - case DriverSync: - color.Warningln("Queue sync driver doesn't need to be run") - - return nil, nil - case DriverRedis: - return m.redisServer(connection, queue), nil - } - - return nil, errors.QueueDriverNotSupported.Args(driver) -} - -func (m *Machinery) redisServer(connection string, queue string) *machinery.Server { - redisConfig, database, defaultQueue := m.config.Redis(connection) - if queue == "" { - queue = defaultQueue - } - - cnf := &config.Config{ - DefaultQueue: queue, - Redis: &config.RedisConfig{}, - } - - broker := redisbroker.NewGR(cnf, []string{redisConfig}, database) - backend := redisbackend.NewGR(cnf, []string{redisConfig}, database) - lock := eager.New() - - debug := m.config.config.GetBool("app.debug") - log.DEBUG = NewDebug(debug, m.log) - log.INFO = NewInfo(debug, m.log) - log.WARNING = NewWarning(debug, m.log) - log.ERROR = NewError(debug, m.log) - log.FATAL = NewFatal(debug, m.log) - - return machinery.NewServer(cnf, broker, backend, lock) -} diff --git a/queue/service_provider.go b/queue/service_provider.go index fb6e41421..5e9117e7c 100644 --- a/queue/service_provider.go +++ b/queue/service_provider.go @@ -2,13 +2,20 @@ package queue import ( "github.com/goravel/framework/contracts/console" + "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/contracts/foundation" + "github.com/goravel/framework/contracts/log" "github.com/goravel/framework/errors" - queueConsole "github.com/goravel/framework/queue/console" + queueconsole "github.com/goravel/framework/queue/console" ) const Binding = "goravel.queue" +var ( + LogFacade log.Log // TODO: Will be removed in v1.17 + OrmFacade orm.Orm +) + type ServiceProvider struct { } @@ -19,17 +26,19 @@ func (receiver *ServiceProvider) Register(app foundation.Application) { return nil, errors.ConfigFacadeNotSet.SetModule(errors.ModuleQueue) } - log := app.MakeLog() - if log == nil { - return nil, errors.LogFacadeNotSet.SetModule(errors.ModuleQueue) - } - - return NewApplication(config, log), nil + return NewApplication(app.MakeConfig()), nil }) } func (receiver *ServiceProvider) Boot(app foundation.Application) { - app.Commands([]console.Command{ - &queueConsole.JobMakeCommand{}, + LogFacade = app.MakeLog() // TODO: Will be removed in v1.17 + OrmFacade = app.MakeOrm() + + receiver.registerCommands(app) +} + +func (receiver *ServiceProvider) registerCommands(app foundation.Application) { + app.MakeArtisan().Register([]console.Command{ + &queueconsole.JobMakeCommand{}, }) } diff --git a/queue/task.go b/queue/task.go index 8c8714227..3c0999d5a 100644 --- a/queue/task.go +++ b/queue/task.go @@ -3,84 +3,75 @@ package queue import ( "time" - "github.com/RichardKnop/machinery/v2" - "github.com/RichardKnop/machinery/v2/tasks" - - "github.com/goravel/framework/contracts/log" "github.com/goravel/framework/contracts/queue" - "github.com/goravel/framework/errors" ) type Task struct { config *Config connection string chain bool - delay *time.Time - machinery *Machinery + delay time.Duration + driver *DriverImpl jobs []queue.Jobs queue string - server *machinery.Server } -func NewTask(config *Config, log log.Log, job queue.Job, args []queue.Arg) *Task { +func NewTask(config *Config, job queue.Job, args []any) *Task { return &Task{ config: config, connection: config.DefaultConnection(), - machinery: NewMachinery(config, log), + driver: NewDriverImpl(config.DefaultConnection(), config), jobs: []queue.Jobs{ { Job: job, Args: args, }, }, + queue: config.Queue(config.DefaultConnection(), ""), } } -func NewChainTask(config *Config, log log.Log, jobs []queue.Jobs) *Task { +func NewChainTask(config *Config, jobs []queue.Jobs) *Task { return &Task{ config: config, connection: config.DefaultConnection(), chain: true, - machinery: NewMachinery(config, log), + driver: NewDriverImpl(config.DefaultConnection(), config), jobs: jobs, + queue: config.Queue(config.DefaultConnection(), ""), } } -func (receiver *Task) Delay(delay time.Time) queue.Task { - receiver.delay = &delay +// Delay sets a delay time for the task +func (receiver *Task) Delay(delay time.Duration) queue.Task { + receiver.delay = delay return receiver } +// Dispatch dispatches the task func (receiver *Task) Dispatch() error { - driver := receiver.config.Driver(receiver.connection) - if driver == "" { - return errors.QueueDriverNotSupported.Args(driver) - } - if driver == DriverSync { - return receiver.DispatchSync() - } - - server, err := receiver.machinery.Server(receiver.connection, receiver.queue) + driver, err := receiver.driver.New() if err != nil { return err } - receiver.server = server - if receiver.chain { - return receiver.handleChain(receiver.jobs) + return driver.Bulk(receiver.jobs, receiver.queue) } else { job := receiver.jobs[0] - - return receiver.handleAsync(job.Job, job.Args) + if receiver.delay > 0 { + return driver.Later(receiver.delay, job.Job, job.Args, receiver.queue) + } + return driver.Push(job.Job, job.Args, receiver.queue) } } +// DispatchSync dispatches the task synchronously func (receiver *Task) DispatchSync() error { if receiver.chain { for _, job := range receiver.jobs { - if err := receiver.handleSync(job.Job, job.Args); err != nil { + if err := job.Job.Handle(job.Args...); err != nil { return err } } @@ -89,76 +80,21 @@ func (receiver *Task) DispatchSync() error { } else { job := receiver.jobs[0] - return receiver.handleSync(job.Job, job.Args) + return job.Job.Handle(job.Args...) } } +// OnConnection sets the connection name func (receiver *Task) OnConnection(connection string) queue.Task { receiver.connection = connection + receiver.driver = NewDriverImpl(connection, receiver.config) return receiver } +// OnQueue sets the queue name func (receiver *Task) OnQueue(queue string) queue.Task { receiver.queue = receiver.config.Queue(receiver.connection, queue) return receiver } - -func (receiver *Task) handleChain(jobs []queue.Jobs) error { - var signatures []*tasks.Signature - for _, job := range jobs { - var realArgs []tasks.Arg - for _, arg := range job.Args { - realArgs = append(realArgs, tasks.Arg{ - Type: arg.Type, - Value: arg.Value, - }) - } - - signatures = append(signatures, &tasks.Signature{ - Name: job.Job.Signature(), - Args: realArgs, - ETA: receiver.delay, - }) - } - - chain, err := tasks.NewChain(signatures...) - if err != nil { - return err - } - - _, err = receiver.server.SendChain(chain) - - return err -} - -func (receiver *Task) handleAsync(job queue.Job, args []queue.Arg) error { - var realArgs []tasks.Arg - for _, arg := range args { - realArgs = append(realArgs, tasks.Arg{ - Type: arg.Type, - Value: arg.Value, - }) - } - - _, err := receiver.server.SendTask(&tasks.Signature{ - Name: job.Signature(), - Args: realArgs, - ETA: receiver.delay, - }) - if err != nil { - return err - } - - return nil -} - -func (receiver *Task) handleSync(job queue.Job, args []queue.Arg) error { - var realArgs []any - for _, arg := range args { - realArgs = append(realArgs, arg.Value) - } - - return job.Handle(realArgs...) -} diff --git a/queue/task_test.go b/queue/task_test.go index e82967195..d84e26678 100644 --- a/queue/task_test.go +++ b/queue/task_test.go @@ -1,7 +1,6 @@ package queue import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -21,31 +20,26 @@ func (receiver *Test) Signature() string { // Handle Execute the job. func (receiver *Test) Handle(args ...any) error { - if len(args) == 0 { - return fmt.Errorf("no arguments provided") - } - - arg, ok := args[0].(string) - if !ok { - return fmt.Errorf("expected a string argument") - } - - return file.Create("test.txt", arg) + return file.Create("test.txt", args[0].(string)) } func TestDispatchSync(t *testing.T) { task := &Task{ jobs: []queue.Jobs{ { - Job: &Test{}, - Args: []queue.Arg{ - {Type: "uint64", Value: "test"}, - }, + Job: &Test{}, + Args: []any{"test"}, }, }, } - err := task.DispatchSync() + jobs := NewJobImpl() + err := jobs.Register([]queue.Job{ + &Test{}, + }) + assert.Nil(t, err) + + err = task.DispatchSync() assert.Nil(t, err) assert.True(t, file.Exists("test.txt")) assert.True(t, testingfile.GetLineNum("test.txt") == 1) diff --git a/queue/utils.go b/queue/utils.go index 4a82c9433..cb7233806 100644 --- a/queue/utils.go +++ b/queue/utils.go @@ -1,9 +1,11 @@ package queue import ( + "errors" + "fmt" + "github.com/goravel/framework/contracts/event" "github.com/goravel/framework/contracts/queue" - "github.com/goravel/framework/errors" ) func jobs2Tasks(jobs []queue.Job) (map[string]any, error) { @@ -11,11 +13,11 @@ func jobs2Tasks(jobs []queue.Job) (map[string]any, error) { for _, job := range jobs { if job.Signature() == "" { - return nil, errors.QueueEmptyJobSignature + return nil, errors.New("the Signature of job can't be empty") } if tasks[job.Signature()] != nil { - return nil, errors.QueueDuplicateJobSignature.Args(job.Signature()) + return nil, fmt.Errorf("job signature duplicate: %s, the names of Job and Listener cannot be duplicated", job.Signature()) } tasks[job.Signature()] = job.Handle @@ -30,7 +32,7 @@ func eventsToTasks(events map[event.Event][]event.Listener) (map[string]any, err for _, listeners := range events { for _, listener := range listeners { if listener.Signature() == "" { - return nil, errors.QueueEmptyListenerSignature + return nil, errors.New("the Signature of listener can't be empty") } if tasks[listener.Signature()] != nil { diff --git a/queue/utils_test.go b/queue/utils_test.go index 64aa0499b..fdd448c2c 100644 --- a/queue/utils_test.go +++ b/queue/utils_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/goravel/framework/contracts/event" - queuecontract "github.com/goravel/framework/contracts/queue" + contractsqueue "github.com/goravel/framework/contracts/queue" ) type TestJob struct { @@ -43,20 +43,20 @@ func (receiver *TestJobEmpty) Handle(args ...any) error { } func TestJobs2Tasks(t *testing.T) { - _, err := jobs2Tasks([]queuecontract.Job{ + _, err := jobs2Tasks([]contractsqueue.Job{ &TestJob{}, }) assert.Nil(t, err, "success") - _, err = jobs2Tasks([]queuecontract.Job{ + _, err = jobs2Tasks([]contractsqueue.Job{ &TestJob{}, &TestJobDuplicate{}, }) assert.NotNil(t, err, "Signature duplicate") - _, err = jobs2Tasks([]queuecontract.Job{ + _, err = jobs2Tasks([]contractsqueue.Job{ &TestJobEmpty{}, }) diff --git a/queue/worker.go b/queue/worker.go index 19a494d88..fb06faedd 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -1,61 +1,83 @@ package queue import ( - "github.com/goravel/framework/contracts/log" - "github.com/goravel/framework/contracts/queue" -) + "fmt" + "time" -const ( - DriverSync string = "sync" - DriverRedis string = "redis" + "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/support/carbon" ) type Worker struct { - concurrent int - connection string - machinery *Machinery - jobs []queue.Job - queue string + concurrent int + driver *DriverImpl + job *JobImpl + failedJobs orm.Query + queue string + failedJobChan chan FailedJob + isShutdown bool } -func NewWorker(config *Config, log log.Log, concurrent int, connection string, jobs []queue.Job, queue string) *Worker { +func NewWorker(config *Config, concurrent int, connection string, queue string, job *JobImpl) *Worker { return &Worker{ - concurrent: concurrent, - connection: connection, - machinery: NewMachinery(config, log), - jobs: jobs, - queue: queue, + concurrent: concurrent, + driver: NewDriverImpl(connection, config), + job: job, + failedJobs: config.FailedJobsQuery(), + queue: queue, + failedJobChan: make(chan FailedJob), } } -func (receiver *Worker) Run() error { - server, err := receiver.machinery.Server(receiver.connection, receiver.queue) +func (r *Worker) Run() error { + r.isShutdown = false + + driver, err := r.driver.New() if err != nil { return err } - if server == nil { - return nil + if driver.Driver() == DriverSync { + return fmt.Errorf("queue %s driver not need run", r.queue) } - jobTasks, err := jobs2Tasks(receiver.jobs) - if err != nil { - return err - } + for i := 0; i < r.concurrent; i++ { + go func() { + for { + if r.isShutdown { + return + } - if err := server.RegisterTasks(jobTasks); err != nil { - return err - } + job, args, err := driver.Pop(r.queue) + if err != nil { + // This error not need to be reported. + // It is usually caused by the queue being empty. + time.Sleep(1 * time.Second) + continue + } - if receiver.queue == "" { - receiver.queue = server.GetConfig().DefaultQueue - } - if receiver.concurrent == 0 { - receiver.concurrent = 1 - } - worker := server.NewWorker(receiver.queue, receiver.concurrent) - if err := worker.Launch(); err != nil { - return err + if err = r.job.Call(job.Signature(), args); err != nil { + r.failedJobChan <- FailedJob{ + Queue: r.queue, + Signature: job.Signature(), + Payloads: args, + Exception: err.Error(), + FailedAt: carbon.DateTime{Carbon: carbon.Now()}, + } + } + } + }() } + go func() { + for job := range r.failedJobChan { + _ = r.failedJobs.Create(&job) + } + }() + + return nil +} + +func (r *Worker) Shutdown() error { + r.isShutdown = true return nil } From 9ef22f080250d9bd3c31987bbd7aeb80aff6021c Mon Sep 17 00:00:00 2001 From: devhaozi Date: Sun, 5 Jan 2025 07:55:14 +0000 Subject: [PATCH 02/32] chore: update mocks --- mocks/queue/Driver.go | 338 ++++++++++++++++++++++++++++++++++++++++++ mocks/queue/Queue.go | 123 +++++++++++---- mocks/queue/Task.go | 12 +- mocks/queue/Worker.go | 45 ++++++ 4 files changed, 486 insertions(+), 32 deletions(-) create mode 100644 mocks/queue/Driver.go diff --git a/mocks/queue/Driver.go b/mocks/queue/Driver.go new file mode 100644 index 000000000..e253b07f6 --- /dev/null +++ b/mocks/queue/Driver.go @@ -0,0 +1,338 @@ +// Code generated by mockery. DO NOT EDIT. + +package queue + +import ( + queue "github.com/goravel/framework/contracts/queue" + mock "github.com/stretchr/testify/mock" + + time "time" +) + +// Driver is an autogenerated mock type for the Driver type +type Driver struct { + mock.Mock +} + +type Driver_Expecter struct { + mock *mock.Mock +} + +func (_m *Driver) EXPECT() *Driver_Expecter { + return &Driver_Expecter{mock: &_m.Mock} +} + +// Bulk provides a mock function with given fields: jobs, _a1 +func (_m *Driver) Bulk(jobs []queue.Jobs, _a1 string) error { + ret := _m.Called(jobs, _a1) + + if len(ret) == 0 { + panic("no return value specified for Bulk") + } + + var r0 error + if rf, ok := ret.Get(0).(func([]queue.Jobs, string) error); ok { + r0 = rf(jobs, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Driver_Bulk_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Bulk' +type Driver_Bulk_Call struct { + *mock.Call +} + +// Bulk is a helper method to define mock.On call +// - jobs []queue.Jobs +// - _a1 string +func (_e *Driver_Expecter) Bulk(jobs interface{}, _a1 interface{}) *Driver_Bulk_Call { + return &Driver_Bulk_Call{Call: _e.mock.On("Bulk", jobs, _a1)} +} + +func (_c *Driver_Bulk_Call) Run(run func(jobs []queue.Jobs, _a1 string)) *Driver_Bulk_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]queue.Jobs), args[1].(string)) + }) + return _c +} + +func (_c *Driver_Bulk_Call) Return(_a0 error) *Driver_Bulk_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Driver_Bulk_Call) RunAndReturn(run func([]queue.Jobs, string) error) *Driver_Bulk_Call { + _c.Call.Return(run) + return _c +} + +// Connection provides a mock function with no fields +func (_m *Driver) Connection() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Connection") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Driver_Connection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Connection' +type Driver_Connection_Call struct { + *mock.Call +} + +// Connection is a helper method to define mock.On call +func (_e *Driver_Expecter) Connection() *Driver_Connection_Call { + return &Driver_Connection_Call{Call: _e.mock.On("Connection")} +} + +func (_c *Driver_Connection_Call) Run(run func()) *Driver_Connection_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Driver_Connection_Call) Return(_a0 string) *Driver_Connection_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Driver_Connection_Call) RunAndReturn(run func() string) *Driver_Connection_Call { + _c.Call.Return(run) + return _c +} + +// Driver provides a mock function with no fields +func (_m *Driver) Driver() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Driver") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Driver_Driver_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Driver' +type Driver_Driver_Call struct { + *mock.Call +} + +// Driver is a helper method to define mock.On call +func (_e *Driver_Expecter) Driver() *Driver_Driver_Call { + return &Driver_Driver_Call{Call: _e.mock.On("Driver")} +} + +func (_c *Driver_Driver_Call) Run(run func()) *Driver_Driver_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Driver_Driver_Call) Return(_a0 string) *Driver_Driver_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Driver_Driver_Call) RunAndReturn(run func() string) *Driver_Driver_Call { + _c.Call.Return(run) + return _c +} + +// Later provides a mock function with given fields: delay, job, args, _a3 +func (_m *Driver) Later(delay time.Duration, job queue.Job, args []interface{}, _a3 string) error { + ret := _m.Called(delay, job, args, _a3) + + if len(ret) == 0 { + panic("no return value specified for Later") + } + + var r0 error + if rf, ok := ret.Get(0).(func(time.Duration, queue.Job, []interface{}, string) error); ok { + r0 = rf(delay, job, args, _a3) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Driver_Later_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Later' +type Driver_Later_Call struct { + *mock.Call +} + +// Later is a helper method to define mock.On call +// - delay time.Duration +// - job queue.Job +// - args []interface{} +// - _a3 string +func (_e *Driver_Expecter) Later(delay interface{}, job interface{}, args interface{}, _a3 interface{}) *Driver_Later_Call { + return &Driver_Later_Call{Call: _e.mock.On("Later", delay, job, args, _a3)} +} + +func (_c *Driver_Later_Call) Run(run func(delay time.Duration, job queue.Job, args []interface{}, _a3 string)) *Driver_Later_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(time.Duration), args[1].(queue.Job), args[2].([]interface{}), args[3].(string)) + }) + return _c +} + +func (_c *Driver_Later_Call) Return(_a0 error) *Driver_Later_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Driver_Later_Call) RunAndReturn(run func(time.Duration, queue.Job, []interface{}, string) error) *Driver_Later_Call { + _c.Call.Return(run) + return _c +} + +// Pop provides a mock function with given fields: _a0 +func (_m *Driver) Pop(_a0 string) (queue.Job, []interface{}, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for Pop") + } + + var r0 queue.Job + var r1 []interface{} + var r2 error + if rf, ok := ret.Get(0).(func(string) (queue.Job, []interface{}, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(string) queue.Job); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(queue.Job) + } + } + + if rf, ok := ret.Get(1).(func(string) []interface{}); ok { + r1 = rf(_a0) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]interface{}) + } + } + + if rf, ok := ret.Get(2).(func(string) error); ok { + r2 = rf(_a0) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Driver_Pop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Pop' +type Driver_Pop_Call struct { + *mock.Call +} + +// Pop is a helper method to define mock.On call +// - _a0 string +func (_e *Driver_Expecter) Pop(_a0 interface{}) *Driver_Pop_Call { + return &Driver_Pop_Call{Call: _e.mock.On("Pop", _a0)} +} + +func (_c *Driver_Pop_Call) Run(run func(_a0 string)) *Driver_Pop_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Driver_Pop_Call) Return(_a0 queue.Job, _a1 []interface{}, _a2 error) *Driver_Pop_Call { + _c.Call.Return(_a0, _a1, _a2) + return _c +} + +func (_c *Driver_Pop_Call) RunAndReturn(run func(string) (queue.Job, []interface{}, error)) *Driver_Pop_Call { + _c.Call.Return(run) + return _c +} + +// Push provides a mock function with given fields: job, args, _a2 +func (_m *Driver) Push(job queue.Job, args []interface{}, _a2 string) error { + ret := _m.Called(job, args, _a2) + + if len(ret) == 0 { + panic("no return value specified for Push") + } + + var r0 error + if rf, ok := ret.Get(0).(func(queue.Job, []interface{}, string) error); ok { + r0 = rf(job, args, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Driver_Push_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Push' +type Driver_Push_Call struct { + *mock.Call +} + +// Push is a helper method to define mock.On call +// - job queue.Job +// - args []interface{} +// - _a2 string +func (_e *Driver_Expecter) Push(job interface{}, args interface{}, _a2 interface{}) *Driver_Push_Call { + return &Driver_Push_Call{Call: _e.mock.On("Push", job, args, _a2)} +} + +func (_c *Driver_Push_Call) Run(run func(job queue.Job, args []interface{}, _a2 string)) *Driver_Push_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(queue.Job), args[1].([]interface{}), args[2].(string)) + }) + return _c +} + +func (_c *Driver_Push_Call) Return(_a0 error) *Driver_Push_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Driver_Push_Call) RunAndReturn(run func(queue.Job, []interface{}, string) error) *Driver_Push_Call { + _c.Call.Return(run) + return _c +} + +// NewDriver creates a new instance of Driver. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDriver(t interface { + mock.TestingT + Cleanup(func()) +}) *Driver { + mock := &Driver{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/queue/Queue.go b/mocks/queue/Queue.go index fc79dbcf3..7442fcf61 100644 --- a/mocks/queue/Queue.go +++ b/mocks/queue/Queue.go @@ -68,6 +68,64 @@ func (_c *Queue_Chain_Call) RunAndReturn(run func([]queue.Jobs) queue.Task) *Que return _c } +// GetJob provides a mock function with given fields: signature +func (_m *Queue) GetJob(signature string) (queue.Job, error) { + ret := _m.Called(signature) + + if len(ret) == 0 { + panic("no return value specified for GetJob") + } + + var r0 queue.Job + var r1 error + if rf, ok := ret.Get(0).(func(string) (queue.Job, error)); ok { + return rf(signature) + } + if rf, ok := ret.Get(0).(func(string) queue.Job); ok { + r0 = rf(signature) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(queue.Job) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(signature) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Queue_GetJob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJob' +type Queue_GetJob_Call struct { + *mock.Call +} + +// GetJob is a helper method to define mock.On call +// - signature string +func (_e *Queue_Expecter) GetJob(signature interface{}) *Queue_GetJob_Call { + return &Queue_GetJob_Call{Call: _e.mock.On("GetJob", signature)} +} + +func (_c *Queue_GetJob_Call) Run(run func(signature string)) *Queue_GetJob_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Queue_GetJob_Call) Return(_a0 queue.Job, _a1 error) *Queue_GetJob_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Queue_GetJob_Call) RunAndReturn(run func(string) (queue.Job, error)) *Queue_GetJob_Call { + _c.Call.Return(run) + return _c +} + // GetJobs provides a mock function with no fields func (_m *Queue) GetJobs() []queue.Job { ret := _m.Called() @@ -116,7 +174,7 @@ func (_c *Queue_GetJobs_Call) RunAndReturn(run func() []queue.Job) *Queue_GetJob } // Job provides a mock function with given fields: job, args -func (_m *Queue) Job(job queue.Job, args []queue.Arg) queue.Task { +func (_m *Queue) Job(job queue.Job, args []interface{}) queue.Task { ret := _m.Called(job, args) if len(ret) == 0 { @@ -124,7 +182,7 @@ func (_m *Queue) Job(job queue.Job, args []queue.Arg) queue.Task { } var r0 queue.Task - if rf, ok := ret.Get(0).(func(queue.Job, []queue.Arg) queue.Task); ok { + if rf, ok := ret.Get(0).(func(queue.Job, []interface{}) queue.Task); ok { r0 = rf(job, args) } else { if ret.Get(0) != nil { @@ -142,14 +200,14 @@ type Queue_Job_Call struct { // Job is a helper method to define mock.On call // - job queue.Job -// - args []queue.Arg +// - args []interface{} func (_e *Queue_Expecter) Job(job interface{}, args interface{}) *Queue_Job_Call { return &Queue_Job_Call{Call: _e.mock.On("Job", job, args)} } -func (_c *Queue_Job_Call) Run(run func(job queue.Job, args []queue.Arg)) *Queue_Job_Call { +func (_c *Queue_Job_Call) Run(run func(job queue.Job, args []interface{})) *Queue_Job_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(queue.Job), args[1].([]queue.Arg)) + run(args[0].(queue.Job), args[1].([]interface{})) }) return _c } @@ -159,14 +217,27 @@ func (_c *Queue_Job_Call) Return(_a0 queue.Task) *Queue_Job_Call { return _c } -func (_c *Queue_Job_Call) RunAndReturn(run func(queue.Job, []queue.Arg) queue.Task) *Queue_Job_Call { +func (_c *Queue_Job_Call) RunAndReturn(run func(queue.Job, []interface{}) queue.Task) *Queue_Job_Call { _c.Call.Return(run) return _c } // Register provides a mock function with given fields: jobs -func (_m *Queue) Register(jobs []queue.Job) { - _m.Called(jobs) +func (_m *Queue) Register(jobs []queue.Job) error { + ret := _m.Called(jobs) + + if len(ret) == 0 { + panic("no return value specified for Register") + } + + var r0 error + if rf, ok := ret.Get(0).(func([]queue.Job) error); ok { + r0 = rf(jobs) + } else { + r0 = ret.Error(0) + } + + return r0 } // Queue_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' @@ -187,21 +258,21 @@ func (_c *Queue_Register_Call) Run(run func(jobs []queue.Job)) *Queue_Register_C return _c } -func (_c *Queue_Register_Call) Return() *Queue_Register_Call { - _c.Call.Return() +func (_c *Queue_Register_Call) Return(_a0 error) *Queue_Register_Call { + _c.Call.Return(_a0) return _c } -func (_c *Queue_Register_Call) RunAndReturn(run func([]queue.Job)) *Queue_Register_Call { - _c.Run(run) +func (_c *Queue_Register_Call) RunAndReturn(run func([]queue.Job) error) *Queue_Register_Call { + _c.Call.Return(run) return _c } -// Worker provides a mock function with given fields: args -func (_m *Queue) Worker(args ...queue.Args) queue.Worker { - _va := make([]interface{}, len(args)) - for _i := range args { - _va[_i] = args[_i] +// Worker provides a mock function with given fields: payloads +func (_m *Queue) Worker(payloads ...*queue.Args) queue.Worker { + _va := make([]interface{}, len(payloads)) + for _i := range payloads { + _va[_i] = payloads[_i] } var _ca []interface{} _ca = append(_ca, _va...) @@ -212,8 +283,8 @@ func (_m *Queue) Worker(args ...queue.Args) queue.Worker { } var r0 queue.Worker - if rf, ok := ret.Get(0).(func(...queue.Args) queue.Worker); ok { - r0 = rf(args...) + if rf, ok := ret.Get(0).(func(...*queue.Args) queue.Worker); ok { + r0 = rf(payloads...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(queue.Worker) @@ -229,18 +300,18 @@ type Queue_Worker_Call struct { } // Worker is a helper method to define mock.On call -// - args ...queue.Args -func (_e *Queue_Expecter) Worker(args ...interface{}) *Queue_Worker_Call { +// - payloads ...*queue.Args +func (_e *Queue_Expecter) Worker(payloads ...interface{}) *Queue_Worker_Call { return &Queue_Worker_Call{Call: _e.mock.On("Worker", - append([]interface{}{}, args...)...)} + append([]interface{}{}, payloads...)...)} } -func (_c *Queue_Worker_Call) Run(run func(args ...queue.Args)) *Queue_Worker_Call { +func (_c *Queue_Worker_Call) Run(run func(payloads ...*queue.Args)) *Queue_Worker_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]queue.Args, len(args)-0) + variadicArgs := make([]*queue.Args, len(args)-0) for i, a := range args[0:] { if a != nil { - variadicArgs[i] = a.(queue.Args) + variadicArgs[i] = a.(*queue.Args) } } run(variadicArgs...) @@ -253,7 +324,7 @@ func (_c *Queue_Worker_Call) Return(_a0 queue.Worker) *Queue_Worker_Call { return _c } -func (_c *Queue_Worker_Call) RunAndReturn(run func(...queue.Args) queue.Worker) *Queue_Worker_Call { +func (_c *Queue_Worker_Call) RunAndReturn(run func(...*queue.Args) queue.Worker) *Queue_Worker_Call { _c.Call.Return(run) return _c } diff --git a/mocks/queue/Task.go b/mocks/queue/Task.go index 0e66c994d..6cf8a45aa 100644 --- a/mocks/queue/Task.go +++ b/mocks/queue/Task.go @@ -23,7 +23,7 @@ func (_m *Task) EXPECT() *Task_Expecter { } // Delay provides a mock function with given fields: _a0 -func (_m *Task) Delay(_a0 time.Time) queue.Task { +func (_m *Task) Delay(_a0 time.Duration) queue.Task { ret := _m.Called(_a0) if len(ret) == 0 { @@ -31,7 +31,7 @@ func (_m *Task) Delay(_a0 time.Time) queue.Task { } var r0 queue.Task - if rf, ok := ret.Get(0).(func(time.Time) queue.Task); ok { + if rf, ok := ret.Get(0).(func(time.Duration) queue.Task); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -48,14 +48,14 @@ type Task_Delay_Call struct { } // Delay is a helper method to define mock.On call -// - _a0 time.Time +// - _a0 time.Duration func (_e *Task_Expecter) Delay(_a0 interface{}) *Task_Delay_Call { return &Task_Delay_Call{Call: _e.mock.On("Delay", _a0)} } -func (_c *Task_Delay_Call) Run(run func(_a0 time.Time)) *Task_Delay_Call { +func (_c *Task_Delay_Call) Run(run func(_a0 time.Duration)) *Task_Delay_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Time)) + run(args[0].(time.Duration)) }) return _c } @@ -65,7 +65,7 @@ func (_c *Task_Delay_Call) Return(_a0 queue.Task) *Task_Delay_Call { return _c } -func (_c *Task_Delay_Call) RunAndReturn(run func(time.Time) queue.Task) *Task_Delay_Call { +func (_c *Task_Delay_Call) RunAndReturn(run func(time.Duration) queue.Task) *Task_Delay_Call { _c.Call.Return(run) return _c } diff --git a/mocks/queue/Worker.go b/mocks/queue/Worker.go index d1e4b72d7..47d3dd775 100644 --- a/mocks/queue/Worker.go +++ b/mocks/queue/Worker.go @@ -62,6 +62,51 @@ func (_c *Worker_Run_Call) RunAndReturn(run func() error) *Worker_Run_Call { return _c } +// Shutdown provides a mock function with no fields +func (_m *Worker) Shutdown() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Shutdown") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Worker_Shutdown_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Shutdown' +type Worker_Shutdown_Call struct { + *mock.Call +} + +// Shutdown is a helper method to define mock.On call +func (_e *Worker_Expecter) Shutdown() *Worker_Shutdown_Call { + return &Worker_Shutdown_Call{Call: _e.mock.On("Shutdown")} +} + +func (_c *Worker_Shutdown_Call) Run(run func()) *Worker_Shutdown_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Worker_Shutdown_Call) Return(_a0 error) *Worker_Shutdown_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Worker_Shutdown_Call) RunAndReturn(run func() error) *Worker_Shutdown_Call { + _c.Call.Return(run) + return _c +} + // NewWorker creates a new instance of Worker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewWorker(t interface { From 3fe9d36ccc0550102570cc8990ff2f14c524fd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:00:49 +0800 Subject: [PATCH 03/32] feat: add job test --- queue/job_test.go | 77 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 queue/job_test.go diff --git a/queue/job_test.go b/queue/job_test.go new file mode 100644 index 000000000..091403074 --- /dev/null +++ b/queue/job_test.go @@ -0,0 +1,77 @@ +package queue + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/contracts/queue" +) + +type JobTestSuite struct { + suite.Suite + jobManager *JobImpl +} + +func TestJobTestSuite(t *testing.T) { + suite.Run(t, new(JobTestSuite)) +} + +func (s *JobTestSuite) SetupTest() { + s.jobManager = NewJobImpl() +} + +func (s *JobTestSuite) RegisterJobsSuccessfully() { + jobs := []queue.Job{ + &MockJob{signature: "job1"}, + &MockJob{signature: "job2"}, + } + + err := s.jobManager.Register(jobs) + s.NoError(err) + + registeredJobs := s.jobManager.GetJobs() + s.Len(registeredJobs, 2) +} + +func (s *JobTestSuite) CallRegisteredJobSuccessfully() { + job := &MockJob{signature: "job1"} + s.NoError(s.jobManager.Register([]queue.Job{job})) + + err := s.jobManager.Call("job1", []any{"arg1"}) + s.NoError(err) + s.True(job.called) +} + +func (s *JobTestSuite) CallUnregisteredJobFails() { + err := s.jobManager.Call("nonexistent", []any{"arg1"}) + s.Error(err) +} + +func (s *JobTestSuite) GetRegisteredJobSuccessfully() { + job := &MockJob{signature: "job1"} + s.NoError(s.jobManager.Register([]queue.Job{job})) + + retrievedJob, err := s.jobManager.Get("job1") + s.NoError(err) + s.Equal(job, retrievedJob) +} + +func (s *JobTestSuite) GetUnregisteredJobFails() { + _, err := s.jobManager.Get("nonexistent") + s.Error(err) +} + +type MockJob struct { + signature string + called bool +} + +func (m *MockJob) Signature() string { + return m.signature +} + +func (m *MockJob) Handle(args ...any) error { + m.called = true + return nil +} From 043a2074ffcf225d0379fbb434bdbaf5670cf320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:07:42 +0800 Subject: [PATCH 04/32] feat: fix some test --- contracts/queue/queue.go | 2 +- event/task.go | 9 +++------ event/task_test.go | 8 ++------ mail/application_test.go | 10 +++------- queue/application.go | 4 ++-- queue/driver_async_test.go | 8 ++++---- 6 files changed, 15 insertions(+), 26 deletions(-) diff --git a/contracts/queue/queue.go b/contracts/queue/queue.go index 1a7111429..e0d483a23 100644 --- a/contracts/queue/queue.go +++ b/contracts/queue/queue.go @@ -1,7 +1,7 @@ package queue type Queue interface { - Worker(payloads ...*Args) Worker + Worker(payloads ...Args) Worker // Register register jobs Register(jobs []Job) error // GetJobs get all jobs diff --git a/event/task.go b/event/task.go index 1a32468be..24a039e96 100644 --- a/event/task.go +++ b/event/task.go @@ -61,13 +61,10 @@ func (receiver *Task) Dispatch() error { return nil } -func eventArgsToQueueArgs(args []event.Arg) []queuecontract.Arg { - var queueArgs []queuecontract.Arg +func eventArgsToQueueArgs(args []event.Arg) []any { + var queueArgs []any for _, arg := range args { - queueArgs = append(queueArgs, queuecontract.Arg{ - Type: arg.Type, - Value: arg.Value, - }) + queueArgs = append(queueArgs, arg.Value) } return queueArgs diff --git a/event/task_test.go b/event/task_test.go index 2f2454d37..16386ca3d 100644 --- a/event/task_test.go +++ b/event/task_test.go @@ -32,9 +32,7 @@ func TestDispatch(t *testing.T) { listener := &TestListener{} mockTask := &queuemock.Task{} - mockQueue.On("Job", listener, []queuecontract.Arg{ - {Type: "string", Value: "test"}, - }).Return(mockTask).Once() + mockQueue.On("Job", listener, []any{"test"}).Return(mockTask).Once() mockTask.On("DispatchSync").Return(nil).Once() task = NewTask(mockQueue, []event.Arg{ @@ -51,9 +49,7 @@ func TestDispatch(t *testing.T) { listener := &TestListenerHandleError{} mockTask := &queuemock.Task{} - mockQueue.On("Job", listener, []queuecontract.Arg{ - {Type: "string", Value: "test"}, - }).Return(mockTask).Once() + mockQueue.On("Job", listener, []any{"test"}).Return(mockTask).Once() mockTask.On("DispatchSync").Return(errors.New("error")).Once() task = NewTask(mockQueue, []event.Arg{ diff --git a/mail/application_test.go b/mail/application_test.go index 2dbc43989..3edaa5a2a 100644 --- a/mail/application_test.go +++ b/mail/application_test.go @@ -13,7 +13,6 @@ import ( "github.com/goravel/framework/contracts/mail" queuecontract "github.com/goravel/framework/contracts/queue" configmock "github.com/goravel/framework/mocks/config" - logmock "github.com/goravel/framework/mocks/log" "github.com/goravel/framework/queue" "github.com/goravel/framework/support/color" testingdocker "github.com/goravel/framework/support/docker" @@ -95,9 +94,8 @@ func (s *ApplicationTestSuite) TestSendMailWithMailable() { func (s *ApplicationTestSuite) TestQueueMail() { mockConfig := mockConfig(587, s.redisPort) - mockLog := &logmock.Log{} - queueFacade := queue.NewApplication(mockConfig, mockLog) + queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ NewSendMailJob(mockConfig), }) @@ -126,9 +124,8 @@ func (s *ApplicationTestSuite) TestQueueMail() { func (s *ApplicationTestSuite) TestQueueMailWithConnection() { mockConfig := mockConfig(587, s.redisPort) - mockLog := &logmock.Log{} - queueFacade := queue.NewApplication(mockConfig, mockLog) + queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ NewSendMailJob(mockConfig), }) @@ -160,9 +157,8 @@ func (s *ApplicationTestSuite) TestQueueMailWithConnection() { func (s *ApplicationTestSuite) TestQueueMailWithMailable() { mockConfig := mockConfig(587, s.redisPort) - mockLog := &logmock.Log{} - queueFacade := queue.NewApplication(mockConfig, mockLog) + queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ NewSendMailJob(mockConfig), }) diff --git a/queue/application.go b/queue/application.go index 7c13c4792..d1064cdb9 100644 --- a/queue/application.go +++ b/queue/application.go @@ -17,10 +17,10 @@ func NewApplication(config configcontract.Config) *Application { } } -func (app *Application) Worker(payloads ...*queue.Args) queue.Worker { +func (app *Application) Worker(payloads ...queue.Args) queue.Worker { defaultConnection := app.config.DefaultConnection() - if len(payloads) == 0 || payloads[0] == nil { + if len(payloads) == 0 { return NewWorker(app.config, 1, defaultConnection, app.config.Queue(defaultConnection, ""), app.job) } if payloads[0].Connection == "" { diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 715cca969..0d7bb860d 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -91,7 +91,7 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() go func(ctx context.Context) { - worker := s.app.Worker(&queue.Args{ + worker := s.app.Worker(queue.Args{ Queue: "delay", }) s.Nil(worker.Run()) @@ -121,7 +121,7 @@ func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go func(ctx context.Context) { - worker := s.app.Worker(&queue.Args{ + worker := s.app.Worker(queue.Args{ Connection: "custom", Queue: "custom1", Concurrent: 2, @@ -152,7 +152,7 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go func(ctx context.Context) { - worker := s.app.Worker(&queue.Args{ + worker := s.app.Worker(queue.Args{ Queue: "error", }) s.Nil(worker.Run()) @@ -180,7 +180,7 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go func(ctx context.Context) { - worker := s.app.Worker(&queue.Args{ + worker := s.app.Worker(queue.Args{ Queue: "chain", }) s.Nil(worker.Run()) From 7c405105483b8d9ffdf8556cbca711418a36f639 Mon Sep 17 00:00:00 2001 From: devhaozi Date: Sun, 5 Jan 2025 08:08:21 +0000 Subject: [PATCH 05/32] chore: update mocks --- mocks/queue/Queue.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mocks/queue/Queue.go b/mocks/queue/Queue.go index 7442fcf61..d25b49187 100644 --- a/mocks/queue/Queue.go +++ b/mocks/queue/Queue.go @@ -269,7 +269,7 @@ func (_c *Queue_Register_Call) RunAndReturn(run func([]queue.Job) error) *Queue_ } // Worker provides a mock function with given fields: payloads -func (_m *Queue) Worker(payloads ...*queue.Args) queue.Worker { +func (_m *Queue) Worker(payloads ...queue.Args) queue.Worker { _va := make([]interface{}, len(payloads)) for _i := range payloads { _va[_i] = payloads[_i] @@ -283,7 +283,7 @@ func (_m *Queue) Worker(payloads ...*queue.Args) queue.Worker { } var r0 queue.Worker - if rf, ok := ret.Get(0).(func(...*queue.Args) queue.Worker); ok { + if rf, ok := ret.Get(0).(func(...queue.Args) queue.Worker); ok { r0 = rf(payloads...) } else { if ret.Get(0) != nil { @@ -300,18 +300,18 @@ type Queue_Worker_Call struct { } // Worker is a helper method to define mock.On call -// - payloads ...*queue.Args +// - payloads ...queue.Args func (_e *Queue_Expecter) Worker(payloads ...interface{}) *Queue_Worker_Call { return &Queue_Worker_Call{Call: _e.mock.On("Worker", append([]interface{}{}, payloads...)...)} } -func (_c *Queue_Worker_Call) Run(run func(payloads ...*queue.Args)) *Queue_Worker_Call { +func (_c *Queue_Worker_Call) Run(run func(payloads ...queue.Args)) *Queue_Worker_Call { _c.Call.Run(func(args mock.Arguments) { - variadicArgs := make([]*queue.Args, len(args)-0) + variadicArgs := make([]queue.Args, len(args)-0) for i, a := range args[0:] { if a != nil { - variadicArgs[i] = a.(*queue.Args) + variadicArgs[i] = a.(queue.Args) } } run(variadicArgs...) @@ -324,7 +324,7 @@ func (_c *Queue_Worker_Call) Return(_a0 queue.Worker) *Queue_Worker_Call { return _c } -func (_c *Queue_Worker_Call) RunAndReturn(run func(...*queue.Args) queue.Worker) *Queue_Worker_Call { +func (_c *Queue_Worker_Call) RunAndReturn(run func(...queue.Args) queue.Worker) *Queue_Worker_Call { _c.Call.Return(run) return _c } From 6aa01be2b421f8ad03ced544c828eff1ffe77230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:10:10 +0800 Subject: [PATCH 06/32] feat: fix some test --- mail/application.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mail/application.go b/mail/application.go index 8bfc26d96..1062da736 100644 --- a/mail/application.go +++ b/mail/application.go @@ -70,15 +70,15 @@ func (r *Application) Queue(mailable ...mail.Mailable) error { r.setUsingMailable(mailable[0]) } - job := r.queue.Job(NewSendMailJob(r.config), []queuecontract.Arg{ - {Value: r.subject, Type: "string"}, - {Value: r.html, Type: "string"}, - {Value: r.from.Address, Type: "string"}, - {Value: r.from.Name, Type: "string"}, - {Value: r.to, Type: "[]string"}, - {Value: r.cc, Type: "[]string"}, - {Value: r.bcc, Type: "[]string"}, - {Value: r.attachments, Type: "[]string"}, + job := r.queue.Job(NewSendMailJob(r.config), []any{ + r.subject, + r.html, + r.from.Address, + r.from.Name, + r.to, + r.cc, + r.bcc, + r.attachments, }) if len(mailable) > 0 { From a97b1232ad5138619d52fb4a2fbb94dee89fec32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:10:21 +0800 Subject: [PATCH 07/32] feat: fix some test --- event/task_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/event/task_test.go b/event/task_test.go index 16386ca3d..0dc0f457a 100644 --- a/event/task_test.go +++ b/event/task_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/goravel/framework/contracts/event" - queuecontract "github.com/goravel/framework/contracts/queue" "github.com/goravel/framework/errors" queuemock "github.com/goravel/framework/mocks/queue" ) From 63f2edfca4ca0256c5fee85dae1145aa97b18cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:10:52 +0800 Subject: [PATCH 08/32] feat: fix some test --- queue/driver_async_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 0d7bb860d..ce311e673 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -65,7 +65,7 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go func(ctx context.Context) { - worker := s.app.Worker(nil) + worker := s.app.Worker() s.Nil(worker.Run()) <-ctx.Done() From ace5f8a0f6517eb2e1021835101c40fc17d5cd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:12:18 +0800 Subject: [PATCH 09/32] feat: fix some test --- mail/application_test.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mail/application_test.go b/mail/application_test.go index 3edaa5a2a..ed9ee963e 100644 --- a/mail/application_test.go +++ b/mail/application_test.go @@ -182,16 +182,11 @@ func (s *ApplicationTestSuite) TestQueueMailWithMailable() { func mockConfig(mailPort, redisPort int) *configmock.Config { mockConfig := &configmock.Config{} mockConfig.On("GetString", "app.name").Return("goravel") - mockConfig.On("GetBool", "app.debug").Return(false) - mockConfig.On("GetString", "queue.default").Return("redis") - mockConfig.On("GetString", "queue.connections.sync.driver").Return("sync") - mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis") - mockConfig.On("GetString", "queue.connections.redis.connection").Return("default") - mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default") - mockConfig.On("GetString", "database.redis.default.host").Return("localhost") - mockConfig.On("GetString", "database.redis.default.password").Return("") - mockConfig.On("GetInt", "database.redis.default.port").Return(redisPort) - mockConfig.On("GetInt", "database.redis.default.database").Return(0) + mockConfig.On("GetString", "queue.default").Return("async") + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default") + mockConfig.On("GetString", "queue.connections.async.driver").Return("async") + mockConfig.On("GetString", "queue.failed.database").Return("database") + mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs") if file.Exists("../.env") { vip := viper.New() From 70578c63f20a11a5d7d1d3f7a66d8e0a22ce0dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:22:00 +0800 Subject: [PATCH 10/32] feat: fix some test --- mail/application_test.go | 33 ++++++++------------------------- queue/driver_async_test.go | 12 ++++++++---- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/mail/application_test.go b/mail/application_test.go index ed9ee963e..4dd273f1c 100644 --- a/mail/application_test.go +++ b/mail/application_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/spf13/viper" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/mail" @@ -15,8 +14,6 @@ import ( configmock "github.com/goravel/framework/mocks/config" "github.com/goravel/framework/queue" "github.com/goravel/framework/support/color" - testingdocker "github.com/goravel/framework/support/docker" - "github.com/goravel/framework/support/env" "github.com/goravel/framework/support/file" ) @@ -24,33 +21,19 @@ var testBcc, testCc, testTo, testFromAddress, testFromName string type ApplicationTestSuite struct { suite.Suite - redisPort int } func TestApplicationTestSuite(t *testing.T) { - if env.IsWindows() { - t.Skip("Skip test that using Docker") - } - if !file.Exists("../.env") && os.Getenv("MAIL_HOST") == "" { color.Errorln("No mail tests run, need create .env based on .env.example, then initialize it") return } - - redisDocker := testingdocker.NewRedis() - assert.Nil(t, redisDocker.Build()) - - suite.Run(t, &ApplicationTestSuite{ - redisPort: redisDocker.Config().Port, - }) - - assert.Nil(t, redisDocker.Shutdown()) } func (s *ApplicationTestSuite) SetupTest() {} func (s *ApplicationTestSuite) TestSendMailBy465Port() { - mockConfig := mockConfig(465, s.redisPort) + mockConfig := mockConfig(465) app := NewApplication(mockConfig, nil) s.Nil(app.To([]string{testTo}). Cc([]string{testCc}). @@ -62,7 +45,7 @@ func (s *ApplicationTestSuite) TestSendMailBy465Port() { } func (s *ApplicationTestSuite) TestSendMailBy587Port() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) app := NewApplication(mockConfig, nil) s.Nil(app.To([]string{testTo}). Cc([]string{testCc}). @@ -74,7 +57,7 @@ func (s *ApplicationTestSuite) TestSendMailBy587Port() { } func (s *ApplicationTestSuite) TestSendMailWithFrom() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) app := NewApplication(mockConfig, nil) s.Nil(app.From(Address(testFromAddress, testFromName)). To([]string{testTo}). @@ -87,13 +70,13 @@ func (s *ApplicationTestSuite) TestSendMailWithFrom() { } func (s *ApplicationTestSuite) TestSendMailWithMailable() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) app := NewApplication(mockConfig, nil) s.Nil(app.Send(NewTestMailable())) } func (s *ApplicationTestSuite) TestQueueMail() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ @@ -123,7 +106,7 @@ func (s *ApplicationTestSuite) TestQueueMail() { } func (s *ApplicationTestSuite) TestQueueMailWithConnection() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ @@ -156,7 +139,7 @@ func (s *ApplicationTestSuite) TestQueueMailWithConnection() { } func (s *ApplicationTestSuite) TestQueueMailWithMailable() { - mockConfig := mockConfig(587, s.redisPort) + mockConfig := mockConfig(587) queueFacade := queue.NewApplication(mockConfig) queueFacade.Register([]queuecontract.Job{ @@ -179,7 +162,7 @@ func (s *ApplicationTestSuite) TestQueueMailWithMailable() { time.Sleep(3 * time.Second) } -func mockConfig(mailPort, redisPort int) *configmock.Config { +func mockConfig(mailPort int) *configmock.Config { mockConfig := &configmock.Config{} mockConfig.On("GetString", "app.name").Return("goravel") mockConfig.On("GetString", "queue.default").Return("async") diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index ce311e673..aaee02210 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -58,7 +58,8 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -84,7 +85,8 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -144,7 +146,8 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Once() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Once() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -173,7 +176,8 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Times(2) + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() From f4e70fc8f174d125e14106ce2c393097c1bfa8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:29:10 +0800 Subject: [PATCH 11/32] feat: fix some test --- contracts/queue/queue.go | 2 +- queue/application.go | 8 ++------ queue/driver_async_test.go | 4 +--- queue/driver_sync_test.go | 3 +-- queue/job.go | 7 +++---- queue/job_test.go | 11 ++++------- queue/task_test.go | 5 ++--- 7 files changed, 14 insertions(+), 26 deletions(-) diff --git a/contracts/queue/queue.go b/contracts/queue/queue.go index e0d483a23..3297fa516 100644 --- a/contracts/queue/queue.go +++ b/contracts/queue/queue.go @@ -3,7 +3,7 @@ package queue type Queue interface { Worker(payloads ...Args) Worker // Register register jobs - Register(jobs []Job) error + Register(jobs []Job) // GetJobs get all jobs GetJobs() []Job // GetJob get job by signature diff --git a/queue/application.go b/queue/application.go index d1064cdb9..3f0e3019f 100644 --- a/queue/application.go +++ b/queue/application.go @@ -33,12 +33,8 @@ func (app *Application) Worker(payloads ...queue.Args) queue.Worker { return NewWorker(app.config, payloads[0].Concurrent, payloads[0].Connection, app.config.Queue(payloads[0].Connection, payloads[0].Queue), app.job) } -func (app *Application) Register(jobs []queue.Job) error { - if err := app.job.Register(jobs); err != nil { - return err - } - - return nil +func (app *Application) Register(jobs []queue.Job) { + app.job.Register(jobs) } func (app *Application) GetJobs() []queue.Job { diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index aaee02210..b73290a65 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/queue" @@ -42,7 +41,7 @@ func TestDriverAsyncTestSuite(t *testing.T) { OrmFacade = mockOrm - assert.Nil(t, app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}})) + app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}}) suite.Run(t, &DriverAsyncTestSuite{ app: app, mockConfig: mockConfig, @@ -148,7 +147,6 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Once() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index 5c407157f..c856ecafa 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -4,7 +4,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/queue" @@ -29,7 +28,7 @@ func TestDriverSyncTestSuite(t *testing.T) { mockQueue := &queuemock.Queue{} app := NewApplication(mockConfig) - assert.Nil(t, app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}})) + app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}}) suite.Run(t, &DriverSyncTestSuite{ app: app, mockConfig: mockConfig, diff --git a/queue/job.go b/queue/job.go index 6607683cb..3025db8ab 100644 --- a/queue/job.go +++ b/queue/job.go @@ -4,6 +4,7 @@ import ( "sync" contractsqueue "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" "github.com/goravel/framework/support/carbon" ) @@ -32,12 +33,10 @@ func NewJobImpl() *JobImpl { } // Register registers jobs to the job manager -func (r *JobImpl) Register(jobs []contractsqueue.Job) error { +func (r *JobImpl) Register(jobs []contractsqueue.Job) { for _, job := range jobs { r.jobs.Store(job.Signature(), job) } - - return nil } // Call calls a registered job using its signature @@ -56,7 +55,7 @@ func (r *JobImpl) Get(signature string) (contractsqueue.Job, error) { return job.(contractsqueue.Job), nil } - return nil, nil + return nil, errors.New("job not found") } // GetJobs gets all registered jobs diff --git a/queue/job_test.go b/queue/job_test.go index 091403074..db180d236 100644 --- a/queue/job_test.go +++ b/queue/job_test.go @@ -22,13 +22,10 @@ func (s *JobTestSuite) SetupTest() { } func (s *JobTestSuite) RegisterJobsSuccessfully() { - jobs := []queue.Job{ + s.jobManager.Register([]queue.Job{ &MockJob{signature: "job1"}, &MockJob{signature: "job2"}, - } - - err := s.jobManager.Register(jobs) - s.NoError(err) + }) registeredJobs := s.jobManager.GetJobs() s.Len(registeredJobs, 2) @@ -36,7 +33,7 @@ func (s *JobTestSuite) RegisterJobsSuccessfully() { func (s *JobTestSuite) CallRegisteredJobSuccessfully() { job := &MockJob{signature: "job1"} - s.NoError(s.jobManager.Register([]queue.Job{job})) + s.jobManager.Register([]queue.Job{job}) err := s.jobManager.Call("job1", []any{"arg1"}) s.NoError(err) @@ -50,7 +47,7 @@ func (s *JobTestSuite) CallUnregisteredJobFails() { func (s *JobTestSuite) GetRegisteredJobSuccessfully() { job := &MockJob{signature: "job1"} - s.NoError(s.jobManager.Register([]queue.Job{job})) + s.jobManager.Register([]queue.Job{job}) retrievedJob, err := s.jobManager.Get("job1") s.NoError(err) diff --git a/queue/task_test.go b/queue/task_test.go index d84e26678..1cc34e93d 100644 --- a/queue/task_test.go +++ b/queue/task_test.go @@ -34,12 +34,11 @@ func TestDispatchSync(t *testing.T) { } jobs := NewJobImpl() - err := jobs.Register([]queue.Job{ + jobs.Register([]queue.Job{ &Test{}, }) - assert.Nil(t, err) - err = task.DispatchSync() + err := task.DispatchSync() assert.Nil(t, err) assert.True(t, file.Exists("test.txt")) assert.True(t, testingfile.GetLineNum("test.txt") == 1) From 8ad5afb27ed7fdd11d18163e6aae331e11581cbb Mon Sep 17 00:00:00 2001 From: devhaozi Date: Sun, 5 Jan 2025 08:29:51 +0000 Subject: [PATCH 12/32] chore: update mocks --- mocks/queue/Queue.go | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/mocks/queue/Queue.go b/mocks/queue/Queue.go index d25b49187..3bd0f0ed7 100644 --- a/mocks/queue/Queue.go +++ b/mocks/queue/Queue.go @@ -223,21 +223,8 @@ func (_c *Queue_Job_Call) RunAndReturn(run func(queue.Job, []interface{}) queue. } // Register provides a mock function with given fields: jobs -func (_m *Queue) Register(jobs []queue.Job) error { - ret := _m.Called(jobs) - - if len(ret) == 0 { - panic("no return value specified for Register") - } - - var r0 error - if rf, ok := ret.Get(0).(func([]queue.Job) error); ok { - r0 = rf(jobs) - } else { - r0 = ret.Error(0) - } - - return r0 +func (_m *Queue) Register(jobs []queue.Job) { + _m.Called(jobs) } // Queue_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' @@ -258,13 +245,13 @@ func (_c *Queue_Register_Call) Run(run func(jobs []queue.Job)) *Queue_Register_C return _c } -func (_c *Queue_Register_Call) Return(_a0 error) *Queue_Register_Call { - _c.Call.Return(_a0) +func (_c *Queue_Register_Call) Return() *Queue_Register_Call { + _c.Call.Return() return _c } -func (_c *Queue_Register_Call) RunAndReturn(run func([]queue.Job) error) *Queue_Register_Call { - _c.Call.Return(run) +func (_c *Queue_Register_Call) RunAndReturn(run func([]queue.Job)) *Queue_Register_Call { + _c.Run(run) return _c } From ee9dbee00368ae2631dfa8886228171322d7b31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:35:30 +0800 Subject: [PATCH 13/32] feat: fix some test --- queue/driver_async_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index b73290a65..56e057ed0 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -58,7 +58,7 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -85,7 +85,7 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -146,7 +146,7 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -175,7 +175,7 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetString", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() From 5ed60a4a6190e654d9d67d991a14f514734f249c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:41:06 +0800 Subject: [PATCH 14/32] feat: fix some test --- queue/driver_async_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 56e057ed0..90f98b1d3 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -58,7 +58,7 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -85,7 +85,7 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -146,7 +146,7 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() @@ -175,7 +175,7 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size").Return(10).Twice() + s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() From 22efc89ae208cc768e0a014fd8396771aeb1eb1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:42:06 +0800 Subject: [PATCH 15/32] feat: fix some test --- queue/driver_async_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 90f98b1d3..c6a8e0665 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -116,6 +116,7 @@ func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("async").Times(2) + s.mockConfig.On("GetInt", "queue.connections.custom.size", 100).Return(10).Twice() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() From c41668df9a00c76f7f0fea6f54b758d1f979e6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 5 Jan 2025 16:49:56 +0800 Subject: [PATCH 16/32] feat: fix some test --- queue/driver_async_test.go | 7 ++++--- queue/driver_machinery_test.go | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index c6a8e0665..95ec687d2 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -101,7 +101,7 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { s.Nil(worker.Shutdown()) }(ctx) time.Sleep(1 * time.Second) - s.Nil(s.app.Job(&TestDelayAsyncJob{}, []any{"TestDelayAsyncQueue", 1}).OnQueue("delay").Delay(3).Dispatch()) + s.Nil(s.app.Job(&TestDelayAsyncJob{}, []any{"TestDelayAsyncQueue", 1}).OnQueue("delay").Delay(3 * time.Second).Dispatch()) time.Sleep(2 * time.Second) s.Equal(0, testDelayAsyncJob) time.Sleep(3 * time.Second) @@ -146,8 +146,9 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Once() + s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Once() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Once() s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() diff --git a/queue/driver_machinery_test.go b/queue/driver_machinery_test.go index 105198d50..e23ad832f 100644 --- a/queue/driver_machinery_test.go +++ b/queue/driver_machinery_test.go @@ -40,7 +40,6 @@ func (s *MachineryTestSuite) TestServer() { name: "redis", connection: "redis", setup: func() { - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Once() s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Once() s.mockConfig.On("GetString", "database.redis.default.host").Return("127.0.0.1").Once() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Once() From eeba900fe71a78db4fca81ae37bfd91d8e582dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 6 Jan 2025 22:14:12 +0800 Subject: [PATCH 17/32] feat: update --- contracts/queue/config.go | 14 ++++++++ contracts/queue/driver.go | 15 +++++--- contracts/queue/job.go | 7 ++++ contracts/queue/queue.go | 15 ++++---- contracts/queue/task.go | 2 +- errors/list.go | 5 +++ queue/application.go | 43 +++++++++++----------- queue/config.go | 35 ++++++++++-------- queue/config_test.go | 9 ++--- queue/driver.go | 65 ++++++++++------------------------ queue/driver_async.go | 30 ++++++++-------- queue/driver_async_test.go | 46 +++++++++--------------- queue/driver_machinery.go | 12 +++---- queue/driver_machinery_test.go | 13 ++++--- queue/driver_sync.go | 6 ++-- queue/driver_sync_test.go | 12 +++---- queue/job.go | 52 +++++++++++++-------------- queue/job_test.go | 2 +- queue/service_provider.go | 4 +-- queue/task.go | 59 +++++++++++++----------------- queue/utils.go | 10 +++--- queue/worker.go | 42 ++++++++++++---------- 22 files changed, 242 insertions(+), 256 deletions(-) create mode 100644 contracts/queue/config.go diff --git a/contracts/queue/config.go b/contracts/queue/config.go new file mode 100644 index 000000000..b3bed8bc9 --- /dev/null +++ b/contracts/queue/config.go @@ -0,0 +1,14 @@ +package queue + +import "github.com/goravel/framework/contracts/database/orm" + +type Config interface { + Debug() bool + DefaultConnection() string + Driver(connection string) string + FailedJobsQuery() orm.Query + Queue(connection, queue string) string + Redis(queueConnection string) (dsn string, database int, queue string) // TODO: Will be removed in v1.17 + Size(connection string) int + Via(connection string) any +} diff --git a/contracts/queue/driver.go b/contracts/queue/driver.go index 29ef603bc..08dc13db9 100644 --- a/contracts/queue/driver.go +++ b/contracts/queue/driver.go @@ -2,17 +2,22 @@ package queue import "time" +const DriverSync string = "sync" +const DriverAsync string = "async" +const DriverMachinery string = "machinery" // TODO: Will be removed in v1.17 +const DriverCustom string = "custom" + type Driver interface { + // Bulk pushes a slice of jobs onto the queue. + Bulk(jobs []Jobs, queue string) error // Connection returns the connection name for the driver. Connection() string // Driver returns the driver name for the driver. Driver() string - // Push pushes the job onto the queue. - Push(job Job, args []any, queue string) error - // Bulk pushes a slice of jobs onto the queue. - Bulk(jobs []Jobs, queue string) error // Later pushes the job onto the queue after a delay. - Later(delay time.Duration, job Job, args []any, queue string) error + Later(delay time.Time, job Job, args []any, queue string) error // Pop pops the next job off of the queue. Pop(queue string) (Job, []any, error) + // Push pushes the job onto the queue. + Push(job Job, args []any, queue string) error } diff --git a/contracts/queue/job.go b/contracts/queue/job.go index 8eff008cc..00ab1d9b3 100644 --- a/contracts/queue/job.go +++ b/contracts/queue/job.go @@ -9,6 +9,13 @@ type Job interface { Handle(args ...any) error } +type JobRepository interface { + All() []Job + Call(signature string, args []any) error + Get(signature string) (Job, error) + Register(jobs []Job) +} + type Jobs struct { Job Job Args []any diff --git a/contracts/queue/queue.go b/contracts/queue/queue.go index 3297fa516..26b92c016 100644 --- a/contracts/queue/queue.go +++ b/contracts/queue/queue.go @@ -1,17 +1,18 @@ package queue type Queue interface { - Worker(payloads ...Args) Worker - // Register register jobs - Register(jobs []Job) - // GetJobs get all jobs - GetJobs() []Job + // All get all jobs + All() []Job + // Chain creates a chain of jobs to be processed one by one, passing + Chain(jobs []Jobs) Task // GetJob get job by signature GetJob(signature string) (Job, error) // Job add a job to queue Job(job Job, args []any) Task - // Chain creates a chain of jobs to be processed one by one, passing - Chain(jobs []Jobs) Task + // Register register jobs + Register(jobs []Job) + // Worker create a queue worker + Worker(payloads ...Args) Worker } type Worker interface { diff --git a/contracts/queue/task.go b/contracts/queue/task.go index 46ff8f2fc..ff7a1df5b 100644 --- a/contracts/queue/task.go +++ b/contracts/queue/task.go @@ -10,7 +10,7 @@ type Task interface { // DispatchSync dispatches the task synchronously. DispatchSync() error // Delay dispatches the task after the given delay. - Delay(time time.Duration) Task + Delay(time time.Time) Task // OnConnection sets the connection of the task. OnConnection(connection string) Task // OnQueue sets the queue of the task. diff --git a/errors/list.go b/errors/list.go index 8ff6b5271..a801ce039 100644 --- a/errors/list.go +++ b/errors/list.go @@ -104,10 +104,15 @@ var ( OrmRecordNotFound = New("record not found") OrmDeletedAtColumnNotFound = New("deleted at column not found") + QueueDriverAsyncNoJobFound = New("no job found in %s queue") + QueueDriverSyncNotNeedRun = New("queue %s driver sync not need run") QueueDriverNotSupported = New("unknown queue driver: %s") + QueueDriverInvalid = New("%s doesn't implement contracts/queue/driver") QueueDuplicateJobSignature = New("job signature duplicate: %s, the names of Job and Listener cannot be duplicated") QueueEmptyJobSignature = New("the Signature of job can't be empty") QueueEmptyListenerSignature = New("the Signature of listener can't be empty") + QueueJobNotFound = New("job not found: %s") + QueueFailedToSaveFailedJob = New("failed to save failed job: %v") RouteDefaultDriverNotSet = New("please set default driver") RouteInvalidDriver = New("init %s route driver fail: route must be implement route.Route or func() (route.Route, error)") diff --git a/queue/application.go b/queue/application.go index 3f0e3019f..6da59ec50 100644 --- a/queue/application.go +++ b/queue/application.go @@ -6,8 +6,8 @@ import ( ) type Application struct { - config *Config - job *JobImpl + config queue.Config + job queue.JobRepository } func NewApplication(config configcontract.Config) *Application { @@ -17,6 +17,25 @@ func NewApplication(config configcontract.Config) *Application { } } +func (app *Application) All() []queue.Job { + return app.job.All() +} +func (app *Application) Chain(jobs []queue.Jobs) queue.Task { + return NewChainTask(app.config, jobs) +} + +func (app *Application) GetJob(signature string) (queue.Job, error) { + return app.job.Get(signature) +} + +func (app *Application) Job(job queue.Job, args []any) queue.Task { + return NewTask(app.config, job, args) +} + +func (app *Application) Register(jobs []queue.Job) { + app.job.Register(jobs) +} + func (app *Application) Worker(payloads ...queue.Args) queue.Worker { defaultConnection := app.config.DefaultConnection() @@ -32,23 +51,3 @@ func (app *Application) Worker(payloads ...queue.Args) queue.Worker { return NewWorker(app.config, payloads[0].Concurrent, payloads[0].Connection, app.config.Queue(payloads[0].Connection, payloads[0].Queue), app.job) } - -func (app *Application) Register(jobs []queue.Job) { - app.job.Register(jobs) -} - -func (app *Application) GetJobs() []queue.Job { - return app.job.GetJobs() -} - -func (app *Application) GetJob(signature string) (queue.Job, error) { - return app.job.Get(signature) -} - -func (app *Application) Job(job queue.Job, args []any) queue.Task { - return NewTask(app.config, job, args) -} - -func (app *Application) Chain(jobs []queue.Jobs) queue.Task { - return NewChainTask(app.config, jobs) -} diff --git a/queue/config.go b/queue/config.go index b749086e5..9b9701b97 100644 --- a/queue/config.go +++ b/queue/config.go @@ -5,22 +5,41 @@ import ( configcontract "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/database/orm" + "github.com/goravel/framework/contracts/queue" ) type Config struct { config configcontract.Config } -func NewConfig(config configcontract.Config) *Config { +func NewConfig(config configcontract.Config) queue.Config { return &Config{ config: config, } } +func (r *Config) Debug() bool { + return r.config.GetBool("app.debug") +} + func (r *Config) DefaultConnection() string { return r.config.GetString("queue.default") } +func (r *Config) Driver(connection string) string { + if connection == "" { + connection = r.DefaultConnection() + } + + return r.config.GetString(fmt.Sprintf("queue.connections.%s.driver", connection)) +} + +func (r *Config) FailedJobsQuery() orm.Query { + connection := r.config.GetString("queue.failed.database") + table := r.config.GetString("queue.failed.table") + return OrmFacade.Connection(connection).Query().Table(table) +} + func (r *Config) Queue(connection, queue string) string { appName := r.config.GetString("app.name") if appName == "" { @@ -36,14 +55,6 @@ func (r *Config) Queue(connection, queue string) string { return fmt.Sprintf("%s_queues:%s", appName, queue) } -func (r *Config) Driver(connection string) string { - if connection == "" { - connection = r.DefaultConnection() - } - - return r.config.GetString(fmt.Sprintf("queue.connections.%s.driver", connection)) -} - // Redis returns the Redis configuration for a given connection. // TODO: Will be removed in v1.17 func (r *Config) Redis(queueConnection string) (dsn string, database int, queue string) { @@ -78,9 +89,3 @@ func (r *Config) Via(connection string) any { return r.config.Get(fmt.Sprintf("queue.connections.%s.via", connection)) } - -func (r *Config) FailedJobsQuery() orm.Query { - connection := r.config.GetString("queue.failed.database") - table := r.config.GetString("queue.failed.table") - return OrmFacade.Connection(connection).Query().Table(table) -} diff --git a/queue/config_test.go b/queue/config_test.go index 4aa7a2248..19e009b7f 100644 --- a/queue/config_test.go +++ b/queue/config_test.go @@ -5,13 +5,14 @@ import ( "github.com/stretchr/testify/suite" - configmock "github.com/goravel/framework/mocks/config" + "github.com/goravel/framework/contracts/queue" + mocksconfig "github.com/goravel/framework/mocks/config" ) type ConfigTestSuite struct { suite.Suite - config *Config - mockConfig *configmock.Config + config queue.Config + mockConfig *mocksconfig.Config } func TestConfigTestSuite(t *testing.T) { @@ -19,7 +20,7 @@ func TestConfigTestSuite(t *testing.T) { } func (s *ConfigTestSuite) SetupTest() { - s.mockConfig = &configmock.Config{} + s.mockConfig = mocksconfig.NewConfig(s.T()) s.config = NewConfig(s.mockConfig) } diff --git a/queue/driver.go b/queue/driver.go index 4d1640623..1ea4f0ddb 100644 --- a/queue/driver.go +++ b/queue/driver.go @@ -1,55 +1,28 @@ package queue import ( - "fmt" - "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" ) -const DriverSync string = "sync" -const DriverASync string = "async" -const DriverMachinery string = "machinery" // TODO: Will be removed in v1.17 -const DriverCustom string = "custom" - -type Driver interface { - New(store string) (queue.Driver, error) -} - -type DriverImpl struct { - connection string - config *Config -} - -func NewDriverImpl(connection string, config *Config) *DriverImpl { - return &DriverImpl{ - connection: connection, - config: config, - } -} - -func (d *DriverImpl) New() (queue.Driver, error) { - switch d.config.Driver(d.connection) { - case DriverSync: - return NewSync(d.connection), nil - case DriverASync: - return NewASync(d.connection, d.config.Size(d.connection)), nil - case DriverMachinery: - return NewMachinery(d.connection, d.config, LogFacade), nil // TODO: Will be removed in v1.17 - case DriverCustom: - return d.custom(d.connection) +func NewDriver(connection string, config queue.Config) (queue.Driver, error) { + switch config.Driver(connection) { + case queue.DriverSync: + return NewSync(connection), nil + case queue.DriverAsync: + return NewAsync(connection, config.Size(connection)), nil + case queue.DriverMachinery: + return NewMachinery(connection, config, LogFacade), nil // TODO: Will be removed in v1.17 + case queue.DriverCustom: + custom := config.Via(connection) + if driver, ok := custom.(queue.Driver); ok { + return driver, nil + } + if driver, ok := custom.(func() (queue.Driver, error)); ok { + return driver() + } + return nil, errors.QueueDriverInvalid.Args(connection) default: - return nil, fmt.Errorf("invalid driver: %s, only support sync, async, custom\n", d.connection) + return nil, errors.QueueDriverNotSupported.Args(config.Driver(connection)) } } - -func (d *DriverImpl) custom(connection string) (queue.Driver, error) { - custom := d.config.Via(connection) - if driver, ok := custom.(queue.Driver); ok { - return driver, nil - } - if driver, ok := custom.(func() (queue.Driver, error)); ok { - return driver() - } - - return nil, fmt.Errorf("%s doesn't implement contracts/queue/driver\n", connection) -} diff --git a/queue/driver_async.go b/queue/driver_async.go index b1ac44959..c9c5bde89 100644 --- a/queue/driver_async.go +++ b/queue/driver_async.go @@ -1,41 +1,41 @@ package queue import ( - "fmt" "sync" "time" contractsqueue "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" ) var asyncQueues sync.Map -type ASync struct { +type Async struct { connection string size int } -func NewASync(connection string, size int) *ASync { - return &ASync{ +func NewAsync(connection string, size int) *Async { + return &Async{ connection: connection, size: size, } } -func (r *ASync) Connection() string { +func (r *Async) Connection() string { return r.connection } -func (r *ASync) Driver() string { - return DriverASync +func (r *Async) Driver() string { + return contractsqueue.DriverAsync } -func (r *ASync) Push(job contractsqueue.Job, args []any, queue string) error { +func (r *Async) Push(job contractsqueue.Job, args []any, queue string) error { r.getQueue(queue) <- contractsqueue.Jobs{Job: job, Args: args} return nil } -func (r *ASync) Bulk(jobs []contractsqueue.Jobs, queue string) error { +func (r *Async) Bulk(jobs []contractsqueue.Jobs, queue string) error { for _, job := range jobs { if job.Delay > 0 { go func(j contractsqueue.Jobs) { @@ -51,19 +51,19 @@ func (r *ASync) Bulk(jobs []contractsqueue.Jobs, queue string) error { return nil } -func (r *ASync) Later(delay time.Duration, job contractsqueue.Job, args []any, queue string) error { +func (r *Async) Later(delay time.Time, job contractsqueue.Job, args []any, queue string) error { go func() { - time.Sleep(delay) + time.Sleep(time.Until(delay)) r.getQueue(queue) <- contractsqueue.Jobs{Job: job, Args: args} }() return nil } -func (r *ASync) Pop(queue string) (contractsqueue.Job, []any, error) { +func (r *Async) Pop(queue string) (contractsqueue.Job, []any, error) { ch, ok := asyncQueues.Load(queue) if !ok { - return nil, nil, fmt.Errorf("no queue found: %s", queue) + return nil, nil, errors.QueueDriverAsyncNoJobFound.Args(queue) } queueChan := ch.(chan contractsqueue.Jobs) @@ -71,11 +71,11 @@ func (r *ASync) Pop(queue string) (contractsqueue.Job, []any, error) { case job := <-queueChan: return job.Job, job.Args, nil default: - return nil, nil, fmt.Errorf("no job found in %s queue", queue) + return nil, nil, errors.QueueDriverAsyncNoJobFound.Args(queue) } } -func (r *ASync) getQueue(queue string) chan contractsqueue.Jobs { +func (r *Async) getQueue(queue string) chan contractsqueue.Jobs { ch, ok := asyncQueues.Load(queue) if !ok { ch = make(chan contractsqueue.Jobs, r.size) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 95ec687d2..49253b2bc 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/queue" - configmock "github.com/goravel/framework/mocks/config" - ormmock "github.com/goravel/framework/mocks/database/orm" - queuemock "github.com/goravel/framework/mocks/queue" + mocksconfig "github.com/goravel/framework/mocks/config" + mocksorm "github.com/goravel/framework/mocks/database/orm" + mocksqueue "github.com/goravel/framework/mocks/queue" ) var ( @@ -24,23 +24,15 @@ var ( type DriverAsyncTestSuite struct { suite.Suite app *Application - mockConfig *configmock.Config - mockQueue *queuemock.Queue + mockConfig *mocksconfig.Config + mockQueue *mocksqueue.Queue } func TestDriverAsyncTestSuite(t *testing.T) { - mockConfig := &configmock.Config{} - mockQueue := &queuemock.Queue{} + mockConfig := mocksconfig.NewConfig(t) + mockQueue := mocksqueue.NewQueue(t) app := NewApplication(mockConfig) - mockOrm := &ormmock.Orm{} - mockQuery := &ormmock.Query{} - mockOrm.On("Connection", "database").Return(mockOrm) - mockOrm.On("Query").Return(mockQuery) - mockQuery.On("Table", "failed_jobs").Return(mockQuery) - - OrmFacade = mockOrm - app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}}) suite.Run(t, &DriverAsyncTestSuite{ app: app, @@ -51,6 +43,14 @@ func TestDriverAsyncTestSuite(t *testing.T) { func (s *DriverAsyncTestSuite) SetupTest() { testAsyncJob = 0 + + s.mockConfig = mocksconfig.NewConfig(s.T()) + mockOrm := mocksorm.NewOrm(s.T()) + mockQuery := mocksorm.NewQuery(s.T()) + mockOrm.EXPECT().Connection("database").Return(mockOrm) + mockOrm.On("Query").Return(mockQuery) + mockQuery.On("Table", "failed_jobs").Return(mockQuery) + OrmFacade = mockOrm } func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { @@ -75,9 +75,6 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { s.Nil(s.app.Job(&TestAsyncJob{}, []any{"TestDefaultAsyncQueue", 1}).Dispatch()) time.Sleep(2 * time.Second) s.Equal(1, testAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) - s.mockQueue.AssertExpectations(s.T()) } func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { @@ -101,14 +98,11 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { s.Nil(worker.Shutdown()) }(ctx) time.Sleep(1 * time.Second) - s.Nil(s.app.Job(&TestDelayAsyncJob{}, []any{"TestDelayAsyncQueue", 1}).OnQueue("delay").Delay(3 * time.Second).Dispatch()) + s.Nil(s.app.Job(&TestDelayAsyncJob{}, []any{"TestDelayAsyncQueue", 1}).OnQueue("delay").Delay(time.Now().Add(3 * time.Second)).Dispatch()) time.Sleep(2 * time.Second) s.Equal(0, testDelayAsyncJob) time.Sleep(3 * time.Second) s.Equal(1, testDelayAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) - s.mockQueue.AssertExpectations(s.T()) } func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { @@ -137,9 +131,6 @@ func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { s.Nil(s.app.Job(&TestCustomAsyncJob{}, []any{"TestCustomAsyncQueue", 1}).OnConnection("custom").OnQueue("custom1").Dispatch()) time.Sleep(2 * time.Second) s.Equal(1, testCustomAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) - s.mockQueue.AssertExpectations(s.T()) } func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { @@ -167,9 +158,6 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.Error(s.app.Job(&TestErrorAsyncJob{}, []any{"TestErrorAsyncQueue", 1}).OnConnection("redis").OnQueue("error1").Dispatch()) time.Sleep(2 * time.Second) s.Equal(0, testErrorAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) - s.mockQueue.AssertExpectations(s.T()) } func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { @@ -208,8 +196,6 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { time.Sleep(2 * time.Second) s.Equal(1, testChainAsyncJob) s.Equal(1, testAsyncJob) - - s.mockConfig.AssertExpectations(s.T()) } type TestAsyncJob struct { diff --git a/queue/driver_machinery.go b/queue/driver_machinery.go index ce78bbf52..36ea81a9a 100644 --- a/queue/driver_machinery.go +++ b/queue/driver_machinery.go @@ -12,17 +12,17 @@ import ( "github.com/RichardKnop/machinery/v2/locks/eager" "github.com/RichardKnop/machinery/v2/log" - logcontract "github.com/goravel/framework/contracts/log" + contractslog "github.com/goravel/framework/contracts/log" "github.com/goravel/framework/contracts/queue" ) type Machinery struct { connection string - config *Config - log logcontract.Log + config queue.Config + log contractslog.Log } -func NewMachinery(connection string, config *Config, log logcontract.Log) *Machinery { +func NewMachinery(connection string, config queue.Config, log contractslog.Log) *Machinery { return &Machinery{ connection: connection, config: config, @@ -49,7 +49,7 @@ func (m *Machinery) Bulk(jobs []queue.Jobs, queue string) error { panic("implement me") } -func (m *Machinery) Later(delay time.Duration, job queue.Job, args []any, queue string) error { +func (m *Machinery) Later(delay time.Time, job queue.Job, args []any, queue string) error { //TODO implement me panic("implement me") } @@ -74,7 +74,7 @@ func (m *Machinery) server(queue string) *machinery.Server { backend := redisbackend.NewGR(cnf, []string{redisConfig}, database) lock := eager.New() - debug := m.config.config.GetBool("app.debug") + debug := m.config.Debug() log.DEBUG = NewDebug(debug, m.log) log.INFO = NewInfo(debug, m.log) log.WARNING = NewWarning(debug, m.log) diff --git a/queue/driver_machinery_test.go b/queue/driver_machinery_test.go index e23ad832f..885b4b75b 100644 --- a/queue/driver_machinery_test.go +++ b/queue/driver_machinery_test.go @@ -7,14 +7,14 @@ import ( "github.com/stretchr/testify/suite" - configmock "github.com/goravel/framework/mocks/config" - logmock "github.com/goravel/framework/mocks/log" + mocksconfig "github.com/goravel/framework/mocks/config" + mockslog "github.com/goravel/framework/mocks/log" ) type MachineryTestSuite struct { suite.Suite - mockConfig *configmock.Config - mockLog *logmock.Log + mockConfig *mocksconfig.Config + mockLog *mockslog.Log machinery *Machinery } @@ -23,8 +23,8 @@ func TestMachineryTestSuite(t *testing.T) { } func (s *MachineryTestSuite) SetupTest() { - s.mockConfig = &configmock.Config{} - s.mockLog = &logmock.Log{} + s.mockConfig = mocksconfig.NewConfig(s.T()) + s.mockLog = mockslog.NewLog(s.T()) } func (s *MachineryTestSuite) TestServer() { @@ -59,7 +59,6 @@ func (s *MachineryTestSuite) TestServer() { test.setup() server := s.machinery.server(test.queue) s.Equal(test.expectServer, server != nil) - s.mockConfig.AssertExpectations(s.T()) }) } } diff --git a/queue/driver_sync.go b/queue/driver_sync.go index f4a43b9c6..cb2e1419f 100644 --- a/queue/driver_sync.go +++ b/queue/driver_sync.go @@ -21,7 +21,7 @@ func (r *Sync) Connection() string { } func (r *Sync) Driver() string { - return DriverSync + return queue.DriverSync } func (r *Sync) Push(job queue.Job, args []any, _ string) error { @@ -41,8 +41,8 @@ func (r *Sync) Bulk(jobs []queue.Jobs, _ string) error { return nil } -func (r *Sync) Later(delay time.Duration, job queue.Job, args []any, _ string) error { - time.Sleep(delay) +func (r *Sync) Later(delay time.Time, job queue.Job, args []any, _ string) error { + time.Sleep(time.Until(delay)) return job.Handle(args...) } diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index c856ecafa..224b7ea53 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/suite" "github.com/goravel/framework/contracts/queue" - configmock "github.com/goravel/framework/mocks/config" - queuemock "github.com/goravel/framework/mocks/queue" + mocksconfig "github.com/goravel/framework/mocks/config" + mocksqueue "github.com/goravel/framework/mocks/queue" ) var ( @@ -19,13 +19,13 @@ var ( type DriverSyncTestSuite struct { suite.Suite app *Application - mockConfig *configmock.Config - mockQueue *queuemock.Queue + mockConfig *mocksconfig.Config + mockQueue *mocksqueue.Queue } func TestDriverSyncTestSuite(t *testing.T) { - mockConfig := &configmock.Config{} - mockQueue := &queuemock.Queue{} + mockConfig := mocksconfig.NewConfig(t) + mockQueue := mocksqueue.NewQueue(t) app := NewApplication(mockConfig) app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}}) diff --git a/queue/job.go b/queue/job.go index 3025db8ab..04fb786f1 100644 --- a/queue/job.go +++ b/queue/job.go @@ -3,25 +3,21 @@ package queue import ( "sync" + "github.com/google/uuid" + contractsqueue "github.com/goravel/framework/contracts/queue" "github.com/goravel/framework/errors" "github.com/goravel/framework/support/carbon" ) type FailedJob struct { - ID uint `gorm:"primaryKey"` // The unique ID of the job. - Queue string `gorm:"not null"` // The name of the queue the job belongs to. - Signature string `gorm:"not null"` // The signature of the handler for this job. - Payloads []any `gorm:"not null;serializer:json"` // The arguments passed to the job. - Exception string `gorm:"not null"` // The exception that caused the job to fail. - FailedAt carbon.DateTime `gorm:"not null"` // The timestamp when the job failed. -} - -type Job interface { - Register(jobs []contractsqueue.Job) error - Call(signature string, args []any) error - Get(signature string) (contractsqueue.Job, error) - GetJobs() []contractsqueue.Job + ID uint `gorm:"primaryKey"` // The unique ID of the job. + UUID uuid.UUID `gorm:"not null;unique"` // The UUID of the job. + Connection string `gorm:"not null"` // The name of the connection the job belongs to. + Queue string `gorm:"not null"` // The name of the queue the job belongs to. + Payload []any `gorm:"not null;serializer:json"` // The arguments passed to the job. + Exception string `gorm:"not null"` // The exception that caused the job to fail. + FailedAt carbon.DateTime `gorm:"not null"` // The timestamp when the job failed. } type JobImpl struct { @@ -32,11 +28,15 @@ func NewJobImpl() *JobImpl { return &JobImpl{} } -// Register registers jobs to the job manager -func (r *JobImpl) Register(jobs []contractsqueue.Job) { - for _, job := range jobs { - r.jobs.Store(job.Signature(), job) - } +// All gets all registered jobs +func (r *JobImpl) All() []contractsqueue.Job { + var jobs []contractsqueue.Job + r.jobs.Range(func(_, value any) bool { + jobs = append(jobs, value.(contractsqueue.Job)) + return true + }) + + return jobs } // Call calls a registered job using its signature @@ -55,16 +55,12 @@ func (r *JobImpl) Get(signature string) (contractsqueue.Job, error) { return job.(contractsqueue.Job), nil } - return nil, errors.New("job not found") + return nil, errors.QueueJobNotFound.Args(signature) } -// GetJobs gets all registered jobs -func (r *JobImpl) GetJobs() []contractsqueue.Job { - var jobs []contractsqueue.Job - r.jobs.Range(func(_, value any) bool { - jobs = append(jobs, value.(contractsqueue.Job)) - return true - }) - - return jobs +// Register registers jobs to the job manager +func (r *JobImpl) Register(jobs []contractsqueue.Job) { + for _, job := range jobs { + r.jobs.Store(job.Signature(), job) + } } diff --git a/queue/job_test.go b/queue/job_test.go index db180d236..94222d798 100644 --- a/queue/job_test.go +++ b/queue/job_test.go @@ -27,7 +27,7 @@ func (s *JobTestSuite) RegisterJobsSuccessfully() { &MockJob{signature: "job2"}, }) - registeredJobs := s.jobManager.GetJobs() + registeredJobs := s.jobManager.All() s.Len(registeredJobs, 2) } diff --git a/queue/service_provider.go b/queue/service_provider.go index 5e9117e7c..c96a352e5 100644 --- a/queue/service_provider.go +++ b/queue/service_provider.go @@ -12,7 +12,7 @@ import ( const Binding = "goravel.queue" var ( - LogFacade log.Log // TODO: Will be removed in v1.17 + LogFacade log.Log OrmFacade orm.Orm ) @@ -31,7 +31,7 @@ func (receiver *ServiceProvider) Register(app foundation.Application) { } func (receiver *ServiceProvider) Boot(app foundation.Application) { - LogFacade = app.MakeLog() // TODO: Will be removed in v1.17 + LogFacade = app.MakeLog() OrmFacade = app.MakeOrm() receiver.registerCommands(app) diff --git a/queue/task.go b/queue/task.go index 3c0999d5a..55d0ac77e 100644 --- a/queue/task.go +++ b/queue/task.go @@ -7,20 +7,18 @@ import ( ) type Task struct { - config *Config + config queue.Config connection string chain bool - delay time.Duration - driver *DriverImpl + delay time.Time jobs []queue.Jobs queue string } -func NewTask(config *Config, job queue.Job, args []any) *Task { +func NewTask(config queue.Config, job queue.Job, args []any) *Task { return &Task{ config: config, connection: config.DefaultConnection(), - driver: NewDriverImpl(config.DefaultConnection(), config), jobs: []queue.Jobs{ { Job: job, @@ -31,70 +29,63 @@ func NewTask(config *Config, job queue.Job, args []any) *Task { } } -func NewChainTask(config *Config, jobs []queue.Jobs) *Task { +func NewChainTask(config queue.Config, jobs []queue.Jobs) *Task { return &Task{ config: config, connection: config.DefaultConnection(), chain: true, - driver: NewDriverImpl(config.DefaultConnection(), config), jobs: jobs, queue: config.Queue(config.DefaultConnection(), ""), } } // Delay sets a delay time for the task -func (receiver *Task) Delay(delay time.Duration) queue.Task { - receiver.delay = delay - - return receiver +func (r *Task) Delay(delay time.Time) queue.Task { + r.delay = delay + return r } // Dispatch dispatches the task -func (receiver *Task) Dispatch() error { - driver, err := receiver.driver.New() +func (r *Task) Dispatch() error { + driver, err := NewDriver(r.connection, r.config) if err != nil { return err } - if receiver.chain { - return driver.Bulk(receiver.jobs, receiver.queue) + if r.chain { + return driver.Bulk(r.jobs, r.queue) } else { - job := receiver.jobs[0] - if receiver.delay > 0 { - return driver.Later(receiver.delay, job.Job, job.Args, receiver.queue) + job := r.jobs[0] + if !r.delay.IsZero() { + return driver.Later(r.delay, job.Job, job.Args, r.queue) } - return driver.Push(job.Job, job.Args, receiver.queue) + return driver.Push(job.Job, job.Args, r.queue) } } // DispatchSync dispatches the task synchronously -func (receiver *Task) DispatchSync() error { - if receiver.chain { - for _, job := range receiver.jobs { +func (r *Task) DispatchSync() error { + if r.chain { + for _, job := range r.jobs { if err := job.Job.Handle(job.Args...); err != nil { return err } } - return nil } else { - job := receiver.jobs[0] - + job := r.jobs[0] return job.Job.Handle(job.Args...) } } // OnConnection sets the connection name -func (receiver *Task) OnConnection(connection string) queue.Task { - receiver.connection = connection - receiver.driver = NewDriverImpl(connection, receiver.config) - - return receiver +func (r *Task) OnConnection(connection string) queue.Task { + r.connection = connection + return r } // OnQueue sets the queue name -func (receiver *Task) OnQueue(queue string) queue.Task { - receiver.queue = receiver.config.Queue(receiver.connection, queue) - - return receiver +func (r *Task) OnQueue(queue string) queue.Task { + r.queue = r.config.Queue(r.connection, queue) + return r } diff --git a/queue/utils.go b/queue/utils.go index cb7233806..4a82c9433 100644 --- a/queue/utils.go +++ b/queue/utils.go @@ -1,11 +1,9 @@ package queue import ( - "errors" - "fmt" - "github.com/goravel/framework/contracts/event" "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" ) func jobs2Tasks(jobs []queue.Job) (map[string]any, error) { @@ -13,11 +11,11 @@ func jobs2Tasks(jobs []queue.Job) (map[string]any, error) { for _, job := range jobs { if job.Signature() == "" { - return nil, errors.New("the Signature of job can't be empty") + return nil, errors.QueueEmptyJobSignature } if tasks[job.Signature()] != nil { - return nil, fmt.Errorf("job signature duplicate: %s, the names of Job and Listener cannot be duplicated", job.Signature()) + return nil, errors.QueueDuplicateJobSignature.Args(job.Signature()) } tasks[job.Signature()] = job.Handle @@ -32,7 +30,7 @@ func eventsToTasks(events map[event.Event][]event.Listener) (map[string]any, err for _, listeners := range events { for _, listener := range listeners { if listener.Signature() == "" { - return nil, errors.New("the Signature of listener can't be empty") + return nil, errors.QueueEmptyListenerSignature } if tasks[listener.Signature()] != nil { diff --git a/queue/worker.go b/queue/worker.go index fb06faedd..6cfc1c5c8 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -1,29 +1,32 @@ package queue import ( - "fmt" "time" - "github.com/goravel/framework/contracts/database/orm" + "github.com/google/uuid" + + "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" "github.com/goravel/framework/support/carbon" ) type Worker struct { concurrent int - driver *DriverImpl - job *JobImpl - failedJobs orm.Query - queue string + config queue.Config + connection string + driver queue.Driver failedJobChan chan FailedJob isShutdown bool + job queue.JobRepository + queue string } -func NewWorker(config *Config, concurrent int, connection string, queue string, job *JobImpl) *Worker { +func NewWorker(config queue.Config, concurrent int, connection string, queue string, job queue.JobRepository) *Worker { return &Worker{ concurrent: concurrent, - driver: NewDriverImpl(connection, config), + config: config, + connection: connection, job: job, - failedJobs: config.FailedJobsQuery(), queue: queue, failedJobChan: make(chan FailedJob), } @@ -32,12 +35,12 @@ func NewWorker(config *Config, concurrent int, connection string, queue string, func (r *Worker) Run() error { r.isShutdown = false - driver, err := r.driver.New() + driver, err := NewDriver(r.connection, r.config) if err != nil { return err } - if driver.Driver() == DriverSync { - return fmt.Errorf("queue %s driver not need run", r.queue) + if driver.Driver() == queue.DriverSync { + return errors.QueueDriverSyncNotNeedRun.Args(r.queue) } for i := 0; i < r.concurrent; i++ { @@ -57,11 +60,12 @@ func (r *Worker) Run() error { if err = r.job.Call(job.Signature(), args); err != nil { r.failedJobChan <- FailedJob{ - Queue: r.queue, - Signature: job.Signature(), - Payloads: args, - Exception: err.Error(), - FailedAt: carbon.DateTime{Carbon: carbon.Now()}, + UUID: uuid.New(), + Connection: r.connection, + Queue: r.queue, + Payload: args, + Exception: err.Error(), + FailedAt: carbon.DateTime{Carbon: carbon.Now()}, } } } @@ -70,7 +74,9 @@ func (r *Worker) Run() error { go func() { for job := range r.failedJobChan { - _ = r.failedJobs.Create(&job) + if err = r.config.FailedJobsQuery().Create(&job); err != nil { + LogFacade.Error(errors.QueueFailedToSaveFailedJob.Args(err)) + } } }() From f097ecc7268d618b6dd7b36cdd98c99bde5be652 Mon Sep 17 00:00:00 2001 From: devhaozi Date: Mon, 6 Jan 2025 14:14:58 +0000 Subject: [PATCH 18/32] chore: update mocks --- mocks/queue/Config.go | 422 +++++++++++++++++++++++++++++++++++ mocks/queue/Driver.go | 12 +- mocks/queue/JobRepository.go | 220 ++++++++++++++++++ mocks/queue/Queue.go | 94 ++++---- mocks/queue/Task.go | 12 +- 5 files changed, 701 insertions(+), 59 deletions(-) create mode 100644 mocks/queue/Config.go create mode 100644 mocks/queue/JobRepository.go diff --git a/mocks/queue/Config.go b/mocks/queue/Config.go new file mode 100644 index 000000000..4a02b9f00 --- /dev/null +++ b/mocks/queue/Config.go @@ -0,0 +1,422 @@ +// Code generated by mockery. DO NOT EDIT. + +package queue + +import ( + orm "github.com/goravel/framework/contracts/database/orm" + mock "github.com/stretchr/testify/mock" +) + +// Config is an autogenerated mock type for the Config type +type Config struct { + mock.Mock +} + +type Config_Expecter struct { + mock *mock.Mock +} + +func (_m *Config) EXPECT() *Config_Expecter { + return &Config_Expecter{mock: &_m.Mock} +} + +// Debug provides a mock function with no fields +func (_m *Config) Debug() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Debug") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// Config_Debug_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Debug' +type Config_Debug_Call struct { + *mock.Call +} + +// Debug is a helper method to define mock.On call +func (_e *Config_Expecter) Debug() *Config_Debug_Call { + return &Config_Debug_Call{Call: _e.mock.On("Debug")} +} + +func (_c *Config_Debug_Call) Run(run func()) *Config_Debug_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Config_Debug_Call) Return(_a0 bool) *Config_Debug_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_Debug_Call) RunAndReturn(run func() bool) *Config_Debug_Call { + _c.Call.Return(run) + return _c +} + +// DefaultConnection provides a mock function with no fields +func (_m *Config) DefaultConnection() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DefaultConnection") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Config_DefaultConnection_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DefaultConnection' +type Config_DefaultConnection_Call struct { + *mock.Call +} + +// DefaultConnection is a helper method to define mock.On call +func (_e *Config_Expecter) DefaultConnection() *Config_DefaultConnection_Call { + return &Config_DefaultConnection_Call{Call: _e.mock.On("DefaultConnection")} +} + +func (_c *Config_DefaultConnection_Call) Run(run func()) *Config_DefaultConnection_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Config_DefaultConnection_Call) Return(_a0 string) *Config_DefaultConnection_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_DefaultConnection_Call) RunAndReturn(run func() string) *Config_DefaultConnection_Call { + _c.Call.Return(run) + return _c +} + +// Driver provides a mock function with given fields: connection +func (_m *Config) Driver(connection string) string { + ret := _m.Called(connection) + + if len(ret) == 0 { + panic("no return value specified for Driver") + } + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(connection) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Config_Driver_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Driver' +type Config_Driver_Call struct { + *mock.Call +} + +// Driver is a helper method to define mock.On call +// - connection string +func (_e *Config_Expecter) Driver(connection interface{}) *Config_Driver_Call { + return &Config_Driver_Call{Call: _e.mock.On("Driver", connection)} +} + +func (_c *Config_Driver_Call) Run(run func(connection string)) *Config_Driver_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Config_Driver_Call) Return(_a0 string) *Config_Driver_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_Driver_Call) RunAndReturn(run func(string) string) *Config_Driver_Call { + _c.Call.Return(run) + return _c +} + +// FailedJobsQuery provides a mock function with no fields +func (_m *Config) FailedJobsQuery() orm.Query { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for FailedJobsQuery") + } + + var r0 orm.Query + if rf, ok := ret.Get(0).(func() orm.Query); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(orm.Query) + } + } + + return r0 +} + +// Config_FailedJobsQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FailedJobsQuery' +type Config_FailedJobsQuery_Call struct { + *mock.Call +} + +// FailedJobsQuery is a helper method to define mock.On call +func (_e *Config_Expecter) FailedJobsQuery() *Config_FailedJobsQuery_Call { + return &Config_FailedJobsQuery_Call{Call: _e.mock.On("FailedJobsQuery")} +} + +func (_c *Config_FailedJobsQuery_Call) Run(run func()) *Config_FailedJobsQuery_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Config_FailedJobsQuery_Call) Return(_a0 orm.Query) *Config_FailedJobsQuery_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_FailedJobsQuery_Call) RunAndReturn(run func() orm.Query) *Config_FailedJobsQuery_Call { + _c.Call.Return(run) + return _c +} + +// Queue provides a mock function with given fields: connection, _a1 +func (_m *Config) Queue(connection string, _a1 string) string { + ret := _m.Called(connection, _a1) + + if len(ret) == 0 { + panic("no return value specified for Queue") + } + + var r0 string + if rf, ok := ret.Get(0).(func(string, string) string); ok { + r0 = rf(connection, _a1) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Config_Queue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Queue' +type Config_Queue_Call struct { + *mock.Call +} + +// Queue is a helper method to define mock.On call +// - connection string +// - _a1 string +func (_e *Config_Expecter) Queue(connection interface{}, _a1 interface{}) *Config_Queue_Call { + return &Config_Queue_Call{Call: _e.mock.On("Queue", connection, _a1)} +} + +func (_c *Config_Queue_Call) Run(run func(connection string, _a1 string)) *Config_Queue_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Config_Queue_Call) Return(_a0 string) *Config_Queue_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_Queue_Call) RunAndReturn(run func(string, string) string) *Config_Queue_Call { + _c.Call.Return(run) + return _c +} + +// Redis provides a mock function with given fields: queueConnection +func (_m *Config) Redis(queueConnection string) (string, int, string) { + ret := _m.Called(queueConnection) + + if len(ret) == 0 { + panic("no return value specified for Redis") + } + + var r0 string + var r1 int + var r2 string + if rf, ok := ret.Get(0).(func(string) (string, int, string)); ok { + return rf(queueConnection) + } + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(queueConnection) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(string) int); ok { + r1 = rf(queueConnection) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(string) string); ok { + r2 = rf(queueConnection) + } else { + r2 = ret.Get(2).(string) + } + + return r0, r1, r2 +} + +// Config_Redis_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Redis' +type Config_Redis_Call struct { + *mock.Call +} + +// Redis is a helper method to define mock.On call +// - queueConnection string +func (_e *Config_Expecter) Redis(queueConnection interface{}) *Config_Redis_Call { + return &Config_Redis_Call{Call: _e.mock.On("Redis", queueConnection)} +} + +func (_c *Config_Redis_Call) Run(run func(queueConnection string)) *Config_Redis_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Config_Redis_Call) Return(dsn string, database int, _a2 string) *Config_Redis_Call { + _c.Call.Return(dsn, database, _a2) + return _c +} + +func (_c *Config_Redis_Call) RunAndReturn(run func(string) (string, int, string)) *Config_Redis_Call { + _c.Call.Return(run) + return _c +} + +// Size provides a mock function with given fields: connection +func (_m *Config) Size(connection string) int { + ret := _m.Called(connection) + + if len(ret) == 0 { + panic("no return value specified for Size") + } + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(connection) + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} + +// Config_Size_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Size' +type Config_Size_Call struct { + *mock.Call +} + +// Size is a helper method to define mock.On call +// - connection string +func (_e *Config_Expecter) Size(connection interface{}) *Config_Size_Call { + return &Config_Size_Call{Call: _e.mock.On("Size", connection)} +} + +func (_c *Config_Size_Call) Run(run func(connection string)) *Config_Size_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Config_Size_Call) Return(_a0 int) *Config_Size_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_Size_Call) RunAndReturn(run func(string) int) *Config_Size_Call { + _c.Call.Return(run) + return _c +} + +// Via provides a mock function with given fields: connection +func (_m *Config) Via(connection string) interface{} { + ret := _m.Called(connection) + + if len(ret) == 0 { + panic("no return value specified for Via") + } + + var r0 interface{} + if rf, ok := ret.Get(0).(func(string) interface{}); ok { + r0 = rf(connection) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(interface{}) + } + } + + return r0 +} + +// Config_Via_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Via' +type Config_Via_Call struct { + *mock.Call +} + +// Via is a helper method to define mock.On call +// - connection string +func (_e *Config_Expecter) Via(connection interface{}) *Config_Via_Call { + return &Config_Via_Call{Call: _e.mock.On("Via", connection)} +} + +func (_c *Config_Via_Call) Run(run func(connection string)) *Config_Via_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Config_Via_Call) Return(_a0 interface{}) *Config_Via_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Config_Via_Call) RunAndReturn(run func(string) interface{}) *Config_Via_Call { + _c.Call.Return(run) + return _c +} + +// NewConfig creates a new instance of Config. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewConfig(t interface { + mock.TestingT + Cleanup(func()) +}) *Config { + mock := &Config{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/queue/Driver.go b/mocks/queue/Driver.go index e253b07f6..a0bf64605 100644 --- a/mocks/queue/Driver.go +++ b/mocks/queue/Driver.go @@ -160,7 +160,7 @@ func (_c *Driver_Driver_Call) RunAndReturn(run func() string) *Driver_Driver_Cal } // Later provides a mock function with given fields: delay, job, args, _a3 -func (_m *Driver) Later(delay time.Duration, job queue.Job, args []interface{}, _a3 string) error { +func (_m *Driver) Later(delay time.Time, job queue.Job, args []interface{}, _a3 string) error { ret := _m.Called(delay, job, args, _a3) if len(ret) == 0 { @@ -168,7 +168,7 @@ func (_m *Driver) Later(delay time.Duration, job queue.Job, args []interface{}, } var r0 error - if rf, ok := ret.Get(0).(func(time.Duration, queue.Job, []interface{}, string) error); ok { + if rf, ok := ret.Get(0).(func(time.Time, queue.Job, []interface{}, string) error); ok { r0 = rf(delay, job, args, _a3) } else { r0 = ret.Error(0) @@ -183,7 +183,7 @@ type Driver_Later_Call struct { } // Later is a helper method to define mock.On call -// - delay time.Duration +// - delay time.Time // - job queue.Job // - args []interface{} // - _a3 string @@ -191,9 +191,9 @@ func (_e *Driver_Expecter) Later(delay interface{}, job interface{}, args interf return &Driver_Later_Call{Call: _e.mock.On("Later", delay, job, args, _a3)} } -func (_c *Driver_Later_Call) Run(run func(delay time.Duration, job queue.Job, args []interface{}, _a3 string)) *Driver_Later_Call { +func (_c *Driver_Later_Call) Run(run func(delay time.Time, job queue.Job, args []interface{}, _a3 string)) *Driver_Later_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Duration), args[1].(queue.Job), args[2].([]interface{}), args[3].(string)) + run(args[0].(time.Time), args[1].(queue.Job), args[2].([]interface{}), args[3].(string)) }) return _c } @@ -203,7 +203,7 @@ func (_c *Driver_Later_Call) Return(_a0 error) *Driver_Later_Call { return _c } -func (_c *Driver_Later_Call) RunAndReturn(run func(time.Duration, queue.Job, []interface{}, string) error) *Driver_Later_Call { +func (_c *Driver_Later_Call) RunAndReturn(run func(time.Time, queue.Job, []interface{}, string) error) *Driver_Later_Call { _c.Call.Return(run) return _c } diff --git a/mocks/queue/JobRepository.go b/mocks/queue/JobRepository.go new file mode 100644 index 000000000..6dcc7c948 --- /dev/null +++ b/mocks/queue/JobRepository.go @@ -0,0 +1,220 @@ +// Code generated by mockery. DO NOT EDIT. + +package queue + +import ( + queue "github.com/goravel/framework/contracts/queue" + mock "github.com/stretchr/testify/mock" +) + +// JobRepository is an autogenerated mock type for the JobRepository type +type JobRepository struct { + mock.Mock +} + +type JobRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *JobRepository) EXPECT() *JobRepository_Expecter { + return &JobRepository_Expecter{mock: &_m.Mock} +} + +// All provides a mock function with no fields +func (_m *JobRepository) All() []queue.Job { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for All") + } + + var r0 []queue.Job + if rf, ok := ret.Get(0).(func() []queue.Job); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]queue.Job) + } + } + + return r0 +} + +// JobRepository_All_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'All' +type JobRepository_All_Call struct { + *mock.Call +} + +// All is a helper method to define mock.On call +func (_e *JobRepository_Expecter) All() *JobRepository_All_Call { + return &JobRepository_All_Call{Call: _e.mock.On("All")} +} + +func (_c *JobRepository_All_Call) Run(run func()) *JobRepository_All_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *JobRepository_All_Call) Return(_a0 []queue.Job) *JobRepository_All_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *JobRepository_All_Call) RunAndReturn(run func() []queue.Job) *JobRepository_All_Call { + _c.Call.Return(run) + return _c +} + +// Call provides a mock function with given fields: signature, args +func (_m *JobRepository) Call(signature string, args []interface{}) error { + ret := _m.Called(signature, args) + + if len(ret) == 0 { + panic("no return value specified for Call") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, []interface{}) error); ok { + r0 = rf(signature, args) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// JobRepository_Call_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Call' +type JobRepository_Call_Call struct { + *mock.Call +} + +// Call is a helper method to define mock.On call +// - signature string +// - args []interface{} +func (_e *JobRepository_Expecter) Call(signature interface{}, args interface{}) *JobRepository_Call_Call { + return &JobRepository_Call_Call{Call: _e.mock.On("Call", signature, args)} +} + +func (_c *JobRepository_Call_Call) Run(run func(signature string, args []interface{})) *JobRepository_Call_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].([]interface{})) + }) + return _c +} + +func (_c *JobRepository_Call_Call) Return(_a0 error) *JobRepository_Call_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *JobRepository_Call_Call) RunAndReturn(run func(string, []interface{}) error) *JobRepository_Call_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: signature +func (_m *JobRepository) Get(signature string) (queue.Job, error) { + ret := _m.Called(signature) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 queue.Job + var r1 error + if rf, ok := ret.Get(0).(func(string) (queue.Job, error)); ok { + return rf(signature) + } + if rf, ok := ret.Get(0).(func(string) queue.Job); ok { + r0 = rf(signature) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(queue.Job) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(signature) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// JobRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type JobRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - signature string +func (_e *JobRepository_Expecter) Get(signature interface{}) *JobRepository_Get_Call { + return &JobRepository_Get_Call{Call: _e.mock.On("Get", signature)} +} + +func (_c *JobRepository_Get_Call) Run(run func(signature string)) *JobRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *JobRepository_Get_Call) Return(_a0 queue.Job, _a1 error) *JobRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *JobRepository_Get_Call) RunAndReturn(run func(string) (queue.Job, error)) *JobRepository_Get_Call { + _c.Call.Return(run) + return _c +} + +// Register provides a mock function with given fields: jobs +func (_m *JobRepository) Register(jobs []queue.Job) { + _m.Called(jobs) +} + +// JobRepository_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' +type JobRepository_Register_Call struct { + *mock.Call +} + +// Register is a helper method to define mock.On call +// - jobs []queue.Job +func (_e *JobRepository_Expecter) Register(jobs interface{}) *JobRepository_Register_Call { + return &JobRepository_Register_Call{Call: _e.mock.On("Register", jobs)} +} + +func (_c *JobRepository_Register_Call) Run(run func(jobs []queue.Job)) *JobRepository_Register_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].([]queue.Job)) + }) + return _c +} + +func (_c *JobRepository_Register_Call) Return() *JobRepository_Register_Call { + _c.Call.Return() + return _c +} + +func (_c *JobRepository_Register_Call) RunAndReturn(run func([]queue.Job)) *JobRepository_Register_Call { + _c.Run(run) + return _c +} + +// NewJobRepository creates a new instance of JobRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewJobRepository(t interface { + mock.TestingT + Cleanup(func()) +}) *JobRepository { + mock := &JobRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/queue/Queue.go b/mocks/queue/Queue.go index 3bd0f0ed7..7e1743f4d 100644 --- a/mocks/queue/Queue.go +++ b/mocks/queue/Queue.go @@ -20,6 +20,53 @@ func (_m *Queue) EXPECT() *Queue_Expecter { return &Queue_Expecter{mock: &_m.Mock} } +// All provides a mock function with no fields +func (_m *Queue) All() []queue.Job { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for All") + } + + var r0 []queue.Job + if rf, ok := ret.Get(0).(func() []queue.Job); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]queue.Job) + } + } + + return r0 +} + +// Queue_All_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'All' +type Queue_All_Call struct { + *mock.Call +} + +// All is a helper method to define mock.On call +func (_e *Queue_Expecter) All() *Queue_All_Call { + return &Queue_All_Call{Call: _e.mock.On("All")} +} + +func (_c *Queue_All_Call) Run(run func()) *Queue_All_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Queue_All_Call) Return(_a0 []queue.Job) *Queue_All_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Queue_All_Call) RunAndReturn(run func() []queue.Job) *Queue_All_Call { + _c.Call.Return(run) + return _c +} + // Chain provides a mock function with given fields: jobs func (_m *Queue) Chain(jobs []queue.Jobs) queue.Task { ret := _m.Called(jobs) @@ -126,53 +173,6 @@ func (_c *Queue_GetJob_Call) RunAndReturn(run func(string) (queue.Job, error)) * return _c } -// GetJobs provides a mock function with no fields -func (_m *Queue) GetJobs() []queue.Job { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetJobs") - } - - var r0 []queue.Job - if rf, ok := ret.Get(0).(func() []queue.Job); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]queue.Job) - } - } - - return r0 -} - -// Queue_GetJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJobs' -type Queue_GetJobs_Call struct { - *mock.Call -} - -// GetJobs is a helper method to define mock.On call -func (_e *Queue_Expecter) GetJobs() *Queue_GetJobs_Call { - return &Queue_GetJobs_Call{Call: _e.mock.On("GetJobs")} -} - -func (_c *Queue_GetJobs_Call) Run(run func()) *Queue_GetJobs_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Queue_GetJobs_Call) Return(_a0 []queue.Job) *Queue_GetJobs_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Queue_GetJobs_Call) RunAndReturn(run func() []queue.Job) *Queue_GetJobs_Call { - _c.Call.Return(run) - return _c -} - // Job provides a mock function with given fields: job, args func (_m *Queue) Job(job queue.Job, args []interface{}) queue.Task { ret := _m.Called(job, args) diff --git a/mocks/queue/Task.go b/mocks/queue/Task.go index 6cf8a45aa..0e66c994d 100644 --- a/mocks/queue/Task.go +++ b/mocks/queue/Task.go @@ -23,7 +23,7 @@ func (_m *Task) EXPECT() *Task_Expecter { } // Delay provides a mock function with given fields: _a0 -func (_m *Task) Delay(_a0 time.Duration) queue.Task { +func (_m *Task) Delay(_a0 time.Time) queue.Task { ret := _m.Called(_a0) if len(ret) == 0 { @@ -31,7 +31,7 @@ func (_m *Task) Delay(_a0 time.Duration) queue.Task { } var r0 queue.Task - if rf, ok := ret.Get(0).(func(time.Duration) queue.Task); ok { + if rf, ok := ret.Get(0).(func(time.Time) queue.Task); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { @@ -48,14 +48,14 @@ type Task_Delay_Call struct { } // Delay is a helper method to define mock.On call -// - _a0 time.Duration +// - _a0 time.Time func (_e *Task_Expecter) Delay(_a0 interface{}) *Task_Delay_Call { return &Task_Delay_Call{Call: _e.mock.On("Delay", _a0)} } -func (_c *Task_Delay_Call) Run(run func(_a0 time.Duration)) *Task_Delay_Call { +func (_c *Task_Delay_Call) Run(run func(_a0 time.Time)) *Task_Delay_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(time.Duration)) + run(args[0].(time.Time)) }) return _c } @@ -65,7 +65,7 @@ func (_c *Task_Delay_Call) Return(_a0 queue.Task) *Task_Delay_Call { return _c } -func (_c *Task_Delay_Call) RunAndReturn(run func(time.Duration) queue.Task) *Task_Delay_Call { +func (_c *Task_Delay_Call) RunAndReturn(run func(time.Time) queue.Task) *Task_Delay_Call { _c.Call.Return(run) return _c } From 1c2cb05ef729b383a97a592a7f44a955dbe68217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 6 Jan 2025 23:36:41 +0800 Subject: [PATCH 19/32] feat: update tests --- queue/driver_async_test.go | 16 +++--- queue/driver_sync_test.go | 16 ++---- queue/job.go | 14 ++--- queue/worker.go | 8 ++- queue/worker_test.go | 102 +++++++++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 queue/worker_test.go diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 49253b2bc..442469ab7 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -43,14 +43,7 @@ func TestDriverAsyncTestSuite(t *testing.T) { func (s *DriverAsyncTestSuite) SetupTest() { testAsyncJob = 0 - s.mockConfig = mocksconfig.NewConfig(s.T()) - mockOrm := mocksorm.NewOrm(s.T()) - mockQuery := mocksorm.NewQuery(s.T()) - mockOrm.EXPECT().Connection("database").Return(mockOrm) - mockOrm.On("Query").Return(mockQuery) - mockQuery.On("Table", "failed_jobs").Return(mockQuery) - OrmFacade = mockOrm } func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { @@ -143,6 +136,13 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() + mockOrm := mocksorm.NewOrm(s.T()) + mockQuery := mocksorm.NewQuery(s.T()) + mockOrm.EXPECT().Connection("database").Return(mockOrm) + mockOrm.On("Query").Return(mockQuery) + mockQuery.On("Table", "failed_jobs").Return(mockQuery) + OrmFacade = mockOrm + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() go func(ctx context.Context) { @@ -193,7 +193,7 @@ func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { }, }).OnQueue("chain").Dispatch()) - time.Sleep(2 * time.Second) + time.Sleep(3 * time.Second) s.Equal(1, testChainAsyncJob) s.Equal(1, testAsyncJob) } diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index 224b7ea53..018f0541b 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -37,25 +37,21 @@ func TestDriverSyncTestSuite(t *testing.T) { } func (s *DriverSyncTestSuite) SetupTest() { + s.mockConfig.On("GetString", "queue.default").Return("sync").Once() + s.mockConfig.On("GetString", "app.name").Return("goravel").Once() + s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() testSyncJob = 0 testChainSyncJob = 0 } func (s *DriverSyncTestSuite) TestSyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("sync").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Once() - s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() - s.Nil(s.app.Job(&TestSyncJob{}, []any{"TestSyncQueue", 1}).DispatchSync()) s.Equal(1, testSyncJob) - - s.mockConfig.AssertExpectations(s.T()) } func (s *DriverSyncTestSuite) TestChainSyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("sync").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Twice() - s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() + s.mockConfig.On("GetString", "queue.default").Return("sync").Times(2) + s.mockConfig.On("GetString", "app.name").Return("goravel").Once() s.mockConfig.On("GetString", "queue.connections.sync.driver").Return("sync").Once() s.Nil(s.app.Chain([]queue.Jobs{ @@ -71,8 +67,6 @@ func (s *DriverSyncTestSuite) TestChainSyncQueue() { time.Sleep(2 * time.Second) s.Equal(1, testChainSyncJob) - - s.mockConfig.AssertExpectations(s.T()) } type TestSyncJob struct { diff --git a/queue/job.go b/queue/job.go index 04fb786f1..fe7dee650 100644 --- a/queue/job.go +++ b/queue/job.go @@ -11,13 +11,13 @@ import ( ) type FailedJob struct { - ID uint `gorm:"primaryKey"` // The unique ID of the job. - UUID uuid.UUID `gorm:"not null;unique"` // The UUID of the job. - Connection string `gorm:"not null"` // The name of the connection the job belongs to. - Queue string `gorm:"not null"` // The name of the queue the job belongs to. - Payload []any `gorm:"not null;serializer:json"` // The arguments passed to the job. - Exception string `gorm:"not null"` // The exception that caused the job to fail. - FailedAt carbon.DateTime `gorm:"not null"` // The timestamp when the job failed. + ID uint `gorm:"primaryKey"` // The unique ID of the job. + UUID uuid.UUID // The UUID of the job. + Connection string // The name of the connection the job belongs to. + Queue string // The name of the queue the job belongs to. + Payload []any `gorm:"serializer:json"` // The arguments passed to the job. + Exception string // The exception that caused the job to fail. + FailedAt carbon.DateTime // The timestamp when the job failed. } type JobImpl struct { diff --git a/queue/worker.go b/queue/worker.go index 6cfc1c5c8..f6931b574 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -1,6 +1,7 @@ package queue import ( + "sync" "time" "github.com/google/uuid" @@ -19,6 +20,7 @@ type Worker struct { isShutdown bool job queue.JobRepository queue string + wg sync.WaitGroup } func NewWorker(config queue.Config, concurrent int, connection string, queue string, job queue.JobRepository) *Worker { @@ -44,7 +46,9 @@ func (r *Worker) Run() error { } for i := 0; i < r.concurrent; i++ { + r.wg.Add(1) go func() { + defer r.wg.Done() for { if r.isShutdown { return @@ -52,8 +56,6 @@ func (r *Worker) Run() error { job, args, err := driver.Pop(r.queue) if err != nil { - // This error not need to be reported. - // It is usually caused by the queue being empty. time.Sleep(1 * time.Second) continue } @@ -85,5 +87,7 @@ func (r *Worker) Run() error { func (r *Worker) Shutdown() error { r.isShutdown = true + r.wg.Wait() + close(r.failedJobChan) return nil } diff --git a/queue/worker_test.go b/queue/worker_test.go new file mode 100644 index 000000000..e6fe74cd5 --- /dev/null +++ b/queue/worker_test.go @@ -0,0 +1,102 @@ +package queue + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + contractsqueue "github.com/goravel/framework/contracts/queue" + "github.com/goravel/framework/errors" + mocksconfig "github.com/goravel/framework/mocks/config" + mocksorm "github.com/goravel/framework/mocks/database/orm" +) + +type WorkerTestSuite struct { + suite.Suite + failedJobChan chan FailedJob +} + +func TestWorkerTestSuite(t *testing.T) { + suite.Run(t, new(WorkerTestSuite)) +} + +func (s *WorkerTestSuite) SetupTest() {} + +func (s *WorkerTestSuite) TestRun_Success() { + mockConfig := mocksconfig.NewConfig(s.T()) + mockConfig.On("GetString", "queue.default").Return("async").Times(4) + mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) + mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + app := NewApplication(mockConfig) + testJob := new(MockJob) + app.Register([]contractsqueue.Job{testJob}) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + s.NoError(app.Worker().Run()) + <-ctx.Done() + s.NoError(app.Worker().Shutdown()) + }(ctx) + + time.Sleep(1 * time.Second) + s.NoError(app.Job(testJob, []any{}).Dispatch()) + time.Sleep(2 * time.Second) + s.True(testJob.called) +} + +func (s *WorkerTestSuite) TestRun_FailedJob() { + mockConfig := mocksconfig.NewConfig(s.T()) + mockConfig.On("GetString", "queue.default").Return("async").Times(4) + mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) + mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() + mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + + mockConfig.On("GetString", "queue.failed.database").Return("database").Times(1) + mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Times(1) + + mockOrm := mocksorm.NewOrm(s.T()) + mockQuery := mocksorm.NewQuery(s.T()) + mockOrm.EXPECT().Connection("database").Return(mockOrm) + mockOrm.EXPECT().Query().Return(mockQuery) + mockQuery.EXPECT().Table("failed_jobs").Return(mockQuery) + mockQuery.EXPECT().Create(mock.Anything).Return(nil) + OrmFacade = mockOrm + + app := NewApplication(mockConfig) + testJob := new(MockFailedJob) + app.Register([]contractsqueue.Job{testJob}) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + go func(ctx context.Context) { + s.NoError(app.Worker().Run()) + <-ctx.Done() + s.NoError(app.Worker().Shutdown()) + }(ctx) + + time.Sleep(1 * time.Second) + s.NoError(app.Job(testJob, []any{}).Dispatch()) + time.Sleep(2 * time.Second) + s.True(testJob.called) +} + +type MockFailedJob struct { + signature string + called bool +} + +func (m *MockFailedJob) Signature() string { + return m.signature +} + +func (m *MockFailedJob) Handle(args ...any) error { + m.called = true + return errors.New("failed job") +} From a5fdf4c99369de8d5e6a90bbe77ecb8aed42ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 6 Jan 2025 23:41:38 +0800 Subject: [PATCH 20/32] fix: lint --- queue/worker.go | 1 - queue/worker_test.go | 1 - 2 files changed, 2 deletions(-) diff --git a/queue/worker.go b/queue/worker.go index f6931b574..bbc8a3c0c 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -15,7 +15,6 @@ type Worker struct { concurrent int config queue.Config connection string - driver queue.Driver failedJobChan chan FailedJob isShutdown bool job queue.JobRepository diff --git a/queue/worker_test.go b/queue/worker_test.go index e6fe74cd5..30f77e3c6 100644 --- a/queue/worker_test.go +++ b/queue/worker_test.go @@ -16,7 +16,6 @@ import ( type WorkerTestSuite struct { suite.Suite - failedJobChan chan FailedJob } func TestWorkerTestSuite(t *testing.T) { From 83e2a5df52c7fba148257b2fd1d00f583124cae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Mon, 6 Jan 2025 23:50:33 +0800 Subject: [PATCH 21/32] feat: optimize worker --- queue/worker.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/queue/worker.go b/queue/worker.go index bbc8a3c0c..ac79860fc 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -2,6 +2,7 @@ package queue import ( "sync" + "sync/atomic" "time" "github.com/google/uuid" @@ -16,10 +17,11 @@ type Worker struct { config queue.Config connection string failedJobChan chan FailedJob - isShutdown bool + isShutdown atomic.Bool job queue.JobRepository queue string wg sync.WaitGroup + failedWg sync.WaitGroup } func NewWorker(config queue.Config, concurrent int, connection string, queue string, job queue.JobRepository) *Worker { @@ -29,12 +31,12 @@ func NewWorker(config queue.Config, concurrent int, connection string, queue str connection: connection, job: job, queue: queue, - failedJobChan: make(chan FailedJob), + failedJobChan: make(chan FailedJob, concurrent), } } func (r *Worker) Run() error { - r.isShutdown = false + r.isShutdown.Store(false) driver, err := NewDriver(r.connection, r.config) if err != nil { @@ -49,7 +51,7 @@ func (r *Worker) Run() error { go func() { defer r.wg.Done() for { - if r.isShutdown { + if r.isShutdown.Load() { return } @@ -60,20 +62,26 @@ func (r *Worker) Run() error { } if err = r.job.Call(job.Signature(), args); err != nil { - r.failedJobChan <- FailedJob{ + select { + case r.failedJobChan <- FailedJob{ UUID: uuid.New(), Connection: r.connection, Queue: r.queue, Payload: args, Exception: err.Error(), FailedAt: carbon.DateTime{Carbon: carbon.Now()}, + }: + default: + LogFacade.Error(errors.New("failed to send failed job to channel")) } } } }() } + r.failedWg.Add(1) go func() { + defer r.failedWg.Done() for job := range r.failedJobChan { if err = r.config.FailedJobsQuery().Create(&job); err != nil { LogFacade.Error(errors.QueueFailedToSaveFailedJob.Args(err)) @@ -85,8 +93,9 @@ func (r *Worker) Run() error { } func (r *Worker) Shutdown() error { - r.isShutdown = true + r.isShutdown.Store(true) r.wg.Wait() close(r.failedJobChan) + r.failedWg.Wait() return nil } From 9b86fa3794b8e5ce892073ece5fd944095fc392d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 8 Jan 2025 17:40:41 +0800 Subject: [PATCH 22/32] feat: optimize --- contracts/queue/queue.go | 4 ++-- queue/application.go | 11 ++++++----- queue/config.go | 6 +++--- queue/driver_async_test.go | 18 ++++++------------ queue/driver_sync_test.go | 12 ++++++------ queue/worker.go | 10 +--------- queue/worker_test.go | 18 ++++++++++-------- 7 files changed, 34 insertions(+), 45 deletions(-) diff --git a/contracts/queue/queue.go b/contracts/queue/queue.go index 26b92c016..84829e2a5 100644 --- a/contracts/queue/queue.go +++ b/contracts/queue/queue.go @@ -1,12 +1,12 @@ package queue type Queue interface { - // All get all jobs - All() []Job // Chain creates a chain of jobs to be processed one by one, passing Chain(jobs []Jobs) Task // GetJob get job by signature GetJob(signature string) (Job, error) + // GetJobs get all jobs + GetJobs() []Job // Job add a job to queue Job(job Job, args []any) Task // Register register jobs diff --git a/queue/application.go b/queue/application.go index 6da59ec50..f38ac94d3 100644 --- a/queue/application.go +++ b/queue/application.go @@ -1,7 +1,7 @@ package queue import ( - configcontract "github.com/goravel/framework/contracts/config" + contractsconfig "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/queue" ) @@ -10,16 +10,13 @@ type Application struct { job queue.JobRepository } -func NewApplication(config configcontract.Config) *Application { +func NewApplication(config contractsconfig.Config) *Application { return &Application{ config: NewConfig(config), job: NewJobImpl(), } } -func (app *Application) All() []queue.Job { - return app.job.All() -} func (app *Application) Chain(jobs []queue.Jobs) queue.Task { return NewChainTask(app.config, jobs) } @@ -28,6 +25,10 @@ func (app *Application) GetJob(signature string) (queue.Job, error) { return app.job.Get(signature) } +func (app *Application) GetJobs() []queue.Job { + return app.job.All() +} + func (app *Application) Job(job queue.Job, args []any) queue.Task { return NewTask(app.config, job, args) } diff --git a/queue/config.go b/queue/config.go index 9b9701b97..815c67888 100644 --- a/queue/config.go +++ b/queue/config.go @@ -3,16 +3,16 @@ package queue import ( "fmt" - configcontract "github.com/goravel/framework/contracts/config" + contractsconfig "github.com/goravel/framework/contracts/config" "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/contracts/queue" ) type Config struct { - config configcontract.Config + config contractsconfig.Config } -func NewConfig(config configcontract.Config) queue.Config { +func NewConfig(config contractsconfig.Config) queue.Config { return &Config{ config: config, } diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index 442469ab7..bbffb808f 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -29,21 +29,15 @@ type DriverAsyncTestSuite struct { } func TestDriverAsyncTestSuite(t *testing.T) { - mockConfig := mocksconfig.NewConfig(t) - mockQueue := mocksqueue.NewQueue(t) - app := NewApplication(mockConfig) - - app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}}) - suite.Run(t, &DriverAsyncTestSuite{ - app: app, - mockConfig: mockConfig, - mockQueue: mockQueue, - }) + suite.Run(t, new(DriverAsyncTestSuite)) } func (s *DriverAsyncTestSuite) SetupTest() { testAsyncJob = 0 + s.mockQueue = mocksqueue.NewQueue(s.T()) s.mockConfig = mocksconfig.NewConfig(s.T()) + s.app = NewApplication(s.mockConfig) + s.app.Register([]queue.Job{&TestAsyncJob{}, &TestDelayAsyncJob{}, &TestCustomAsyncJob{}, &TestErrorAsyncJob{}, &TestChainAsyncJob{}}) } func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { @@ -139,8 +133,8 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { mockOrm := mocksorm.NewOrm(s.T()) mockQuery := mocksorm.NewQuery(s.T()) mockOrm.EXPECT().Connection("database").Return(mockOrm) - mockOrm.On("Query").Return(mockQuery) - mockQuery.On("Table", "failed_jobs").Return(mockQuery) + mockOrm.EXPECT().Query().Return(mockQuery).Once() + mockQuery.EXPECT().Table("failed_jobs").Return(mockQuery).Once() OrmFacade = mockOrm ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index 018f0541b..b5b721a46 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -37,9 +37,9 @@ func TestDriverSyncTestSuite(t *testing.T) { } func (s *DriverSyncTestSuite) SetupTest() { - s.mockConfig.On("GetString", "queue.default").Return("sync").Once() - s.mockConfig.On("GetString", "app.name").Return("goravel").Once() - s.mockConfig.On("GetString", "queue.connections.sync.queue", "default").Return("default").Once() + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Once() + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() + s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() testSyncJob = 0 testChainSyncJob = 0 } @@ -50,9 +50,9 @@ func (s *DriverSyncTestSuite) TestSyncQueue() { } func (s *DriverSyncTestSuite) TestChainSyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("sync").Times(2) - s.mockConfig.On("GetString", "app.name").Return("goravel").Once() - s.mockConfig.On("GetString", "queue.connections.sync.driver").Return("sync").Once() + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Once() + s.mockConfig.EXPECT().GetString("queue.connections.sync.driver").Return("sync").Once() s.Nil(s.app.Chain([]queue.Jobs{ { diff --git a/queue/worker.go b/queue/worker.go index ac79860fc..18144502b 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -21,7 +21,6 @@ type Worker struct { job queue.JobRepository queue string wg sync.WaitGroup - failedWg sync.WaitGroup } func NewWorker(config queue.Config, concurrent int, connection string, queue string, job queue.JobRepository) *Worker { @@ -62,26 +61,20 @@ func (r *Worker) Run() error { } if err = r.job.Call(job.Signature(), args); err != nil { - select { - case r.failedJobChan <- FailedJob{ + r.failedJobChan <- FailedJob{ UUID: uuid.New(), Connection: r.connection, Queue: r.queue, Payload: args, Exception: err.Error(), FailedAt: carbon.DateTime{Carbon: carbon.Now()}, - }: - default: - LogFacade.Error(errors.New("failed to send failed job to channel")) } } } }() } - r.failedWg.Add(1) go func() { - defer r.failedWg.Done() for job := range r.failedJobChan { if err = r.config.FailedJobsQuery().Create(&job); err != nil { LogFacade.Error(errors.QueueFailedToSaveFailedJob.Args(err)) @@ -96,6 +89,5 @@ func (r *Worker) Shutdown() error { r.isShutdown.Store(true) r.wg.Wait() close(r.failedJobChan) - r.failedWg.Wait() return nil } diff --git a/queue/worker_test.go b/queue/worker_test.go index 30f77e3c6..899786fee 100644 --- a/queue/worker_test.go +++ b/queue/worker_test.go @@ -26,9 +26,9 @@ func (s *WorkerTestSuite) SetupTest() {} func (s *WorkerTestSuite) TestRun_Success() { mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async").Times(4) - mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) + mockConfig.On("GetString", "queue.default").Return("async") + mockConfig.On("GetString", "app.name").Return("goravel") + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default") mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() app := NewApplication(mockConfig) @@ -36,7 +36,6 @@ func (s *WorkerTestSuite) TestRun_Success() { app.Register([]contractsqueue.Job{testJob}) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() go func(ctx context.Context) { s.NoError(app.Worker().Run()) <-ctx.Done() @@ -47,13 +46,15 @@ func (s *WorkerTestSuite) TestRun_Success() { s.NoError(app.Job(testJob, []any{}).Dispatch()) time.Sleep(2 * time.Second) s.True(testJob.called) + cancel() + time.Sleep(2 * time.Second) } func (s *WorkerTestSuite) TestRun_FailedJob() { mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async").Times(4) - mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) + mockConfig.On("GetString", "queue.default").Return("async") + mockConfig.On("GetString", "app.name").Return("goravel") + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default") mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() @@ -73,7 +74,6 @@ func (s *WorkerTestSuite) TestRun_FailedJob() { app.Register([]contractsqueue.Job{testJob}) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() go func(ctx context.Context) { s.NoError(app.Worker().Run()) <-ctx.Done() @@ -84,6 +84,8 @@ func (s *WorkerTestSuite) TestRun_FailedJob() { s.NoError(app.Job(testJob, []any{}).Dispatch()) time.Sleep(2 * time.Second) s.True(testJob.called) + cancel() + time.Sleep(2 * time.Second) } type MockFailedJob struct { From 8f2b6eef597db9463007741a399fed8149ddf16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 8 Jan 2025 17:47:40 +0800 Subject: [PATCH 23/32] feat: optimize tests --- queue/driver_async_test.go | 30 ++++++------------------------ queue/driver_sync_test.go | 2 +- queue/worker_test.go | 12 ++++++------ 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index bbffb808f..ea4805f14 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -9,7 +9,6 @@ import ( "github.com/goravel/framework/contracts/queue" mocksconfig "github.com/goravel/framework/mocks/config" - mocksorm "github.com/goravel/framework/mocks/database/orm" mocksqueue "github.com/goravel/framework/mocks/queue" ) @@ -41,13 +40,11 @@ func (s *DriverAsyncTestSuite) SetupTest() { } func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() - s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() - s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -65,13 +62,11 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { } func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() - s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() - s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -93,13 +88,11 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { } func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("custom").Times(4) + s.mockConfig.On("GetString", "queue.default").Return("custom").Times(3) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("async").Times(2) s.mockConfig.On("GetInt", "queue.connections.custom.size", 100).Return(10).Twice() - s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() - s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -121,21 +114,12 @@ func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { } func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Once() s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Once() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Once() - s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() - s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() - - mockOrm := mocksorm.NewOrm(s.T()) - mockQuery := mocksorm.NewQuery(s.T()) - mockOrm.EXPECT().Connection("database").Return(mockOrm) - mockOrm.EXPECT().Query().Return(mockQuery).Once() - mockQuery.EXPECT().Table("failed_jobs").Return(mockQuery).Once() - OrmFacade = mockOrm + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Twice() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -155,13 +139,11 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { } func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(4) + s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() - s.mockConfig.On("GetString", "queue.failed.database").Return("database").Once() - s.mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Once() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index b5b721a46..74e6cac49 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -50,8 +50,8 @@ func (s *DriverSyncTestSuite) TestSyncQueue() { } func (s *DriverSyncTestSuite) TestChainSyncQueue() { - s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Once() + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() s.mockConfig.EXPECT().GetString("queue.connections.sync.driver").Return("sync").Once() s.Nil(s.app.Chain([]queue.Jobs{ diff --git a/queue/worker_test.go b/queue/worker_test.go index 899786fee..050e73ee4 100644 --- a/queue/worker_test.go +++ b/queue/worker_test.go @@ -26,9 +26,9 @@ func (s *WorkerTestSuite) SetupTest() {} func (s *WorkerTestSuite) TestRun_Success() { mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async") - mockConfig.On("GetString", "app.name").Return("goravel") - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default") + mockConfig.On("GetString", "queue.default").Return("async").Times(4) + mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() app := NewApplication(mockConfig) @@ -52,9 +52,9 @@ func (s *WorkerTestSuite) TestRun_Success() { func (s *WorkerTestSuite) TestRun_FailedJob() { mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async") - mockConfig.On("GetString", "app.name").Return("goravel") - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default") + mockConfig.On("GetString", "queue.default").Return("async").Times(4) + mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() From 8934d917107d4b4f9d390c79e029c011a500d407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 8 Jan 2025 17:52:52 +0800 Subject: [PATCH 24/32] feat: optimize tests --- queue/driver_async_test.go | 52 +++++++++++++++++++------------------- queue/worker_test.go | 31 ++++++++++------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/queue/driver_async_test.go b/queue/driver_async_test.go index ea4805f14..ec9c8f510 100644 --- a/queue/driver_async_test.go +++ b/queue/driver_async_test.go @@ -40,11 +40,11 @@ func (s *DriverAsyncTestSuite) SetupTest() { } func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(2) - s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(2) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Twice() + s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Twice() + s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Twice() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -62,11 +62,11 @@ func (s *DriverAsyncTestSuite) TestDefaultAsyncQueue() { } func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Twice() + s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Twice() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -88,11 +88,11 @@ func (s *DriverAsyncTestSuite) TestDelayAsyncQueue() { } func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("custom").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("async").Times(2) - s.mockConfig.On("GetInt", "queue.connections.custom.size", 100).Return(10).Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("custom").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.custom.queue", "default").Return("default").Once() + s.mockConfig.EXPECT().GetString("queue.connections.custom.driver").Return("async").Times(2) + s.mockConfig.EXPECT().GetInt("queue.connections.custom.size", 100).Return(10).Twice() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -114,12 +114,12 @@ func (s *DriverAsyncTestSuite) TestCustomAsyncQueue() { } func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Once() - s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Once() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("").Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Once() + s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Once() + s.mockConfig.EXPECT().GetString("queue.connections.redis.driver").Return("").Twice() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -139,11 +139,11 @@ func (s *DriverAsyncTestSuite) TestErrorAsyncQueue() { } func (s *DriverAsyncTestSuite) TestChainAsyncQueue() { - s.mockConfig.On("GetString", "queue.default").Return("async").Times(3) - s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - s.mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Once() - s.mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - s.mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Once() + s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Twice() + s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Twice() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() diff --git a/queue/worker_test.go b/queue/worker_test.go index 050e73ee4..50322cb18 100644 --- a/queue/worker_test.go +++ b/queue/worker_test.go @@ -16,22 +16,24 @@ import ( type WorkerTestSuite struct { suite.Suite + mockConfig *mocksconfig.Config } func TestWorkerTestSuite(t *testing.T) { suite.Run(t, new(WorkerTestSuite)) } -func (s *WorkerTestSuite) SetupTest() {} +func (s *WorkerTestSuite) SetupTest() { + s.mockConfig = mocksconfig.NewConfig(s.T()) + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(4) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Times(3) + s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Twice() + s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Twice() +} func (s *WorkerTestSuite) TestRun_Success() { - mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async").Times(4) - mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) - mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() - app := NewApplication(mockConfig) + app := NewApplication(s.mockConfig) testJob := new(MockJob) app.Register([]contractsqueue.Job{testJob}) @@ -51,15 +53,8 @@ func (s *WorkerTestSuite) TestRun_Success() { } func (s *WorkerTestSuite) TestRun_FailedJob() { - mockConfig := mocksconfig.NewConfig(s.T()) - mockConfig.On("GetString", "queue.default").Return("async").Times(4) - mockConfig.On("GetString", "app.name").Return("goravel").Times(3) - mockConfig.On("GetString", "queue.connections.async.queue", "default").Return("default").Times(3) - mockConfig.On("GetString", "queue.connections.async.driver").Return("async").Twice() - mockConfig.On("GetInt", "queue.connections.async.size", 100).Return(10).Twice() - - mockConfig.On("GetString", "queue.failed.database").Return("database").Times(1) - mockConfig.On("GetString", "queue.failed.table").Return("failed_jobs").Times(1) + s.mockConfig.EXPECT().GetString("queue.failed.database").Return("database").Times(1) + s.mockConfig.EXPECT().GetString("queue.failed.table").Return("failed_jobs").Times(1) mockOrm := mocksorm.NewOrm(s.T()) mockQuery := mocksorm.NewQuery(s.T()) @@ -69,7 +64,7 @@ func (s *WorkerTestSuite) TestRun_FailedJob() { mockQuery.EXPECT().Create(mock.Anything).Return(nil) OrmFacade = mockOrm - app := NewApplication(mockConfig) + app := NewApplication(s.mockConfig) testJob := new(MockFailedJob) app.Register([]contractsqueue.Job{testJob}) From 95d0c4c02f7c835152a8a7cf4190573987f4561a Mon Sep 17 00:00:00 2001 From: devhaozi Date: Wed, 8 Jan 2025 09:53:34 +0000 Subject: [PATCH 25/32] chore: update mocks --- mocks/queue/Queue.go | 94 ++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/mocks/queue/Queue.go b/mocks/queue/Queue.go index 7e1743f4d..3bd0f0ed7 100644 --- a/mocks/queue/Queue.go +++ b/mocks/queue/Queue.go @@ -20,53 +20,6 @@ func (_m *Queue) EXPECT() *Queue_Expecter { return &Queue_Expecter{mock: &_m.Mock} } -// All provides a mock function with no fields -func (_m *Queue) All() []queue.Job { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for All") - } - - var r0 []queue.Job - if rf, ok := ret.Get(0).(func() []queue.Job); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]queue.Job) - } - } - - return r0 -} - -// Queue_All_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'All' -type Queue_All_Call struct { - *mock.Call -} - -// All is a helper method to define mock.On call -func (_e *Queue_Expecter) All() *Queue_All_Call { - return &Queue_All_Call{Call: _e.mock.On("All")} -} - -func (_c *Queue_All_Call) Run(run func()) *Queue_All_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Queue_All_Call) Return(_a0 []queue.Job) *Queue_All_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Queue_All_Call) RunAndReturn(run func() []queue.Job) *Queue_All_Call { - _c.Call.Return(run) - return _c -} - // Chain provides a mock function with given fields: jobs func (_m *Queue) Chain(jobs []queue.Jobs) queue.Task { ret := _m.Called(jobs) @@ -173,6 +126,53 @@ func (_c *Queue_GetJob_Call) RunAndReturn(run func(string) (queue.Job, error)) * return _c } +// GetJobs provides a mock function with no fields +func (_m *Queue) GetJobs() []queue.Job { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetJobs") + } + + var r0 []queue.Job + if rf, ok := ret.Get(0).(func() []queue.Job); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]queue.Job) + } + } + + return r0 +} + +// Queue_GetJobs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJobs' +type Queue_GetJobs_Call struct { + *mock.Call +} + +// GetJobs is a helper method to define mock.On call +func (_e *Queue_Expecter) GetJobs() *Queue_GetJobs_Call { + return &Queue_GetJobs_Call{Call: _e.mock.On("GetJobs")} +} + +func (_c *Queue_GetJobs_Call) Run(run func()) *Queue_GetJobs_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Queue_GetJobs_Call) Return(_a0 []queue.Job) *Queue_GetJobs_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Queue_GetJobs_Call) RunAndReturn(run func() []queue.Job) *Queue_GetJobs_Call { + _c.Call.Return(run) + return _c +} + // Job provides a mock function with given fields: job, args func (_m *Queue) Job(job queue.Job, args []interface{}) queue.Task { ret := _m.Called(job, args) From 08b552a5137e52238b0b6d299e88ddf476ba2d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 8 Jan 2025 18:04:15 +0800 Subject: [PATCH 26/32] feat: optimize tests --- queue/driver_sync_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index 74e6cac49..81df6f348 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -37,21 +37,22 @@ func TestDriverSyncTestSuite(t *testing.T) { } func (s *DriverSyncTestSuite) SetupTest() { - s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Once() - s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() - s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() testSyncJob = 0 testChainSyncJob = 0 } func (s *DriverSyncTestSuite) TestSyncQueue() { + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() + s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() s.Nil(s.app.Job(&TestSyncJob{}, []any{"TestSyncQueue", 1}).DispatchSync()) s.Equal(1, testSyncJob) } func (s *DriverSyncTestSuite) TestChainSyncQueue() { - s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Once() - s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Twice() + s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() s.mockConfig.EXPECT().GetString("queue.connections.sync.driver").Return("sync").Once() s.Nil(s.app.Chain([]queue.Jobs{ From 1761b02b88dae95db8916382c35f4d859b9d3492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 8 Jan 2025 18:09:00 +0800 Subject: [PATCH 27/32] feat: optimize tests --- queue/worker.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue/worker.go b/queue/worker.go index 18144502b..12f89236d 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -74,7 +74,9 @@ func (r *Worker) Run() error { }() } + r.wg.Add(1) go func() { + defer r.wg.Done() for job := range r.failedJobChan { if err = r.config.FailedJobsQuery().Create(&job); err != nil { LogFacade.Error(errors.QueueFailedToSaveFailedJob.Args(err)) From e019c6dcccaa4c8b249f8f6674fed3a257ec61ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 12 Jan 2025 02:05:53 +0800 Subject: [PATCH 28/32] feat: optimize --- queue/driver_sync_test.go | 21 +++++++-------------- queue/worker.go | 3 ++- queue/worker_test.go | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 28 deletions(-) diff --git a/queue/driver_sync_test.go b/queue/driver_sync_test.go index 81df6f348..d6f15d429 100644 --- a/queue/driver_sync_test.go +++ b/queue/driver_sync_test.go @@ -24,35 +24,28 @@ type DriverSyncTestSuite struct { } func TestDriverSyncTestSuite(t *testing.T) { - mockConfig := mocksconfig.NewConfig(t) - mockQueue := mocksqueue.NewQueue(t) - app := NewApplication(mockConfig) - - app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}}) - suite.Run(t, &DriverSyncTestSuite{ - app: app, - mockConfig: mockConfig, - mockQueue: mockQueue, - }) + suite.Run(t, new(DriverSyncTestSuite)) } func (s *DriverSyncTestSuite) SetupTest() { testSyncJob = 0 testChainSyncJob = 0 + s.mockConfig = mocksconfig.NewConfig(s.T()) + s.mockQueue = mocksqueue.NewQueue(s.T()) + s.app = NewApplication(s.mockConfig) + s.app.Register([]queue.Job{&TestSyncJob{}, &TestChainSyncJob{}}) + s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() + s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() } func (s *DriverSyncTestSuite) TestSyncQueue() { - s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Once() - s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() s.Nil(s.app.Job(&TestSyncJob{}, []any{"TestSyncQueue", 1}).DispatchSync()) s.Equal(1, testSyncJob) } func (s *DriverSyncTestSuite) TestChainSyncQueue() { - s.mockConfig.EXPECT().GetString("queue.default").Return("sync").Twice() s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Twice() - s.mockConfig.EXPECT().GetString("queue.connections.sync.queue", "default").Return("default").Once() s.mockConfig.EXPECT().GetString("queue.connections.sync.driver").Return("sync").Once() s.Nil(s.app.Chain([]queue.Jobs{ diff --git a/queue/worker.go b/queue/worker.go index 12f89236d..59a83b792 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -84,12 +84,13 @@ func (r *Worker) Run() error { } }() + r.wg.Wait() + return nil } func (r *Worker) Shutdown() error { r.isShutdown.Store(true) - r.wg.Wait() close(r.failedJobChan) return nil } diff --git a/queue/worker_test.go b/queue/worker_test.go index 50322cb18..495023670 100644 --- a/queue/worker_test.go +++ b/queue/worker_test.go @@ -16,6 +16,7 @@ import ( type WorkerTestSuite struct { suite.Suite + app *Application mockConfig *mocksconfig.Config } @@ -25,27 +26,27 @@ func TestWorkerTestSuite(t *testing.T) { func (s *WorkerTestSuite) SetupTest() { s.mockConfig = mocksconfig.NewConfig(s.T()) - s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(4) - s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(3) - s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Times(3) + s.mockConfig.EXPECT().GetString("queue.default").Return("async").Times(3) + s.mockConfig.EXPECT().GetString("app.name").Return("goravel").Times(2) + s.mockConfig.EXPECT().GetString("queue.connections.async.queue", "default").Return("default").Times(2) s.mockConfig.EXPECT().GetString("queue.connections.async.driver").Return("async").Twice() s.mockConfig.EXPECT().GetInt("queue.connections.async.size", 100).Return(10).Twice() + s.app = NewApplication(s.mockConfig) } func (s *WorkerTestSuite) TestRun_Success() { - app := NewApplication(s.mockConfig) testJob := new(MockJob) - app.Register([]contractsqueue.Job{testJob}) + s.app.Register([]contractsqueue.Job{testJob}) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) go func(ctx context.Context) { - s.NoError(app.Worker().Run()) + s.NoError(s.app.Worker().Run()) <-ctx.Done() - s.NoError(app.Worker().Shutdown()) + s.NoError(s.app.Worker().Shutdown()) }(ctx) time.Sleep(1 * time.Second) - s.NoError(app.Job(testJob, []any{}).Dispatch()) + s.NoError(s.app.Job(testJob, []any{}).Dispatch()) time.Sleep(2 * time.Second) s.True(testJob.called) cancel() @@ -64,19 +65,18 @@ func (s *WorkerTestSuite) TestRun_FailedJob() { mockQuery.EXPECT().Create(mock.Anything).Return(nil) OrmFacade = mockOrm - app := NewApplication(s.mockConfig) testJob := new(MockFailedJob) - app.Register([]contractsqueue.Job{testJob}) + s.app.Register([]contractsqueue.Job{testJob}) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) go func(ctx context.Context) { - s.NoError(app.Worker().Run()) + s.NoError(s.app.Worker().Run()) <-ctx.Done() - s.NoError(app.Worker().Shutdown()) + s.NoError(s.app.Worker().Shutdown()) }(ctx) time.Sleep(1 * time.Second) - s.NoError(app.Job(testJob, []any{}).Dispatch()) + s.NoError(s.app.Job(testJob, []any{}).Dispatch()) time.Sleep(2 * time.Second) s.True(testJob.called) cancel() From 433de7127fc70eb14a118cb805f51183b1009e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 12 Jan 2025 02:34:42 +0800 Subject: [PATCH 29/32] feat: finish machinery --- contracts/queue/job.go | 2 +- queue/driver_async.go | 13 ++----- queue/driver_machinery.go | 80 ++++++++++++++++++++++++++++++++++----- queue/driver_sync.go | 4 +- queue/worker.go | 13 +++++++ 5 files changed, 89 insertions(+), 23 deletions(-) diff --git a/contracts/queue/job.go b/contracts/queue/job.go index 00ab1d9b3..84321adf8 100644 --- a/contracts/queue/job.go +++ b/contracts/queue/job.go @@ -19,5 +19,5 @@ type JobRepository interface { type Jobs struct { Job Job Args []any - Delay time.Duration + Delay time.Time } diff --git a/queue/driver_async.go b/queue/driver_async.go index c9c5bde89..aad334964 100644 --- a/queue/driver_async.go +++ b/queue/driver_async.go @@ -37,15 +37,10 @@ func (r *Async) Push(job contractsqueue.Job, args []any, queue string) error { func (r *Async) Bulk(jobs []contractsqueue.Jobs, queue string) error { for _, job := range jobs { - if job.Delay > 0 { - go func(j contractsqueue.Jobs) { - time.Sleep(j.Delay) - r.getQueue(queue) <- j - }(job) - continue - } - - r.getQueue(queue) <- job + go func() { + time.Sleep(time.Until(job.Delay)) + r.getQueue(queue) <- job + }() } return nil diff --git a/queue/driver_machinery.go b/queue/driver_machinery.go index 36ea81a9a..7b46ae1a8 100644 --- a/queue/driver_machinery.go +++ b/queue/driver_machinery.go @@ -3,6 +3,7 @@ package queue import ( + "reflect" "time" "github.com/RichardKnop/machinery/v2" @@ -11,6 +12,7 @@ import ( "github.com/RichardKnop/machinery/v2/config" "github.com/RichardKnop/machinery/v2/locks/eager" "github.com/RichardKnop/machinery/v2/log" + "github.com/RichardKnop/machinery/v2/tasks" contractslog "github.com/goravel/framework/contracts/log" "github.com/goravel/framework/contracts/queue" @@ -35,28 +37,74 @@ func (m *Machinery) Connection() string { } func (m *Machinery) Driver() string { - //TODO implement me - panic("implement me") + return queue.DriverMachinery } func (m *Machinery) Push(job queue.Job, args []any, queue string) error { - //TODO implement me - panic("implement me") + server := m.server(queue) + _, err := server.SendTask(&tasks.Signature{ + Name: job.Signature(), + Args: m.argsToMachineryArgs(args), + }) + return err } func (m *Machinery) Bulk(jobs []queue.Jobs, queue string) error { - //TODO implement me - panic("implement me") + var signatures []*tasks.Signature + for _, job := range jobs { + signatures = append(signatures, &tasks.Signature{ + Name: job.Job.Signature(), + Args: m.argsToMachineryArgs(job.Args), + ETA: &job.Delay, + }) + } + + chain, err := tasks.NewChain(signatures...) + if err != nil { + return err + } + + server := m.server(queue) + _, err = server.SendChain(chain) + + return err } func (m *Machinery) Later(delay time.Time, job queue.Job, args []any, queue string) error { - //TODO implement me - panic("implement me") + server := m.server(queue) + _, err := server.SendTask(&tasks.Signature{ + Name: job.Signature(), + Args: m.argsToMachineryArgs(args), + ETA: &delay, + }) + return err } func (m *Machinery) Pop(queue string) (queue.Job, []any, error) { - //TODO implement me - panic("implement me") + return nil, nil, nil +} + +func (m *Machinery) Run(jobs []queue.Job, queue string, concurrent int) error { + server := m.server(queue) + if server == nil { + return nil + } + + jobTasks, err := jobs2Tasks(jobs) + if err != nil { + return err + } + + if err = server.RegisterTasks(jobTasks); err != nil { + return err + } + + if queue == "" { + queue = server.GetConfig().DefaultQueue + } + + worker := server.NewWorker(queue, concurrent) + return worker.Launch() } func (m *Machinery) server(queue string) *machinery.Server { @@ -83,3 +131,15 @@ func (m *Machinery) server(queue string) *machinery.Server { return machinery.NewServer(cnf, broker, backend, lock) } + +func (m *Machinery) argsToMachineryArgs(args []any) []tasks.Arg { + var realArgs []tasks.Arg + for _, arg := range args { + reflected := reflect.ValueOf(arg) + realArgs = append(realArgs, tasks.Arg{ + Type: reflected.Type().String(), + Value: reflected.Interface(), + }) + } + return realArgs +} diff --git a/queue/driver_sync.go b/queue/driver_sync.go index cb2e1419f..041fe8723 100644 --- a/queue/driver_sync.go +++ b/queue/driver_sync.go @@ -30,9 +30,7 @@ func (r *Sync) Push(job queue.Job, args []any, _ string) error { func (r *Sync) Bulk(jobs []queue.Jobs, _ string) error { for _, job := range jobs { - if job.Delay > 0 { - time.Sleep(job.Delay) - } + time.Sleep(time.Until(job.Delay)) if err := job.Job.Handle(job.Args...); err != nil { return err } diff --git a/queue/worker.go b/queue/worker.go index 59a83b792..bb8eb1410 100644 --- a/queue/worker.go +++ b/queue/worker.go @@ -45,6 +45,12 @@ func (r *Worker) Run() error { return errors.QueueDriverSyncNotNeedRun.Args(r.queue) } + // special cases for Machinery + // TODO: will remove in v1.17 + if driver.Driver() == queue.DriverMachinery { + return r.runMachinery(driver) + } + for i := 0; i < r.concurrent; i++ { r.wg.Add(1) go func() { @@ -94,3 +100,10 @@ func (r *Worker) Shutdown() error { close(r.failedJobChan) return nil } + +// runMachinery is a special case for Machinery +// TODO: will remove in v1.17 +func (r *Worker) runMachinery(driver queue.Driver) error { + m := driver.(*Machinery) + return m.Run(r.job.All(), r.queue, r.concurrent) +} From 8e8ab9473ce8c7e242909c935385e03c19469b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 12 Jan 2025 02:44:43 +0800 Subject: [PATCH 30/32] feat: finish machinery tests --- queue/driver_machinery_test.go | 414 ++++++++++++++++++++++++++++++++- 1 file changed, 413 insertions(+), 1 deletion(-) diff --git a/queue/driver_machinery_test.go b/queue/driver_machinery_test.go index 885b4b75b..a7a86a042 100644 --- a/queue/driver_machinery_test.go +++ b/queue/driver_machinery_test.go @@ -3,28 +3,62 @@ package queue import ( + "context" + "errors" "testing" + "time" + "github.com/spf13/cast" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" + "github.com/goravel/framework/contracts/queue" mocksconfig "github.com/goravel/framework/mocks/config" mockslog "github.com/goravel/framework/mocks/log" + "github.com/goravel/framework/support/carbon" + testingdocker "github.com/goravel/framework/support/docker" + "github.com/goravel/framework/support/env" +) + +var ( + testMachineryJob = 0 + testMachineryJobOfDisableDebug = 0 + testDelayMachineryJob = 0 + testCustomMachineryJob = 0 + testErrorMachineryJob = 0 + testChainMachineryJob = 0 + testChainMachineryJobError = 0 ) type MachineryTestSuite struct { suite.Suite + app *Application mockConfig *mocksconfig.Config mockLog *mockslog.Log machinery *Machinery + port int } func TestMachineryTestSuite(t *testing.T) { - suite.Run(t, new(MachineryTestSuite)) + if env.IsWindows() { + t.Skip("Skip test that using Docker") + } + + redisDocker := testingdocker.NewRedis() + assert.Nil(t, redisDocker.Build()) + + suite.Run(t, &MachineryTestSuite{ + port: redisDocker.Config().Port, + }) + + assert.Nil(t, redisDocker.Shutdown()) } func (s *MachineryTestSuite) SetupTest() { s.mockConfig = mocksconfig.NewConfig(s.T()) s.mockLog = mockslog.NewLog(s.T()) + s.app = NewApplication(s.mockConfig) } func (s *MachineryTestSuite) TestServer() { @@ -62,3 +96,381 @@ func (s *MachineryTestSuite) TestServer() { }) } } + +func (s *MachineryTestSuite) TestDefaultAsyncQueue_EnableDebug() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(true).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.mockLog.On("Infof", "Launching a worker with the following settings:").Once() + s.mockLog.On("Infof", "- Broker: %s", "://").Once() + s.mockLog.On("Infof", "- DefaultQueue: %s", "goravel_queues:debug").Once() + s.mockLog.On("Infof", "- ResultBackend: %s", "://").Once() + s.mockLog.On("Info", "[*] Waiting for messages. To exit press CTRL+C").Once() + s.mockLog.On("Debugf", "Received new message: %s", mock.Anything).Once() + s.mockLog.On("Debugf", "Processed task %s. Results = %s", mock.Anything, mock.Anything).Once() + s.app.Register([]queue.Job{&TestMachineryJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Queue: "debug", + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + time.Sleep(2 * time.Second) + s.Nil(s.app.Job(&TestMachineryJob{}, []any{ + "TestDefaultAsyncQueue_EnableDebug", + 1, + }).OnQueue("debug").Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(1, testMachineryJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockLog.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestDefaultAsyncQueue_DisableDebug() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.app.Register([]queue.Job{&TestMachineryJobOfDisableDebug{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker().Run()) + + for range ctx.Done() { + return + } + }(ctx) + time.Sleep(2 * time.Second) + s.Nil(s.app.Job(&TestMachineryJobOfDisableDebug{}, []any{ + "TestDefaultAsyncQueue_DisableDebug", + 1, + }).Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(1, testMachineryJobOfDisableDebug) + + s.mockConfig.AssertExpectations(s.T()) + s.mockLog.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestDelayAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.app.Register([]queue.Job{&TestDelayMachineryJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Queue: "delay", + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + time.Sleep(2 * time.Second) + s.Nil(s.app.Job(&TestDelayMachineryJob{}, []any{ + "TestDelayAsyncQueue", + 1, + }).OnQueue("delay").Delay(carbon.Now().AddSeconds(3).StdTime()).Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(0, testDelayMachineryJob) + time.Sleep(3 * time.Second) + s.Equal(1, testDelayMachineryJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestCustomAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.custom.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.custom.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.custom.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.app.Register([]queue.Job{&TestCustomMachineryJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Connection: "custom", + Queue: "custom1", + Concurrent: 2, + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + time.Sleep(2 * time.Second) + s.Nil(s.app.Job(&TestCustomMachineryJob{}, []any{ + "TestCustomAsyncQueue", + 1, + }).OnConnection("custom").OnQueue("custom1").Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(1, testCustomMachineryJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestErrorAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Twice() + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.app.Register([]queue.Job{&TestErrorMachineryJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Queue: "error", + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + time.Sleep(2 * time.Second) + s.Nil(s.app.Job(&TestErrorMachineryJob{}, []any{ + "TestErrorAsyncQueue", + 1, + }).OnConnection("redis").OnQueue("error1").Dispatch()) + time.Sleep(2 * time.Second) + s.Equal(0, testErrorMachineryJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestChainAsyncQueue() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.app.Register([]queue.Job{&TestChainMachineryJob{}, &TestChainSyncJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Queue: "chain", + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + + time.Sleep(2 * time.Second) + s.Nil(s.app.Chain([]queue.Jobs{ + { + Job: &TestChainMachineryJob{}, + Args: []any{ + "TestChainAsyncQueue", + 1, + }, + }, + { + Job: &TestChainSyncJob{}, + Args: []any{ + "TestChainSyncQueue", + 1, + }, + }, + }).OnQueue("chain").Dispatch()) + + time.Sleep(2 * time.Second) + s.Equal(1, testChainMachineryJob) + s.Equal(1, testChainSyncJob) + + s.mockConfig.AssertExpectations(s.T()) +} + +func (s *MachineryTestSuite) TestChainAsyncQueue_Error() { + s.mockConfig.On("GetString", "queue.default").Return("redis").Times(2) + s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) + s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) + s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() + s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() + s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() + s.mockConfig.On("GetInt", "database.redis.default.port").Return(s.port).Twice() + s.mockConfig.On("GetInt", "database.redis.default.database").Return(0).Twice() + s.mockLog.On("Errorf", "Failed processing task %s. Error = %v", mock.Anything, errors.New("error")).Once() + s.app.Register([]queue.Job{&TestChainMachineryJob{}, &TestChainSyncJob{}}) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + go func(ctx context.Context) { + s.Nil(s.app.Worker(queue.Args{ + Queue: "chain", + }).Run()) + + for range ctx.Done() { + return + } + }(ctx) + + time.Sleep(2 * time.Second) + s.Nil(s.app.Chain([]queue.Jobs{ + { + Job: &TestChainMachineryJob{}, + Args: []any{true}, + }, + { + Job: &TestChainSyncJob{}, + Args: []any{}, + }, + }).OnQueue("chain").Dispatch()) + + time.Sleep(2 * time.Second) + s.Equal(1, testChainMachineryJobError) + s.Equal(0, testChainSyncJob) + + s.mockConfig.AssertExpectations(s.T()) + s.mockLog.AssertExpectations(s.T()) +} + +type TestMachineryJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestMachineryJob) Signature() string { + return "test_async_job" +} + +// Handle Execute the job. +func (receiver *TestMachineryJob) Handle(args ...any) error { + testMachineryJob++ + + return nil +} + +type TestMachineryJobOfDisableDebug struct { +} + +// Signature The name and signature of the job. +func (receiver *TestMachineryJobOfDisableDebug) Signature() string { + return "test_async_job_of_disable_debug" +} + +// Handle Execute the job. +func (receiver *TestMachineryJobOfDisableDebug) Handle(args ...any) error { + testMachineryJobOfDisableDebug++ + + return nil +} + +type TestDelayMachineryJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestDelayMachineryJob) Signature() string { + return "test_delay_async_job" +} + +// Handle Execute the job. +func (receiver *TestDelayMachineryJob) Handle(args ...any) error { + testDelayMachineryJob++ + + return nil +} + +type TestCustomMachineryJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestCustomMachineryJob) Signature() string { + return "test_async_job" +} + +// Handle Execute the job. +func (receiver *TestCustomMachineryJob) Handle(args ...any) error { + testCustomMachineryJob++ + + return nil +} + +type TestErrorMachineryJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestErrorMachineryJob) Signature() string { + return "test_async_job" +} + +// Handle Execute the job. +func (receiver *TestErrorMachineryJob) Handle(args ...any) error { + testErrorMachineryJob++ + + return nil +} + +type TestChainMachineryJob struct { +} + +// Signature The name and signature of the job. +func (receiver *TestChainMachineryJob) Signature() string { + return "test_async_job" +} + +// Handle Execute the job. +func (receiver *TestChainMachineryJob) Handle(args ...any) error { + if len(args) > 0 && cast.ToBool(args[0]) { + testChainMachineryJobError++ + + return errors.New("error") + } + + testChainMachineryJob++ + + return nil +} From 91280d5773d0b91efabc92827f435b18442ea7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 12 Jan 2025 02:51:04 +0800 Subject: [PATCH 31/32] feat: finish machinery tests --- queue/driver_machinery_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/queue/driver_machinery_test.go b/queue/driver_machinery_test.go index a7a86a042..8e79c7cbf 100644 --- a/queue/driver_machinery_test.go +++ b/queue/driver_machinery_test.go @@ -102,7 +102,7 @@ func (s *MachineryTestSuite) TestDefaultAsyncQueue_EnableDebug() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) s.mockConfig.On("GetBool", "app.debug").Return(true).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(2) - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() @@ -145,7 +145,7 @@ func (s *MachineryTestSuite) TestDefaultAsyncQueue_DisableDebug() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(3) s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Times(3) - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() @@ -179,7 +179,7 @@ func (s *MachineryTestSuite) TestDelayAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() @@ -253,7 +253,7 @@ func (s *MachineryTestSuite) TestErrorAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() @@ -288,7 +288,7 @@ func (s *MachineryTestSuite) TestChainAsyncQueue() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() @@ -338,7 +338,7 @@ func (s *MachineryTestSuite) TestChainAsyncQueue_Error() { s.mockConfig.On("GetString", "app.name").Return("goravel").Times(4) s.mockConfig.On("GetBool", "app.debug").Return(false).Times(2) s.mockConfig.On("GetString", "queue.connections.redis.queue", "default").Return("default").Twice() - s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("redis").Times(3) + s.mockConfig.On("GetString", "queue.connections.redis.driver").Return("machinery").Times(3) s.mockConfig.On("GetString", "queue.connections.redis.connection").Return("default").Twice() s.mockConfig.On("GetString", "database.redis.default.host").Return("localhost").Twice() s.mockConfig.On("GetString", "database.redis.default.password").Return("").Twice() From 861dfb5399ff8ed0be858cf50aa8d14280749c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Thu, 30 Jan 2025 16:52:54 +0800 Subject: [PATCH 32/32] chore: merge master --- tests/go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/go.sum b/tests/go.sum index 99a072cd9..5f68f57cb 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -113,6 +113,7 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/samber/lo v1.48.0 h1:ELOfcaM7vdYPe0egBS2Nxa8LxkY4lR+9LBzj0l6cHJ0= +github.com/samber/lo v1.48.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=