diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 79919f8..1241116 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -69,12 +69,12 @@ jobs: - name: Run server unit tests with coverage run: | mkdir ./tests/coverage-ci - go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=github.com/roadrunner-server/server/v6 -coverprofile=./tests/coverage-ci/server_u.out -covermode=atomic ./ + go test -timeout 20m -v -race -cover -tags=debug -coverpkg=./... -coverprofile=./tests/coverage-ci/server_u.out -covermode=atomic ./... - name: Run server e2e tests with coverage run: | cd tests - go test -timeout 20m -v -race -cover -tags=debug -failfast -coverpkg=github.com/roadrunner-server/server/v6 -coverprofile=./coverage-ci/server.out -covermode=atomic ./ + go test -timeout 20m -v -race -cover -tags=debug -coverpkg=github.com/roadrunner-server/server/v6/... -coverprofile=./coverage-ci/server.out -covermode=atomic ./... - name: Archive code coverage results uses: actions/upload-artifact@v7 @@ -109,6 +109,12 @@ jobs: } ' summary.txt > summary.filtered.txt mv summary.filtered.txt summary.txt + # a profile that maps to no plugin source uploads fine and reports 0% + blocks=$(($(wc -l < summary.txt) - 1)) + if [ "$blocks" -lt 10 ]; then + echo "::error::coverage summary holds $blocks blocks, the profile does not map to plugin sources" + exit 1 + fi - name: upload to codecov uses: codecov/codecov-action@v7 # Docs: diff --git a/plugin_test.go b/plugin_test.go index b3129b1..2a539de 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -1,9 +1,12 @@ package server import ( + "bytes" + "io" "log/slog" "os" "os/user" + "path/filepath" "runtime" "strconv" "testing" @@ -345,3 +348,99 @@ func TestEnv4(t *testing.T) { t.Fatal("FOO not found") } + +func TestName(t *testing.T) { + require.Equal(t, PluginName, (&Plugin{}).Name()) +} + +// TestUIDGIDWithoutUser covers the nil guard: with no user configured the +// plugin reports 0 rather than dereferencing the unset ids. +func TestUIDGIDWithoutUser(t *testing.T) { + p := &Plugin{} + + require.Equal(t, 0, p.UID()) + require.Equal(t, 0, p.GID()) +} + +func TestUIDGIDWithResolvedUser(t *testing.T) { + p := &Plugin{ids: &ids{uid: 1234, gid: 5678}} + + require.Equal(t, 1234, p.UID()) + require.Equal(t, 5678, p.GID()) +} + +func TestConfigInitDefaults(t *testing.T) { + t.Run("command is required", func(t *testing.T) { + require.ErrorContains(t, (&Config{}).InitDefaults(), "command should not be empty") + }) + + t.Run("relay defaults to pipes", func(t *testing.T) { + cfg := &Config{Command: []string{"php", "worker.php"}} + require.NoError(t, cfg.InitDefaults()) + require.Equal(t, "pipes", cfg.Relay) + }) + + t.Run("relay is left alone when set", func(t *testing.T) { + cfg := &Config{Command: []string{"php", "worker.php"}, Relay: "tcp://127.0.0.1:9999"} + require.NoError(t, cfg.InitDefaults()) + require.Equal(t, "tcp://127.0.0.1:9999", cfg.Relay) + }) + + t.Run("on_init command is required", func(t *testing.T) { + cfg := &Config{Command: []string{"php", "worker.php"}, OnInit: &InitConfig{}} + require.ErrorContains(t, cfg.InitDefaults(), "on_init command should not be empty") + }) + + t.Run("on_init exec timeout defaults to a minute", func(t *testing.T) { + cfg := &Config{ + Command: []string{"php", "worker.php"}, + OnInit: &InitConfig{Command: []string{"php", "init.php"}}, + } + require.NoError(t, cfg.InitDefaults()) + require.Equal(t, time.Minute, cfg.OnInit.ExecTimeout) + }) + + t.Run("on_init exec timeout is left alone when set", func(t *testing.T) { + cfg := &Config{ + Command: []string{"php", "worker.php"}, + OnInit: &InitConfig{Command: []string{"php", "init.php"}, ExecTimeout: time.Second * 5}, + } + require.NoError(t, cfg.InitDefaults()) + require.Equal(t, time.Second*5, cfg.OnInit.ExecTimeout) + }) +} + +// TestCommandWriteForwardsToLogger covers the io.Writer the on_init command's +// output is piped through. +func TestCommandWriteForwardsToLogger(t *testing.T) { + var buf bytes.Buffer + c := newCommand(slog.New(slog.NewTextHandler(&buf, nil)), &InitConfig{}) + + n, err := c.Write([]byte("hello from on_init")) + + require.NoError(t, err) + require.Equal(t, len("hello from on_init"), n) + require.Contains(t, buf.String(), "hello from on_init") +} + +// TestCreateProcessAppliesEnv checks config env is uppercased, expanded and +// appended after the OS environment so it wins. +func TestCreateProcessAppliesEnv(t *testing.T) { + t.Setenv("SERVER_TEST_BASE", "expanded") + + c := newCommand(slog.New(slog.NewTextHandler(io.Discard, nil)), &InitConfig{}) + cmd := c.createProcess(map[string]string{"lower_key": "${SERVER_TEST_BASE}-value"}, []string{"php", "worker.php"}) + + require.Equal(t, "php", filepath.Base(cmd.Path)) + require.Equal(t, []string{"php", "worker.php"}, cmd.Args) + require.Contains(t, cmd.Env, "LOWER_KEY=expanded-value") +} + +// TestCreateProcessSingleArgument covers the branch where the command carries +// no arguments. +func TestCreateProcessSingleArgument(t *testing.T) { + c := newCommand(slog.New(slog.NewTextHandler(io.Discard, nil)), &InitConfig{}) + cmd := c.createProcess(nil, []string{"php"}) + + require.Equal(t, []string{"php"}, cmd.Args) +} diff --git a/tests/configs/.rr-env.yaml b/tests/configs/.rr-env.yaml index 6e86ef6..aac6ab5 100644 --- a/tests/configs/.rr-env.yaml +++ b/tests/configs/.rr-env.yaml @@ -1,12 +1,13 @@ version: '3' server: - command: "php php_test_files/client.php echo pipes" + command: "php php_test_files/client.php env pipes" relay: "pipes" relay_timeout: "20s" env: - - DATABASE_URL: "mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?serverVersion=5.7" + - RR_PLAIN: "plain-value" + - RR_EXPANDED: "prefix-${RR_TEST_FROM_OS}-suffix" logs: mode: development - level: info + level: error diff --git a/tests/configs/.rr-metrics-oninit.yaml b/tests/configs/.rr-metrics-oninit.yaml index 88fddfe..7b25ea9 100644 --- a/tests/configs/.rr-metrics-oninit.yaml +++ b/tests/configs/.rr-metrics-oninit.yaml @@ -11,7 +11,7 @@ rpc: listen: tcp://127.0.0.1:6001 server: - command: "php foo" + command: "php php_test_files/client.php echo pipes" on_init: command: "php php_test_files/on-init-metrics.php" diff --git a/tests/configs/.rr-no-app-section.yaml b/tests/configs/.rr-no-app-section.yaml deleted file mode 100644 index b95f11a..0000000 --- a/tests/configs/.rr-no-app-section.yaml +++ /dev/null @@ -1,12 +0,0 @@ -version: '3' - -server: - command: "php php_test_files/client.php echo pipes" - env: - - RR_CONFIG: "/some/place/on/the/C134" - - RR_CONFIG2: "C138" - relay: "pipes" - relay_timeout: "20s" -logs: - mode: development - level: debug diff --git a/tests/errors_test.go b/tests/errors_test.go new file mode 100644 index 0000000..1e863b4 --- /dev/null +++ b/tests/errors_test.go @@ -0,0 +1,47 @@ +package tests + +import ( + "testing" + + "tests/helpers" + + "github.com/roadrunner-server/server/v6" +) + +// The cases below all describe a broken configuration. Each must be rejected at +// the boot stage where the problem is detectable, rather than starting a server +// that cannot work. + +// TestMissingConfigFileFailsInit covers a config path that does not exist. +func TestMissingConfigFileFailsInit(t *testing.T) { + _ = helpers.StartExpectInitError(t, "configs/.rrrrrrrrrr.yaml", []any{&server.Plugin{}}, + helpers.WithConfigVersion("v2024.1.0")) +} + +// TestUnknownRelayFailsInit covers a relay value the plugin does not implement. +func TestUnknownRelayFailsInit(t *testing.T) { + _ = helpers.StartExpectInitError(t, "configs/.rr-wrong-relay.yaml", []any{&server.Plugin{}}, + helpers.WithConfigVersion("v2024.1.0")) +} + +// TestUnrunnableCommandFailsServe covers a command that cannot be executed: the +// config is well formed, so this only shows up when workers are allocated. +func TestUnrunnableCommandFailsServe(t *testing.T) { + _ = helpers.StartExpectServeError(t, "configs/.rr-wrong-command.yaml", []any{&server.Plugin{}, &Foo{}}, + helpers.WithConfigVersion("v2024.1.0")) +} + +// TestUnrunnableOnInitCommandFailsServe is the same for the on_init command. +// The fixture has to be registered as well: the server plugin only reports the +// failure once something asks it for a pool. +func TestUnrunnableOnInitCommandFailsServe(t *testing.T) { + _ = helpers.StartExpectServeError(t, "configs/.rr-wrong-command-on-init.yaml", []any{&server.Plugin{}, &Foo3{}}, + helpers.WithConfigVersion("v2024.1.0")) +} + +// TestWorkerExceptionFailsServe covers a worker script that throws during +// startup, so the pool cannot be filled. +func TestWorkerExceptionFailsServe(t *testing.T) { + _ = helpers.StartExpectServeError(t, "configs/.rr-script-err.yaml", []any{&server.Plugin{}, &Foo{}}, + helpers.WithConfigVersion("v2024.1.0")) +} diff --git a/tests/go.mod b/tests/go.mod index 269db02..aff1f41 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -2,17 +2,15 @@ module tests go 1.26 -toolchain go1.26.5 +toolchain go1.26.6 require ( github.com/roadrunner-server/config/v6 v6.0.0-beta.3 github.com/roadrunner-server/endure/v2 v2.6.2 github.com/roadrunner-server/errors v1.5.0 - github.com/roadrunner-server/http/v6 v6.0.0-beta.8 github.com/roadrunner-server/logger/v6 v6.0.0-beta.3 github.com/roadrunner-server/metrics/v6 v6.0.0-beta.5 github.com/roadrunner-server/pool/v2 v2.0.0-beta.1 - github.com/roadrunner-server/prometheus/v6 v6.0.0-beta.2 github.com/roadrunner-server/rpc/v6 v6.0.0-beta.5 github.com/roadrunner-server/server/v6 v6.0.0 github.com/stretchr/testify v1.11.1 @@ -22,26 +20,16 @@ replace github.com/roadrunner-server/server/v6 => ../ require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/caddyserver/certmagic v0.25.4 // indirect - github.com/caddyserver/zerossl v0.1.5 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.19.0 // indirect - github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect - github.com/go-logr/logr v1.4.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/joho/godotenv v1.5.1 // indirect - github.com/klauspost/cpuid/v2 v2.4.0 // indirect - github.com/libdns/libdns v1.1.1 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect - github.com/mholt/acmez v1.2.0 // indirect - github.com/mholt/acmez/v3 v3.1.6 // indirect - github.com/miekg/dns v1.1.72 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -49,11 +37,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.61.0 // indirect github.com/roadrunner-server/api-go/v6 v6.0.0-beta.13 // indirect - github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2 // indirect - github.com/roadrunner-server/context v1.3.0 // indirect github.com/roadrunner-server/events v1.0.1 // indirect github.com/roadrunner-server/goridge/v4 v4.0.0-beta.3 // indirect github.com/roadrunner-server/tcplisten v1.5.2 // indirect @@ -67,26 +51,12 @@ require ( github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zeebo/assert v1.3.1 // indirect - github.com/zeebo/blake3 v0.2.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.45.0 // indirect - go.opentelemetry.io/otel v1.45.0 // indirect - go.opentelemetry.io/otel/metric v1.45.0 // indirect - go.opentelemetry.io/otel/trace v1.45.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/crypto v0.55.0 // indirect - golang.org/x/mod v0.39.0 // indirect - golang.org/x/net v0.58.0 // indirect golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect - golang.org/x/tools v0.48.0 // indirect - google.golang.org/genproto v0.0.0-20260810153831-ec0a7760b754 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 308fd0a..2128b18 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -1,30 +1,15 @@ -code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE= -code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/caddyserver/certmagic v0.25.4 h1:8eIXh0HC3MsGnNo8One+BCxMGTbe5zb/oz+2KsxBFQg= -github.com/caddyserver/certmagic v0.25.4/go.mod h1:YVs43D5+H/Dckt4bTga1KSO/xYfFBfVZainGDywYPAA= -github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= -github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= -github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= -github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -38,30 +23,16 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= -github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= -github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= -github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= -github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ= -github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U= -github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= -github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= -github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= -github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk= -github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY= -github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= -github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= @@ -76,20 +47,10 @@ github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= -github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= -github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= github.com/roadrunner-server/api-go/v6 v6.0.0-beta.13 h1:BAV1aKkRp51C1OXDfEYZXgfrXqn4O7bpr6Z/m5otwd8= github.com/roadrunner-server/api-go/v6 v6.0.0-beta.13/go.mod h1:Y4rsabWjr4Y10Jg6H8J5NDitQqlnXmGhCdgR+zyLYkI= -github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2 h1:GqsZzWQ5jMXRF1O/b8IqFz9PLpS7Ui0K4OyACLql2MI= -github.com/roadrunner-server/api-plugins/v6 v6.0.0-beta.2/go.mod h1:2v4yUK5Kvbvq8C3IkDoBkuamq9h+7i/JLjyf7k1j5JM= github.com/roadrunner-server/config/v6 v6.0.0-beta.3 h1:G0EUzJ6Yw4UnleM6BhnOBbYPXKDHRmCJiGhC3nXDBwI= github.com/roadrunner-server/config/v6 v6.0.0-beta.3/go.mod h1:eIB+c29njpcKokXrxe483FbQOBSTNGvU3hhC6W/qYSU= -github.com/roadrunner-server/context v1.3.0 h1:iyTXVORhPU2/26z7kdzEaggwG5P8yhIKUDLiePjylFQ= -github.com/roadrunner-server/context v1.3.0/go.mod h1:KPAzAlnErXekQazW9t4h55U1S42Q2bk0WCaPQrezJw4= github.com/roadrunner-server/endure/v2 v2.6.2 h1:sIB4kTyE7gtT3fDhuYWUYn6Vt/dcPtiA6FoNS1eS+84= github.com/roadrunner-server/endure/v2 v2.6.2/go.mod h1:t/2+xpNYgGBwhzn83y2MDhvhZ19UVq1REcvqn7j7RB8= github.com/roadrunner-server/errors v1.5.0 h1:unG7LKIZrSzkCCF3YLRLA5VyqE0KKomofXVJUXJe00g= @@ -98,16 +59,12 @@ github.com/roadrunner-server/events v1.0.1 h1:waCkKhxhzdK3VcI1xG22l+h+0J+Nfdpxjh github.com/roadrunner-server/events v1.0.1/go.mod h1:WZRqoEVaFm209t52EuoT7ISUtvX6BrCi6bI/7pjkVC0= github.com/roadrunner-server/goridge/v4 v4.0.0-beta.3 h1:+kUw00/fpqwdMWrPMYW+OZH3O4gEar8hqrY7I+nAztA= github.com/roadrunner-server/goridge/v4 v4.0.0-beta.3/go.mod h1:1aHppV68y/VqRED/AsfNg59sft9aQOhqgr5Z5n49jbM= -github.com/roadrunner-server/http/v6 v6.0.0-beta.8 h1:habLZdPLG57XATjBLiTmDujnVgkOc4bgkHK39lMwYU8= -github.com/roadrunner-server/http/v6 v6.0.0-beta.8/go.mod h1:tv/QMqbNcKbqtdt/iRH4kRwvCWdhRLWwGsfAoMBC9F8= github.com/roadrunner-server/logger/v6 v6.0.0-beta.3 h1:eoJKXAUSyykDfVX6eTUhmAn6Y8pS/LyI5fDP4H+G5rQ= github.com/roadrunner-server/logger/v6 v6.0.0-beta.3/go.mod h1:MwHb3AbltHYtu7nRpml5NeYu7O+W8rCpDBeNTTEoE1M= github.com/roadrunner-server/metrics/v6 v6.0.0-beta.5 h1:JaJGPwjVKDUJi0Ey3ztkjhod1EYHbmf8gmdPoYWCWbo= github.com/roadrunner-server/metrics/v6 v6.0.0-beta.5/go.mod h1:VAY+k1uFqySbt9E9RaGQXXN+Pn0D2cuJEpO0i66Ny2M= github.com/roadrunner-server/pool/v2 v2.0.0-beta.1 h1:jpYXFtdD6QGAdAGPgMxrNi3j1CegCRpb2y+A+3GnXFA= github.com/roadrunner-server/pool/v2 v2.0.0-beta.1/go.mod h1:Bo1wT7RtL3eyQHXBUohNhtj/yAmRt6Rq8smuBg5pWkY= -github.com/roadrunner-server/prometheus/v6 v6.0.0-beta.2 h1:e6Z5YFRwi1Tcr9T5sgecfqcOUo3XC7fTkcoGVKP6mnw= -github.com/roadrunner-server/prometheus/v6 v6.0.0-beta.2/go.mod h1:OYfupkw6fQjZHf83T87KUXNx4/IwCMlBA3J3sqDx5Wg= github.com/roadrunner-server/rpc/v6 v6.0.0-beta.5 h1:FjwXfznbmyCEKFUHkxnvK8yo1HQKAQk+7fiPhSYC27E= github.com/roadrunner-server/rpc/v6 v6.0.0-beta.5/go.mod h1:z387hZZOEJ3+bB8iW1PEAEXF3jUUb/dDLDnVSc1CNNQ= github.com/roadrunner-server/tcplisten v1.5.2 h1:nn8yXYrhRDkfQ9AAu4V075uT4fZRmOnpxkawgE+bWPA= @@ -136,48 +93,16 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= -github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= -github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= -github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= -go.opentelemetry.io/contrib/propagators/jaeger v1.45.0 h1:e8U4utKt9oV2TfLKZFqUzz5shYKnUf3DISalTpLs4lA= -go.opentelemetry.io/contrib/propagators/jaeger v1.45.0/go.mod h1:lx91c/ZlmgS2rjGOuXB+Mmq+f0QxzC9UjYUuJwR4tvQ= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= -go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= -go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= -go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -186,10 +111,6 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= -google.golang.org/genproto v0.0.0-20260810153831-ec0a7760b754 h1:Kj7g/XOpdB2mzcVV92AFeNKvYR5WRNpxfX5Mj3wQ2SM= -google.golang.org/genproto v0.0.0-20260810153831-ec0a7760b754/go.mod h1:UpDDw2l68z31m5UZN/Qi0Kow16ohhlJVlmqC3qRM5Q8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/helpers/rr.go b/tests/helpers/rr.go new file mode 100644 index 0000000..332298e --- /dev/null +++ b/tests/helpers/rr.go @@ -0,0 +1,165 @@ +package helpers + +import ( + "context" + "log/slog" + "net" + "sync" + "testing" + "time" + + "github.com/roadrunner-server/config/v6" + "github.com/roadrunner-server/endure/v2" + "github.com/roadrunner-server/logger/v6" + "github.com/stretchr/testify/require" +) + +const ( + // defaultConfigVersion is the config schema version used by the test configs. + defaultConfigVersion = "v2024.2.0" + // probeTimeout caps how long Start waits for the rpc listener to answer. + probeTimeout = time.Second * 15 + probeTick = time.Millisecond * 20 + probeDial = time.Second +) + +// bootCfg holds the options applied to a container before it is started. +type bootCfg struct { + version string + logLevel slog.Level + probe func(ctx context.Context) bool +} + +// Option customizes the container built by Start. +type Option func(*bootCfg) + +// WithConfigVersion overrides the config schema version. +func WithConfigVersion(v string) Option { + return func(b *bootCfg) { b.version = v } +} + +// WithLogLevel sets the endure container log level (debug by default). +func WithLogLevel(l slog.Level) Option { + return func(b *bootCfg) { b.logLevel = l } +} + +// WithTCPProbe makes Start return only once addr accepts a connection. The rpc +// listener binds after the storage drivers are constructed, so dialing it +// proves the plugin is ready to serve calls. +func WithTCPProbe(addr string) Option { + return func(b *bootCfg) { + b.probe = func(ctx context.Context) bool { + d := net.Dialer{Timeout: probeDial} + conn, err := d.DialContext(ctx, "tcp", addr) + if err != nil { + return false + } + + _ = conn.Close() + return true + } + } +} + +// Start registers the plugins, boots the container and waits for the probe, if +// any, to answer. Errors arriving on the container channel are reported through +// t.Errorf and stop the container, but they do not abort the test. +// +// The returned stop is idempotent and also registered with t.Cleanup. +func Start(t *testing.T, cfgPath string, plugins []any, opts ...Option) func() { + t.Helper() + + cont, bc := newContainer(t, cfgPath, plugins, opts) + require.NoError(t, cont.Init()) + + ch, err := cont.Serve() + require.NoError(t, err) + + stopCont := sync.OnceValue(cont.Stop) + done := make(chan struct{}) + wg := &sync.WaitGroup{} + + wg.Go(func() { + for { + select { + case res := <-ch: + if res == nil { + return + } + t.Errorf("plugin %s reported an error: %v", res.VertexID, res.Error) + if errS := stopCont(); errS != nil { + t.Errorf("container stop: %v", errS) + } + case <-done: + if errS := stopCont(); errS != nil { + t.Errorf("container stop: %v", errS) + } + return + } + } + }) + + // The drain goroutine calls t.Errorf, so it has to be joined while the test + // is still running. + stop := sync.OnceFunc(func() { + close(done) + wg.Wait() + }) + t.Cleanup(stop) + + if bc.probe != nil { + require.Eventually(t, func() bool { return bc.probe(t.Context()) }, probeTimeout, probeTick, "rpc listener did not become ready") + } + + return stop +} + +// StartExpectInitError registers the plugins and requires Init to fail, +// returning its error. +func StartExpectInitError(t *testing.T, cfgPath string, plugins []any, opts ...Option) error { + t.Helper() + + cont, _ := newContainer(t, cfgPath, plugins, opts) + + err := cont.Init() + require.Error(t, err) + + return err +} + +// StartExpectServeError registers the plugins, requires Init to pass and Serve +// to fail, and returns the Serve error. +func StartExpectServeError(t *testing.T, cfgPath string, plugins []any, opts ...Option) error { + t.Helper() + + cont, _ := newContainer(t, cfgPath, plugins, opts) + require.NoError(t, cont.Init()) + + _, err := cont.Serve() + require.Error(t, err) + t.Cleanup(func() { _ = cont.Stop() }) + + return err +} + +// newContainer builds the container and registers the config, the logger and +// the caller's plugins. The container is not initialized yet. +func newContainer(t *testing.T, cfgPath string, plugins []any, opts []Option) (*endure.Endure, *bootCfg) { + t.Helper() + + bc := &bootCfg{version: defaultConfigVersion, logLevel: slog.LevelDebug} + for _, o := range opts { + o(bc) + } + + all := make([]any, 0, 2+len(plugins)) + all = append(all, + &config.Plugin{Version: bc.version, Path: cfgPath}, + &logger.Plugin{}, + ) + + cont := endure.New(bc.logLevel) + require.NoError(t, cont.RegisterAll(append(all, plugins...)...)) + + return cont, bc +} diff --git a/tests/oninit_test.go b/tests/oninit_test.go new file mode 100644 index 0000000..923b664 --- /dev/null +++ b/tests/oninit_test.go @@ -0,0 +1,91 @@ +package tests + +import ( + "io" + "net/http" + "testing" + + "tests/helpers" + + "github.com/roadrunner-server/metrics/v6" + rpcPlugin "github.com/roadrunner-server/rpc/v6" + "github.com/roadrunner-server/server/v6" + "github.com/stretchr/testify/require" +) + +const metricsAddr = "127.0.0.1:9254" + +// TestTCPRelayWithOnInit runs the on_init command before the pool starts and +// then exercises the pool over the tcp relay. +func TestTCPRelayWithOnInit(t *testing.T) { + helpers.Start(t, "configs/.rr-tcp-on-init.yaml", []any{&server.Plugin{}, &Foo2{}}) +} + +// TestSocketsRelayWithOnInit is the same over the socket relay. +func TestSocketsRelayWithOnInit(t *testing.T) { + helpers.Start(t, "configs/.rr-sockets-on-init.yaml", []any{&server.Plugin{}, &Foo2{}}) +} + +// TestOnInitFastClose covers an on_init command that exits immediately: the +// pool must still come up rather than treating the early exit as a failure. +func TestOnInitFastClose(t *testing.T) { + helpers.Start(t, "configs/.rr-sockets-on-init-fast-close.yaml", []any{&server.Plugin{}, &Foo2{}}) +} + +// TestOnInitErrorFailsServe covers an on_init command that exits non-zero. +func TestOnInitErrorFailsServe(t *testing.T) { + _ = helpers.StartExpectServeError(t, "configs/.rr-on-init-error.yaml", []any{&server.Plugin{}}, + helpers.WithConfigVersion("v2024.1.0")) +} + +// TestOnInitTimeoutFailsServe covers an on_init command that never returns; the +// error has to name the timeout so the cause is obvious from CI output. +func TestOnInitTimeoutFailsServe(t *testing.T) { + err := helpers.StartExpectServeError(t, "configs/.rr-on-init-error-timeout.yaml", []any{&server.Plugin{}}, + helpers.WithConfigVersion("v2024.1.0")) + + require.ErrorContains(t, err, "startup process has been killed by timeout") +} + +// TestOnInitRunsWithMetricsEndpoint boots the config whose on_init script talks +// to the metrics plugin over rpc and checks the exporter is serving. +// +// The stronger assertion - that the script's collector shows up as +// foo_bar_test - cannot pass yet. The metrics plugin resolves api-go beta.13, +// which carries the connect-era protos, while spiral/roadrunner-metrics 3.3.0 +// speaks the v1 line, so the Declare message decodes empty: +// +// declaring new metric name="" type=COLLECTOR_TYPE_UNSPECIFIED namespace="" +// +// Once the plugin betas are retagged against api-go beta.14, swap the body for +// a poll on foo_bar_test. +func TestOnInitRunsWithMetricsEndpoint(t *testing.T) { + helpers.Start(t, + "configs/.rr-metrics-oninit.yaml", + []any{&server.Plugin{}, &rpcPlugin.Plugin{}, &metrics.Plugin{}}, + helpers.WithTCPProbe(metricsAddr), + ) + + body := scrapeMetrics(t) + + require.Contains(t, body, "go_goroutines", "the metrics exporter is not serving") +} + +// scrapeMetrics fetches the exporter output. +func scrapeMetrics(t *testing.T) string { + t.Helper() + + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://"+metricsAddr+"/metrics", nil) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + + defer func() { require.NoError(t, resp.Body.Close()) }() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + return string(body) +} diff --git a/tests/php_test_files/env.php b/tests/php_test_files/env.php new file mode 100644 index 0000000..4212af0 --- /dev/null +++ b/tests/php_test_files/env.php @@ -0,0 +1,20 @@ +waitPayload()) { + try { + $rr->respond(new RoadRunner\Payload((string)getenv((string)$in->body))); + } catch (\Throwable $e) { + $rr->error((string)$e); + } +} diff --git a/tests/php_test_files/on-init-metrics.php b/tests/php_test_files/on-init-metrics.php index 3fd2cbf..d798ed3 100644 --- a/tests/php_test_files/on-init-metrics.php +++ b/tests/php_test_files/on-init-metrics.php @@ -2,34 +2,24 @@ declare(strict_types=1); +// deprecation notices on stdout would corrupt nothing here, but keep the +// on_init output clean so a failure is readable in the server log +ini_set('display_errors', 'stderr'); + require __DIR__ . '/vendor/autoload.php'; -use Spiral\Goridge\Relay; use Spiral\Goridge\RPC\RPC; +use Spiral\RoadRunner\Metrics\Collector; +use Spiral\RoadRunner\Metrics\Metrics; -$rpc = new RPC( - Relay::create('tcp://0.0.0.0:6001') -); -echo "foo"; -$rpc = $rpc->withServicePrefix('metrics'); - -$rpc->call('Declare', [ - 'name' => 'test', - 'collector' => [ - 'namespace' => 'foo', - 'subsystem' => 'bar', - 'type' => 'counter', - 'help' => '', - 'labels' => [], - 'buckets' => [], - ], -]); +$metrics = new Metrics(RPC::create('tcp://127.0.0.1:6001')); -echo "foo2"; -$rpc->call('Add', [ - 'name' => 'test', - 'value' => 1.0, - 'labels' => [], -]); +$metrics->declare( + 'test', + Collector::counter() + ->withNamespace('foo') + ->withSubsystem('bar') + ->withHelp('test counter declared from on_init'), +); -echo "ON INIT"; \ No newline at end of file +$metrics->add('test', 1); diff --git a/tests/plugin_env.go b/tests/plugin_env.go new file mode 100644 index 0000000..b9f302f --- /dev/null +++ b/tests/plugin_env.go @@ -0,0 +1,51 @@ +package tests + +import ( + "context" + + "github.com/roadrunner-server/errors" + "github.com/roadrunner-server/pool/v2/payload" +) + +// FooEnv asks the worker for the value of two environment variables declared in +// the server.env config block, proving they reached the worker process. +type FooEnv struct { + wf Server +} + +func (f *FooEnv) Init(_ Configurer, workerFactory Server) error { + f.wf = workerFactory + return nil +} + +func (f *FooEnv) Serve() chan error { + errCh := make(chan error, 1) + + pl, err := f.wf.NewPool(context.Background(), testPoolConfig, nil, nil) + if err != nil { + errCh <- err + return errCh + } + + for name, want := range map[string]string{ + "RR_PLAIN": "plain-value", + "RR_EXPANDED": "prefix-from-os-suffix", + } { + rs, errE := pl.Exec(context.Background(), &payload.Payload{Body: []byte(name)}, make(chan struct{}, 1)) + if errE != nil { + errCh <- errE + return errCh + } + + if got := string((<-rs).Body()); got != want { + errCh <- errors.Errorf("%s: want %q, got %q", name, want, got) + return errCh + } + } + + return errCh +} + +func (f *FooEnv) Stop(context.Context) error { return nil } + +func (f *FooEnv) Name() string { return "foo_env" } diff --git a/tests/plugin_pipes.go b/tests/plugin_pipes.go index 2f92c36..b543097 100644 --- a/tests/plugin_pipes.go +++ b/tests/plugin_pipes.go @@ -68,8 +68,6 @@ func (f *Foo) Init(p Configurer, workerFactory Server) error { } func (f *Foo) Serve() chan error { - const op = errors.Op("serve") - // test payload for echo r := &payload.Payload{ Context: nil, diff --git a/tests/plugin_pool_with_options.go b/tests/plugin_pool_with_options.go index ad627a1..c180a57 100644 --- a/tests/plugin_pool_with_options.go +++ b/tests/plugin_pool_with_options.go @@ -23,7 +23,6 @@ func (f *Foo5) Init(p Configurer, workerFactory Server) error { } func (f *Foo5) Serve() chan error { - const op = errors.Op("serve") var err error errCh := make(chan error, 1) conf := &serverImpl.Config{} diff --git a/tests/plugin_sockets.go b/tests/plugin_sockets.go index 83ef5a0..73e2710 100644 --- a/tests/plugin_sockets.go +++ b/tests/plugin_sockets.go @@ -21,7 +21,6 @@ func (f *Foo2) Init(p Configurer, workerFactory Server) error { } func (f *Foo2) Serve() chan error { - const op = errors.Op("serve") var err error errCh := make(chan error, 1) conf := &serverImpl.Config{} diff --git a/tests/plugin_tcp.go b/tests/plugin_tcp.go index ab11f4f..f070b2b 100644 --- a/tests/plugin_tcp.go +++ b/tests/plugin_tcp.go @@ -21,7 +21,6 @@ func (f *Foo3) Init(p Configurer, workerFactory Server) error { } func (f *Foo3) Serve() chan error { - const op = errors.Op("serve") var err error errCh := make(chan error, 1) conf := &serverImpl.Config{} diff --git a/tests/relay_test.go b/tests/relay_test.go new file mode 100644 index 0000000..4b2ecca --- /dev/null +++ b/tests/relay_test.go @@ -0,0 +1,51 @@ +package tests + +import ( + "testing" + + "tests/helpers" + + "github.com/roadrunner-server/server/v6" +) + +// The fixture plugins below drive the actual assertions: each one asks the +// server plugin for a worker and a pool, execs an echo payload through both and +// pushes anything unexpected onto the container's error channel. Start turns an +// error there into a test failure, so booting the container is the assertion. + +// TestPipesRelay covers the default relay, where the worker talks over stdin +// and stdout. +func TestPipesRelay(t *testing.T) { + helpers.Start(t, "configs/.rr.yaml", []any{&server.Plugin{}, &Foo{}}) +} + +// TestPipesRelayBigResponse covers a response larger than a single frame, which +// exercises the relay's chunking. +func TestPipesRelayBigResponse(t *testing.T) { + helpers.Start(t, "configs/.rr-pipes-big-resp.yaml", []any{&server.Plugin{}, &Foo4{}}) +} + +// TestSocketsRelay covers the unix socket relay. +func TestSocketsRelay(t *testing.T) { + helpers.Start(t, "configs/.rr-sockets.yaml", []any{&server.Plugin{}, &Foo2{}}) +} + +// TestTCPRelay covers the tcp relay. +func TestTCPRelay(t *testing.T) { + helpers.Start(t, "configs/.rr-tcp.yaml", []any{&server.Plugin{}, &Foo3{}}) +} + +// TestPoolWithOptions covers NewPool called with explicit options rather than +// the config defaults. +func TestPoolWithOptions(t *testing.T) { + helpers.Start(t, "configs/.rr-tcp.yaml", []any{&server.Plugin{}, &Foo5{}}) +} + +// TestServerEnvReachesWorker proves the server.env block is passed to the +// worker process, including a value built from an OS variable through ${...} +// expansion. Both env configs in this directory were unreferenced before. +func TestServerEnvReachesWorker(t *testing.T) { + t.Setenv("RR_TEST_FROM_OS", "from-os") + + helpers.Start(t, "configs/.rr-env.yaml", []any{&server.Plugin{}, &FooEnv{}}) +} diff --git a/tests/server_plugin_test.go b/tests/server_plugin_test.go deleted file mode 100644 index 925bb2a..0000000 --- a/tests/server_plugin_test.go +++ /dev/null @@ -1,797 +0,0 @@ -package tests - -import ( - "bytes" - "io" - "log/slog" - "os" - "os/signal" - "strings" - "sync" - "syscall" - "testing" - "time" - - "github.com/roadrunner-server/config/v6" - "github.com/roadrunner-server/endure/v2" - httpPlugin "github.com/roadrunner-server/http/v6" - "github.com/roadrunner-server/logger/v6" - "github.com/roadrunner-server/metrics/v6" - "github.com/roadrunner-server/prometheus/v6" - rpcPlugin "github.com/roadrunner-server/rpc/v6" - "github.com/roadrunner-server/server/v6" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - mockLogger "tests/mock" -) - -func TestAppPipes(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.2.0", - Path: "configs/.rr.yaml", - } - - err := cont.RegisterAll( - vp, - &server.Plugin{}, - &Foo{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - stopCh <- struct{}{} - wg.Wait() -} - -func TestAppPipesBigResp(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.2.0", - Path: "configs/.rr-pipes-big-resp.yaml", - } - - rd, wr, err := os.Pipe() - require.NoError(t, err) - os.Stderr = wr - - err = cont.RegisterAll( - vp, - &server.Plugin{}, - &Foo4{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - time.Sleep(time.Second * 5) - stopCh <- struct{}{} - wg.Wait() - - time.Sleep(time.Second) - _ = wr.Close() - buf := new(bytes.Buffer) - _, err = io.Copy(buf, rd) - require.NoError(t, err) - require.GreaterOrEqual(t, strings.Count(buf.String(), "A"), 64000) -} - -func TestAppSockets(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.2.0", - Path: "configs/.rr-sockets.yaml", - } - - err := cont.RegisterAll( - vp, - &server.Plugin{}, - &Foo2{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - time.Sleep(time.Second * 5) - stopCh <- struct{}{} - wg.Wait() -} - -func TestAppPipesException(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Path: "configs/.rr-script-err.yaml", - Version: "v2024.1.0", - } - - err := container.RegisterAll( - vp, - &server.Plugin{}, - &logger.Plugin{}, - &httpPlugin.Plugin{}, - ) - require.NoError(t, err) - - err = container.Init() - require.NoError(t, err) - - _, err = container.Serve() - require.Error(t, err) - assert.Contains(t, err.Error(), "validation failed on the message sent to STDOUT, see: https://docs.roadrunner.dev/error-codes/stdout-crc, invalid message: warning: some weird php error warning: some weird php error warning: some weird php error warning: some weird php error warning: some weird php error") - _ = container.Stop() -} - -func TestAppTCPOnInitError(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-on-init-error.yaml", - } - - err := cont.Register(vp) - require.NoError(t, err) - - err = cont.RegisterAll( - &logger.Plugin{}, - &server.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - _, err = cont.Serve() - require.Error(t, err) -} - -func TestAppTCPOnInitErrorTimeout(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-on-init-error-timeout.yaml", - } - - err := cont.Register(vp) - require.NoError(t, err) - - err = cont.RegisterAll( - &logger.Plugin{}, - &server.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - _, err = cont.Serve() - require.Error(t, err) - require.Contains(t, err.Error(), "startup process has been killed by timeout") -} - -func TestAppTCPOnInit(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-tcp-on-init.yaml", - } - - err := cont.Register(vp) - require.NoError(t, err) - - l, oLogger := mockLogger.SlogTestLogger(slog.LevelDebug) - err = cont.RegisterAll( - l, - &server.Plugin{}, - &Foo2{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - time.Sleep(time.Second * 10) - stopCh <- struct{}{} - wg.Wait() - - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 0").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 1").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 2").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 3").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 4").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 5").Len()) -} - -func TestAppSocketsOnInit(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-sockets-on-init.yaml", - } - - err := cont.Register(vp) - require.NoError(t, err) - - l, oLogger := mockLogger.SlogTestLogger(slog.LevelDebug) - err = cont.RegisterAll( - l, - &server.Plugin{}, - &Foo2{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - time.Sleep(time.Second * 10) - stopCh <- struct{}{} - wg.Wait() - - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 0\n").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 1\n").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 2\n").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 3\n").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 4\n").Len()) - require.Equal(t, 1, oLogger.FilterMessageSnippet("The number is: 5\n").Len()) -} - -func TestAppSocketsOnInitFastClose(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-sockets-on-init-fast-close.yaml", - } - - err := cont.Register(vp) - require.NoError(t, err) - - l, oLogger := mockLogger.SlogTestLogger(slog.LevelDebug) - err = cont.RegisterAll( - l, - &server.Plugin{}, - &Foo2{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - time.Sleep(time.Second * 10) - stopCh <- struct{}{} - wg.Wait() - - require.Equal(t, 1, oLogger.FilterMessageSnippet("process wait").Len()) -} - -func TestAppTCP(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-tcp.yaml", - } - - err := cont.RegisterAll( - vp, - &server.Plugin{}, - &Foo3{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - stopCh <- struct{}{} - wg.Wait() -} - -func TestAppWrongConfig(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rrrrrrrrrr.yaml", - } - - err := container.Register(vp) - require.NoError(t, err) - - err = container.Register(&server.Plugin{}) - require.NoError(t, err) - - err = container.Register(&Foo3{}) - require.NoError(t, err) - - err = container.Register(&logger.Plugin{}) - require.NoError(t, err) - - require.Error(t, container.Init()) -} - -func TestAppWrongRelay(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-wrong-relay.yaml", - } - - err := container.Register(vp) - assert.NoError(t, err) - - err = container.Register(&server.Plugin{}) - assert.NoError(t, err) - - err = container.Register(&Foo3{}) - assert.NoError(t, err) - - err = container.Register(&logger.Plugin{}) - assert.NoError(t, err) - - err = container.Init() - assert.Error(t, err) - - _, err = container.Serve() - assert.Error(t, err) - - _ = container.Stop() -} - -func TestAppWrongCommand(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-wrong-command.yaml", - } - - err := container.Register(vp) - require.NoError(t, err) - - err = container.Register(&server.Plugin{}) - require.NoError(t, err) - - err = container.Register(&Foo3{}) - require.NoError(t, err) - - err = container.Register(&logger.Plugin{}) - require.NoError(t, err) - - err = container.Init() - require.NoError(t, err) - - _, err = container.Serve() - require.Error(t, err) -} - -func TestAppWrongCommandOnInit(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-wrong-command-on-init.yaml", - } - - err := container.Register(vp) - require.NoError(t, err) - - err = container.Register(&server.Plugin{}) - require.NoError(t, err) - - err = container.Register(&Foo3{}) - require.NoError(t, err) - - err = container.Register(&logger.Plugin{}) - require.NoError(t, err) - - err = container.Init() - require.NoError(t, err) - - _, err = container.Serve() - require.Error(t, err) -} - -func TestAppNoAppSectionInConfig(t *testing.T) { - container := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-wrong-command.yaml", - } - - err := container.Register(vp) - require.NoError(t, err) - - err = container.Register(&server.Plugin{}) - require.NoError(t, err) - - err = container.Register(&Foo3{}) - require.NoError(t, err) - - err = container.Register(&logger.Plugin{}) - require.NoError(t, err) - - err = container.Init() - require.NoError(t, err) - - _, err = container.Serve() - require.Error(t, err) -} - -func TestOnInitMetrics(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-metrics-oninit.yaml", - } - - err := cont.RegisterAll( - vp, - &server.Plugin{}, - &metrics.Plugin{}, - &prometheus.Plugin{}, - &rpcPlugin.Plugin{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - stopCh <- struct{}{} - wg.Wait() -} - -func TestNewPoolWithOptions(t *testing.T) { - cont := endure.New(slog.LevelDebug) - - // config plugin - vp := &config.Plugin{ - Version: "v2024.1.0", - Path: "configs/.rr-tcp.yaml", - } - - err := cont.RegisterAll( - vp, - &server.Plugin{}, - &Foo5{}, - &logger.Plugin{}, - ) - require.NoError(t, err) - - err = cont.Init() - require.NoError(t, err) - - ch, err := cont.Serve() - require.NoError(t, err) - - // stop by CTRL+C - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - - wg := &sync.WaitGroup{} - - stopCh := make(chan struct{}, 1) - - wg.Go(func() { - for { - select { - case e := <-ch: - assert.Fail(t, "error", e.Error.Error()) - case <-sig: - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - case <-stopCh: - // timeout - err = cont.Stop() - if err != nil { - assert.FailNow(t, "error", err.Error()) - } - return - } - } - }) - - stopCh <- struct{}{} - wg.Wait() -}