Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bootstrap/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ import (
"goravel/routes"
)

// BootstrapRunners returns user-defined runners. Tests populate this slice to
// verify the disabled_runners config properly controls which runners start.
var BootstrapRunnerList []contractsfoundation.Runner

var BootstrapRunners = func() []contractsfoundation.Runner {
return BootstrapRunnerList
}

func Boot() contractsfoundation.Application {
return foundation.Setup().
WithMigrations(Migrations).
Expand Down Expand Up @@ -89,6 +97,7 @@ func Boot() contractsfoundation.Application {
paths.App("app")
}).
WithProviders(Providers).
WithRunners(BootstrapRunners).
WithSchedule(func() []schedule.Event {
return []schedule.Event{
facades.Schedule().Call(func() {
Expand Down
19 changes: 19 additions & 0 deletions config/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,24 @@ func init() {
"driver": config.Env("APP_MAINTENANCE_DRIVER", "file"),
"store": config.Env("APP_MAINTENANCE_STORE", ""),
},

// Disabled Runners
//
// Here you may specify which auto-run runners should be skipped when
// app.Start() is called. Patterns use path.Match glob matching.
//
// Framework runner signatures:
// goravel:http – HTTP server
// goravel:grpc – gRPC server
// goravel:queue – Queue worker
// goravel:schedule – Scheduler
// goravel:telemetry – Telemetry
//
// Example: disable scheduler for a web-only container
// "disabled_runners": []string{"goravel:schedule"},
//
// Example: disable all framework runners
// "disabled_runners": []string{"goravel:*"},
"disabled_runners": []string{},
})
}
143 changes: 143 additions & 0 deletions tests/feature/disabled_runners_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package feature

import (
"sync"
"sync/atomic"
"testing"
"time"

contractsfoundation "github.com/goravel/framework/contracts/foundation"
"github.com/stretchr/testify/suite"

"goravel/bootstrap"
"goravel/tests"
)

type trackerRunner struct {
sig string
started chan struct{}
done chan struct{}
once sync.Once
didRun atomic.Bool
}

func newTrackerRunner(sig string) *trackerRunner {
return &trackerRunner{
sig: sig,
started: make(chan struct{}),
done: make(chan struct{}),
}
}

func (r *trackerRunner) Signature() string { return r.sig }
func (r *trackerRunner) ShouldRun() bool { return true }

func (r *trackerRunner) Run() error {
r.didRun.Store(true)
close(r.started)
<-r.done
return nil
}

func (r *trackerRunner) Shutdown() error {
r.once.Do(func() { close(r.done) })
return nil
}

func (r *trackerRunner) Started() bool { return r.didRun.Load() }

type DisabledRunnersTestSuite struct {
suite.Suite
tests.TestCase
}

func TestDisabledRunnersTestSuite(t *testing.T) {
suite.Run(t, new(DisabledRunnersTestSuite))
}

func (s *DisabledRunnersTestSuite) SetupTest() {
bootstrap.BootstrapRunnerList = nil
}

func (s *DisabledRunnersTestSuite) TearDownTest() {
bootstrap.BootstrapRunnerList = nil
}

func (s *DisabledRunnersTestSuite) withRunners(runners []contractsfoundation.Runner, disabledPatterns []string) func() {
bootstrap.BootstrapRunnerList = runners
scope, err := tests.OverrideConfig(map[string]any{
"app.disabled_runners": disabledPatterns,
})
s.Require().NoError(err)

return func() {
bootstrap.BootstrapRunnerList = nil
s.Require().NoError(scope.Restore())
}
}

func (s *DisabledRunnersTestSuite) waitForStart(runner *trackerRunner) {
s.Require().Eventually(func() bool {
return runner.Started()
}, 3*time.Second, 50*time.Millisecond)
}

func (s *DisabledRunnersTestSuite) waitForNotStarted(runner *trackerRunner) {
s.Require().Never(func() bool {
return runner.Started()
}, 2*time.Second, 50*time.Millisecond)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_RunnerStartsWhenNotDisabled() {
runner := newTrackerRunner("test:demo")
cleanup := s.withRunners([]contractsfoundation.Runner{runner}, []string{})
defer cleanup()

s.waitForStart(runner)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_RunnerSkippedWhenDisabled() {
runner := newTrackerRunner("test:demo")
cleanup := s.withRunners([]contractsfoundation.Runner{runner}, []string{"test:demo"})
defer cleanup()

s.waitForNotStarted(runner)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_WildcardSkipsAll() {
runner := newTrackerRunner("test:demo")
cleanup := s.withRunners([]contractsfoundation.Runner{runner}, []string{"*"})
defer cleanup()

s.waitForNotStarted(runner)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_NamespaceWildcard() {
runner := newTrackerRunner("test:demo")
cleanup := s.withRunners([]contractsfoundation.Runner{runner}, []string{"test:*"})
defer cleanup()

s.waitForNotStarted(runner)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_SelectiveDisable() {
alpha := newTrackerRunner("test:alpha")
beta := newTrackerRunner("test:beta")
cleanup := s.withRunners([]contractsfoundation.Runner{alpha, beta}, []string{"test:alpha"})
defer cleanup()

s.waitForNotStarted(alpha)
s.waitForStart(beta)
}

func (s *DisabledRunnersTestSuite) TestDisabledRunners_RestoreEnablesRunner() {
runner := newTrackerRunner("test:demo")
cleanup := s.withRunners([]contractsfoundation.Runner{runner}, []string{"test:demo"})
s.waitForNotStarted(runner)
cleanup()

runner2 := newTrackerRunner("test:demo")
cleanup = s.withRunners([]contractsfoundation.Runner{runner2}, []string{})
defer cleanup()
s.waitForStart(runner2)
}
Loading