From 167caf13ff7be1dd389376e6369c8b6609de6718 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Fri, 24 Jan 2025 10:26:48 +0200 Subject: [PATCH 01/15] feat(router): enable starting the router without subgraphs --- router-tests/integration_test.go | 13 ++++++ .../testenv/testdata/configWithStatic.json | 35 +++++++++++++++ router-tests/testenv/testenv.go | 2 + router/pkg/routerconfig/cdn/client.go | 45 ++++++++++++------- router/pkg/routerconfig/client.go | 40 +++++++++++++++++ router/pkg/routerconfig/s3/client.go | 16 +++++-- 6 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 router-tests/testenv/testdata/configWithStatic.json diff --git a/router-tests/integration_test.go b/router-tests/integration_test.go index fbc1e83f4e..e103379e35 100644 --- a/router-tests/integration_test.go +++ b/router-tests/integration_test.go @@ -49,6 +49,19 @@ func TestSimpleQuery(t *testing.T) { }) } +func TestNoSubgraphConfig(t *testing.T) { + t.Parallel() + + testenv.Run(t, &testenv.Config{ + RouterConfigJSONTemplate: testenv.ConfigWithStaticJSONTemplate, + }, func(t *testing.T, xEnv *testenv.Environment) { + res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ + Query: `query { hello }`, + }) + require.JSONEq(t, `{"data":{"hello":"world"}}`, res.Body) + }) +} + func TestContentTypes(t *testing.T) { t.Parallel() diff --git a/router-tests/testenv/testdata/configWithStatic.json b/router-tests/testenv/testdata/configWithStatic.json new file mode 100644 index 0000000000..e9e57e950c --- /dev/null +++ b/router-tests/testenv/testdata/configWithStatic.json @@ -0,0 +1,35 @@ +{ + "engineConfig": { + "defaultFlushInterval": "500", + "datasourceConfigurations": [ + { + "kind": "STATIC", + "rootNodes": [ + { + "typeName": "Query", + "fieldNames": [ + "hello" + ] + } + ], + "customStatic": { + "data": { + "kind": "STATIC_CONFIGURATION_VARIABLE", + "staticVariableContent": "{\"hello\": \"world\"}" + } + }, + "id": "0" + } + ], + "graphqlSchema": "schema {\n query: Query\n}\ntype Query {\n hello: String\n}", + "fieldConfigurations": [ + { + "typeName": "Query", + "fieldName": "hello" + } + ] + }, + "version": "1a7c0b1a-839c-4b6f-9d05-7cb728168f57", + "subgraphs": [ + ] +} \ No newline at end of file diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 011095b77a..2a6b0b43c1 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -73,6 +73,8 @@ const ( var ( //go:embed testdata/config.json ConfigJSONTemplate string + //go:embed testdata/configWithStatic.json + ConfigWithStaticJSONTemplate string //go:embed testdata/configWithEdfs.json ConfigWithEdfsJSONTemplate string //go:embed testdata/configWithEdfsKafka.json diff --git a/router/pkg/routerconfig/cdn/client.go b/router/pkg/routerconfig/cdn/client.go index 8666d81a70..b4c9571cf3 100644 --- a/router/pkg/routerconfig/cdn/client.go +++ b/router/pkg/routerconfig/cdn/client.go @@ -30,8 +30,9 @@ const ( ) var ( - ErrMissingSignatureHeader = errors.New("signature header not found in CDN response") - ErrInvalidSignature = errors.New("invalid config signature, potential tampering detected") + ErrMissingSignatureHeader = errors.New("signature header not found in CDN response") + ErrInvalidSignature = errors.New("invalid config signature, potential tampering detected") + ErrConfigNotFound error = &routerConfigNotFoundError{} ) type Options struct { @@ -108,8 +109,7 @@ func NewClient(endpoint string, token string, opts *Options) (routerconfig.Clien return c, nil } -func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { - +func (cdn *Client) getRouterConfig(ctx context.Context, version string, modifiedSince time.Time) ([]byte, error) { routerConfigPath := fmt.Sprintf("/%s/%s/routerconfigs/latest.json", cdn.organizationID, cdn.federatedGraphID, @@ -140,9 +140,7 @@ func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSin if resp.StatusCode != http.StatusOK { if resp.StatusCode == http.StatusNotFound { - return nil, &routerConfigNotFoundError{ - federatedGraphId: cdn.federatedGraphID, - } + return nil, ErrConfigNotFound } if resp.StatusCode == http.StatusUnauthorized { return nil, errors.New("could not authenticate against CDN") @@ -177,21 +175,11 @@ func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSin return nil, errors.New("empty response body") } - /* - * Serialize the response body to a RouterConfig object - */ - - routerConfig, err := execution_config.UnmarshalConfig(body) - if err != nil { - return nil, fmt.Errorf("could not unmarshal router external router config from CDN: %w", err) - } - /** * If a signature key is set, we need to validate the signature of the received config */ if cdn.hash != nil { - configSignature := resp.Header.Get(sigResponseHeaderName) if configSignature == "" { cdn.logger.Error( @@ -228,6 +216,29 @@ func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSin ) } + /* + * Serialize the response body to a RouterConfig object + */ + return body, nil +} + +func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { + body, err := cdn.getRouterConfig(ctx, version, modifiedSince) + if err != nil && errors.Is(err, ErrConfigNotFound) { + body = routerconfig.GetDefaultConfig() + } else if err != nil { + return nil, err + } + + /* + * Serialize the response body to a RouterConfig object + */ + + routerConfig, err := execution_config.UnmarshalConfig(body) + if err != nil { + return nil, fmt.Errorf("could not unmarshal router external router config from CDN: %w", err) + } + res := &routerconfig.Response{} res.Config = routerConfig diff --git a/router/pkg/routerconfig/client.go b/router/pkg/routerconfig/client.go index be74d777d9..82b325c125 100644 --- a/router/pkg/routerconfig/client.go +++ b/router/pkg/routerconfig/client.go @@ -21,3 +21,43 @@ type ConfigNotFoundError interface { error FederatedGraphId() string } + +func GetDefaultConfig() []byte { + return []byte(` +{ + "engineConfig": { + "defaultFlushInterval": "500", + "datasourceConfigurations": [ + { + "kind": "STATIC", + "rootNodes": [ + { + "typeName": "Query", + "fieldNames": [ + "hello" + ] + } + ], + "customStatic": { + "data": { + "kind": "STATIC_CONFIGURATION_VARIABLE", + "staticVariableContent": "{\"hello\": \"world\"}" + } + }, + "id": "0" + } + ], + "graphqlSchema": "schema {\n query: Query\n}\ntype Query {\n hello: String\n}", + "fieldConfigurations": [ + { + "typeName": "Query", + "fieldName": "hello" + } + ] + }, + "version": "1a7c0b1a-839c-4b6f-9d05-7cb728168f57", + "subgraphs": [ + ] +} +`) +} diff --git a/router/pkg/routerconfig/s3/client.go b/router/pkg/routerconfig/s3/client.go index 1099667786..aa8d2dd60b 100644 --- a/router/pkg/routerconfig/s3/client.go +++ b/router/pkg/routerconfig/s3/client.go @@ -5,6 +5,7 @@ import ( "errors" "io" "net/http" + "strings" "time" "github.com/minio/minio-go/v7" @@ -66,8 +67,7 @@ func NewClient(endpoint string, options *ClientOptions) (routerconfig.Client, er return client, nil } -func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { - +func (c Client) getConfigFile(ctx context.Context, version string, modifiedSince time.Time) ([]byte, error) { options := minio.GetObjectOptions{} if !modifiedSince.IsZero() { @@ -89,13 +89,21 @@ func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince } defer reader.Close() - body, err := io.ReadAll(reader) + return io.ReadAll(reader) +} + +func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { + body, err := c.getConfigFile(ctx, version, modifiedSince) if err != nil { var minioErr minio.ErrorResponse if errors.As(err, &minioErr) && minioErr.StatusCode == http.StatusNotModified { return nil, configpoller.ErrConfigNotModified + } else if !strings.Contains(err.Error(), "NoSuchKey") { + return nil, err } - return nil, err + + // If the config file is not found, we return a starting config. + body = routerconfig.GetDefaultConfig() } routerConfig, err := execution_config.UnmarshalConfig(body) From 735e1a0482b7864786205d9c54a092f231e7b866 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Fri, 24 Jan 2025 10:30:20 +0200 Subject: [PATCH 02/15] fix --- router/core/router.go | 1 + router/pkg/routerconfig/cdn/client.go | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/router/core/router.go b/router/core/router.go index 4971893f05..28c2869593 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -730,6 +730,7 @@ func (r *Router) NewServer(ctx context.Context) (Server, error) { cfg, err := r.configPoller.GetRouterConfig(ctx) if err != nil { + return nil, fmt.Errorf("failed to get initial execution config: %w", err) } diff --git a/router/pkg/routerconfig/cdn/client.go b/router/pkg/routerconfig/cdn/client.go index b4c9571cf3..fc2dc1ac6b 100644 --- a/router/pkg/routerconfig/cdn/client.go +++ b/router/pkg/routerconfig/cdn/client.go @@ -216,9 +216,6 @@ func (cdn *Client) getRouterConfig(ctx context.Context, version string, modified ) } - /* - * Serialize the response body to a RouterConfig object - */ return body, nil } From ffa7caad097c7d80036181bd8f95ba4cd033b01d Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Sun, 26 Jan 2025 21:40:57 +0200 Subject: [PATCH 03/15] fix: address comments --- router/pkg/routerconfig/cdn/client.go | 13 +++-- router/pkg/routerconfig/client.go | 71 +++++++++++++-------------- router/pkg/routerconfig/s3/client.go | 17 +++---- 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/router/pkg/routerconfig/cdn/client.go b/router/pkg/routerconfig/cdn/client.go index fc2dc1ac6b..3af3676085 100644 --- a/router/pkg/routerconfig/cdn/client.go +++ b/router/pkg/routerconfig/cdn/client.go @@ -220,24 +220,23 @@ func (cdn *Client) getRouterConfig(ctx context.Context, version string, modified } func (cdn *Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { + res := &routerconfig.Response{} + body, err := cdn.getRouterConfig(ctx, version, modifiedSince) if err != nil && errors.Is(err, ErrConfigNotFound) { - body = routerconfig.GetDefaultConfig() + res.Config = routerconfig.GetDefaultConfig() + return res, nil } else if err != nil { return nil, err } /* - * Serialize the response body to a RouterConfig object + * Unmarshal the response body to a RouterConfig object */ - routerConfig, err := execution_config.UnmarshalConfig(body) + res.Config, err = execution_config.UnmarshalConfig(body) if err != nil { return nil, fmt.Errorf("could not unmarshal router external router config from CDN: %w", err) } - - res := &routerconfig.Response{} - res.Config = routerConfig - return res, nil } diff --git a/router/pkg/routerconfig/client.go b/router/pkg/routerconfig/client.go index 82b325c125..6ab2464fa4 100644 --- a/router/pkg/routerconfig/client.go +++ b/router/pkg/routerconfig/client.go @@ -2,6 +2,7 @@ package routerconfig import ( "context" + "fmt" nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" "time" ) @@ -22,42 +23,36 @@ type ConfigNotFoundError interface { FederatedGraphId() string } -func GetDefaultConfig() []byte { - return []byte(` -{ - "engineConfig": { - "defaultFlushInterval": "500", - "datasourceConfigurations": [ - { - "kind": "STATIC", - "rootNodes": [ - { - "typeName": "Query", - "fieldNames": [ - "hello" - ] - } - ], - "customStatic": { - "data": { - "kind": "STATIC_CONFIGURATION_VARIABLE", - "staticVariableContent": "{\"hello\": \"world\"}" - } - }, - "id": "0" - } - ], - "graphqlSchema": "schema {\n query: Query\n}\ntype Query {\n hello: String\n}", - "fieldConfigurations": [ - { - "typeName": "Query", - "fieldName": "hello" - } - ] - }, - "version": "1a7c0b1a-839c-4b6f-9d05-7cb728168f57", - "subgraphs": [ - ] -} -`) +func GetDefaultConfig() *nodev1.RouterConfig { + msg := `Cosmo Router is ready! Follow this guide to deploy your first Supergraph: https://cosmo-docs.wundergraph.com/tutorial/from-zero-to-federation-in-5-steps-using-cosmo` + return &nodev1.RouterConfig{ + Version: "1a7c0b1a-839c-4b6f-9d05-7cb728168f57", + EngineConfig: &nodev1.EngineConfiguration{ + DefaultFlushInterval: 500, + DatasourceConfigurations: []*nodev1.DataSourceConfiguration{ + &nodev1.DataSourceConfiguration{ + Kind: nodev1.DataSourceKind_STATIC, + RootNodes: []*nodev1.TypeField{ + { + TypeName: "Query", + FieldNames: []string{"hello"}, + }, + }, + CustomStatic: &nodev1.DataSourceCustom_Static{ + Data: &nodev1.ConfigurationVariable{ + StaticVariableContent: fmt.Sprintf(`{"hello": "%s"}`, msg), + }, + }, + Id: "0", + }, + }, + GraphqlSchema: "schema {\n query: Query\n}\ntype Query {\n hello: String\n}", + FieldConfigurations: []*nodev1.FieldConfiguration{ + { + TypeName: "Query", + FieldName: "hello", + }, + }, + }, + } } diff --git a/router/pkg/routerconfig/s3/client.go b/router/pkg/routerconfig/s3/client.go index aa8d2dd60b..8df51dd7e0 100644 --- a/router/pkg/routerconfig/s3/client.go +++ b/router/pkg/routerconfig/s3/client.go @@ -93,6 +93,8 @@ func (c Client) getConfigFile(ctx context.Context, version string, modifiedSince } func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince time.Time) (*routerconfig.Response, error) { + res := &routerconfig.Response{} + body, err := c.getConfigFile(ctx, version, modifiedSince) if err != nil { var minioErr minio.ErrorResponse @@ -102,17 +104,10 @@ func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince return nil, err } - // If the config file is not found, we return a starting config. - body = routerconfig.GetDefaultConfig() - } - - routerConfig, err := execution_config.UnmarshalConfig(body) - if err != nil { - return nil, err + res.Config = routerconfig.GetDefaultConfig() + return res, nil } - result := &routerconfig.Response{} - result.Config = routerConfig - - return result, nil + res.Config, err = execution_config.UnmarshalConfig(body) + return res, err } From ed4ae476b54db1e140d0af8f8f1e99c2e8619273 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Mon, 27 Jan 2025 15:07:14 +0200 Subject: [PATCH 04/15] fix: simplify s3 error handling with no config --- router/pkg/routerconfig/s3/client.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/router/pkg/routerconfig/s3/client.go b/router/pkg/routerconfig/s3/client.go index 8df51dd7e0..e063490d49 100644 --- a/router/pkg/routerconfig/s3/client.go +++ b/router/pkg/routerconfig/s3/client.go @@ -5,7 +5,6 @@ import ( "errors" "io" "net/http" - "strings" "time" "github.com/minio/minio-go/v7" @@ -98,14 +97,16 @@ func (c Client) RouterConfig(ctx context.Context, version string, modifiedSince body, err := c.getConfigFile(ctx, version, modifiedSince) if err != nil { var minioErr minio.ErrorResponse - if errors.As(err, &minioErr) && minioErr.StatusCode == http.StatusNotModified { - return nil, configpoller.ErrConfigNotModified - } else if !strings.Contains(err.Error(), "NoSuchKey") { - return nil, err + if errors.As(err, &minioErr) { + if minioErr.StatusCode == http.StatusNotModified { + return nil, configpoller.ErrConfigNotModified + } else if minioErr.Code == "NoSuchKey" { + res.Config = routerconfig.GetDefaultConfig() + return res, nil + } } - res.Config = routerconfig.GetDefaultConfig() - return res, nil + return nil, err } res.Config, err = execution_config.UnmarshalConfig(body) From 31f11df723f1f93c6e3a08d0a5dac6b35d9637d4 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Mon, 27 Jan 2025 19:26:19 +0200 Subject: [PATCH 05/15] test built in config by running the router --- router-tests/fuzzquery/fuzzquery_test.go | 13 +-- router-tests/go.mod | 8 ++ router-tests/go.sum | 21 ++++ router-tests/integration_test.go | 6 +- .../testenv/testdata/configWithStatic.json | 35 ------ router-tests/testenv/testenv.go | 107 ++++++++++++++++-- 6 files changed, 132 insertions(+), 58 deletions(-) delete mode 100644 router-tests/testenv/testdata/configWithStatic.json diff --git a/router-tests/fuzzquery/fuzzquery_test.go b/router-tests/fuzzquery/fuzzquery_test.go index 81d9231820..3988477148 100644 --- a/router-tests/fuzzquery/fuzzquery_test.go +++ b/router-tests/fuzzquery/fuzzquery_test.go @@ -5,21 +5,10 @@ import ( "fmt" "github.com/stretchr/testify/require" "github.com/wundergraph/cosmo/router-tests/testenv" - "math/rand" "net/http" "testing" ) -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -func randString(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = letterBytes[rand.Intn(len(letterBytes))] - } - return string(b) -} - type testQuery struct { Name string Body string @@ -29,7 +18,7 @@ type testQuery struct { func (t *testQuery) Data() []byte { name := t.Name if name == "" { - name = randString(10) + name = testenv.RandString(10) } values := map[string]interface{}{ "query": fmt.Sprintf("query %s %s", name, t.Body), diff --git a/router-tests/go.mod b/router-tests/go.mod index 0ed2233e88..cca89351fb 100644 --- a/router-tests/go.mod +++ b/router-tests/go.mod @@ -43,6 +43,7 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/99designs/gqlgen v0.17.63 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/KimMachineGun/automemlimit v0.6.1 // indirect github.com/MicahParks/keyfunc/v3 v3.3.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect @@ -54,9 +55,12 @@ require ( github.com/caarlos0/env/v11 v11.1.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cilium/ebpf v0.9.1 // indirect github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a // indirect + github.com/containerd/cgroups/v3 v3.0.2 // indirect github.com/containerd/containerd v1.7.12 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -84,6 +88,7 @@ require ( github.com/gobwas/ws v1.4.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.13.4 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect @@ -116,6 +121,8 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -156,6 +163,7 @@ require ( go.opentelemetry.io/otel/exporters/prometheus v0.50.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.1 // indirect go.withmatt.com/connect-brotli v0.4.0 // indirect diff --git a/router-tests/go.sum b/router-tests/go.sum index 2bdc071b5e..7a9b45c96d 100644 --- a/router-tests/go.sum +++ b/router-tests/go.sum @@ -11,6 +11,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg6 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= +github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8= +github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw= github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY= github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo= @@ -49,14 +51,20 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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/cilium/ebpf v0.9.1 h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4= +github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a h1:8d1CEOF1xldesKds5tRG3tExBsMOgWYownMHNCsev54= github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY= github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= +github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -101,6 +109,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -136,6 +146,9 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.13.4 h1:XOnLX9GqT+kH/gB7YzCMUiDBFU9B7pm3HZz6kyeDPkk= github.com/goccy/go-yaml v1.13.4/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -262,6 +275,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d h1:U+PMnTlV2tu7RuMK5etusZG3Cf+rpow5hqQByeCzJ2g= github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d/go.mod h1:lXfE4PvvTW5xOjO6Mba8zDPyw8M93B6AQ7frTGnMlA8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -276,6 +293,8 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k= github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= @@ -404,6 +423,8 @@ go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7e go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= 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/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= diff --git a/router-tests/integration_test.go b/router-tests/integration_test.go index e103379e35..c85d7c40db 100644 --- a/router-tests/integration_test.go +++ b/router-tests/integration_test.go @@ -52,13 +52,11 @@ func TestSimpleQuery(t *testing.T) { func TestNoSubgraphConfig(t *testing.T) { t.Parallel() - testenv.Run(t, &testenv.Config{ - RouterConfigJSONTemplate: testenv.ConfigWithStaticJSONTemplate, - }, func(t *testing.T, xEnv *testenv.Environment) { + testenv.RunBasicRouter(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { hello }`, }) - require.JSONEq(t, `{"data":{"hello":"world"}}`, res.Body) + require.JSONEq(t, `{"data":{"hello":"Cosmo Router is ready! Follow this guide to deploy your first Supergraph: https://cosmo-docs.wundergraph.com/tutorial/from-zero-to-federation-in-5-steps-using-cosmo"}}`, res.Body) }) } diff --git a/router-tests/testenv/testdata/configWithStatic.json b/router-tests/testenv/testdata/configWithStatic.json deleted file mode 100644 index e9e57e950c..0000000000 --- a/router-tests/testenv/testdata/configWithStatic.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "engineConfig": { - "defaultFlushInterval": "500", - "datasourceConfigurations": [ - { - "kind": "STATIC", - "rootNodes": [ - { - "typeName": "Query", - "fieldNames": [ - "hello" - ] - } - ], - "customStatic": { - "data": { - "kind": "STATIC_CONFIGURATION_VARIABLE", - "staticVariableContent": "{\"hello\": \"world\"}" - } - }, - "id": "0" - } - ], - "graphqlSchema": "schema {\n query: Query\n}\ntype Query {\n hello: String\n}", - "fieldConfigurations": [ - { - "typeName": "Query", - "fieldName": "hello" - } - ] - }, - "version": "1a7c0b1a-839c-4b6f-9d05-7cb728168f57", - "subgraphs": [ - ] -} \ No newline at end of file diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 2a6b0b43c1..291e83bd97 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + routercmd "github.com/wundergraph/cosmo/router/cmd" "io" "log" "math/rand" @@ -73,8 +74,6 @@ const ( var ( //go:embed testdata/config.json ConfigJSONTemplate string - //go:embed testdata/configWithStatic.json - ConfigWithStaticJSONTemplate string //go:embed testdata/configWithEdfs.json ConfigWithEdfsJSONTemplate string //go:embed testdata/configWithEdfsKafka.json @@ -120,6 +119,21 @@ func RunWithError(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environm return nil } +// RunBasicRouter runs the test and fails the test if an error occurs +func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { + t.Helper() + env := createBasicRouter(t, cfg) + t.Cleanup(env.Shutdown) + f(t, env) + if cfg.AssertCacheMetrics != nil { + assertCacheMetrics(t, env, cfg.AssertCacheMetrics.BaseGraphAssertions, "") + + for ff, v := range cfg.AssertCacheMetrics.FeatureFlagAssertions { + assertCacheMetrics(t, env, v, ff) + } + } +} + func Bench(b *testing.B, cfg *Config, f func(b *testing.B, xEnv *Environment)) { b.Helper() b.StopTimer() @@ -140,6 +154,16 @@ func Bench(b *testing.B, cfg *Config, f func(b *testing.B, xEnv *Environment)) { } +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +func RandString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + func assertCacheMetrics(t testing.TB, env *Environment, expected CacheMetricsAssertion, featureFlag string) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) @@ -317,6 +341,71 @@ type LogObservationConfig struct { LogLevel zapcore.Level } +func createBasicRouter(t testing.TB, cfg *Config) *Environment { + t.Helper() + + port := freeport.GetOne(t) + listenerAddr := fmt.Sprintf("localhost:%d", port) + + token, err := GenerateJwtToken() + require.NoError(t, err) + vals := "" + for key, val := range map[string]string{ + "GRAPH_API_TOKEN": token, + "LISTEN_ADDR": listenerAddr, + } { + vals += fmt.Sprintf("%s=%s\n", key, val) + } + envFile := filepath.Join(t.TempDir(), RandString(6)+".env") + require.NoError(t, os.WriteFile(envFile, []byte(vals), os.ModePerm)) + + res, err := config.LoadConfig("", envFile) + require.NoError(t, err) + + res.Config.Telemetry.Metrics.OTLP.Enabled = false + testCdn := SetupCDNServer(t) + + ctx, cancel := context.WithCancelCause(context.Background()) + cdnConfig := config.CDNConfiguration{ + URL: testCdn.URL, + CacheSize: 1024 * 1024, + } + + router, err := routercmd.NewRouter(ctx, routercmd.Params{ + Config: &res.Config, + Logger: newTestLogger(), + }, core.WithCDN(cdnConfig)) + require.NoError(t, err) + require.NoError(t, router.Start(ctx)) + + e := &Environment{ + t: t, + graphQLPath: "/graphql", + cfg: cfg, + routerConfigVersionMain: res.Config.Version, + Context: ctx, + cancel: cancel, + Router: router, + RouterURL: router.BaseURL(), + RouterClient: http.DefaultClient, + CDN: testCdn, + shutdown: atomic.NewBool(false), + } + + require.NoError(t, e.WaitForServer(ctx, e.RouterURL+"/health/ready", 100, 10)) + + return e +} + +func newTestLogger() *zap.Logger { + ec := zap.NewProductionEncoderConfig() + ec.EncodeDuration = zapcore.SecondsDurationEncoder + ec.TimeKey = "time" + + syncer := zapcore.AddSync(os.Stderr) + return logging.NewZapLogger(syncer, false, true, zapcore.ErrorLevel) +} + func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { t.Helper() @@ -537,7 +626,7 @@ func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { cfg.ModifyRouterConfig(&routerConfig) } - cdn := setupCDNServer(t) + cdn := SetupCDNServer(t) if cfg.PrometheusRegistry != nil { cfg.PrometheusPort = ports[0] @@ -718,6 +807,12 @@ func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { return e, waitErr } +func GenerateJwtToken() (string, error) { + jwtToken := jwt.New(jwt.SigningMethodHS256) + jwtToken.Claims = testTokenClaims() + return jwtToken.SignedString([]byte("hunter2")) +} + func configureRouter(listenerAddr string, testConfig *Config, routerConfig *nodev1.RouterConfig, cdn *httptest.Server, natsData *NatsData) (*core.Router, error) { cfg := config.Config{ Graph: config.Graph{}, @@ -742,9 +837,7 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node testConfig.ModifyCDNConfig(&cfg.CDN) } - jwtToken := jwt.New(jwt.SigningMethodHS256) - jwtToken.Claims = testTokenClaims() - graphApiToken, err := jwtToken.SignedString([]byte("hunter2")) + graphApiToken, err := GenerateJwtToken() if err != nil { return nil, err } @@ -995,7 +1088,7 @@ func makeSafeHttpTestServer(t testing.TB, handler http.Handler) *httptest.Server return s } -func setupCDNServer(t testing.TB) *httptest.Server { +func SetupCDNServer(t testing.TB) *httptest.Server { _, filePath, _, ok := runtime.Caller(0) require.True(t, ok) baseCdnFile := filepath.Join(path.Dir(filePath), "testdata", "cdn") From 5f51c5ee55e19e3ffa5cbd3fbec03b1681e87b7e Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Mon, 27 Jan 2025 19:43:08 +0200 Subject: [PATCH 06/15] try to address race --- router-tests/testenv/testenv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 291e83bd97..720519d70a 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -122,7 +122,7 @@ func RunWithError(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environm // RunBasicRouter runs the test and fails the test if an error occurs func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { t.Helper() - env := createBasicRouter(t, cfg) + env := createRouterWithDefaultConfig(t, cfg) t.Cleanup(env.Shutdown) f(t, env) if cfg.AssertCacheMetrics != nil { @@ -341,7 +341,7 @@ type LogObservationConfig struct { LogLevel zapcore.Level } -func createBasicRouter(t testing.TB, cfg *Config) *Environment { +func createRouterWithDefaultConfig(t testing.TB, cfg *Config) *Environment { t.Helper() port := freeport.GetOne(t) @@ -371,7 +371,7 @@ func createBasicRouter(t testing.TB, cfg *Config) *Environment { CacheSize: 1024 * 1024, } - router, err := routercmd.NewRouter(ctx, routercmd.Params{ + router, err := routercmd.NewRouter(context.Background(), routercmd.Params{ Config: &res.Config, Logger: newTestLogger(), }, core.WithCDN(cdnConfig)) From 2d9c9e4dde151c0c5dd656d0ef3dc1598f383169 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Mon, 27 Jan 2025 21:11:19 +0200 Subject: [PATCH 07/15] fix: add shutdown delay for race issue --- router-tests/testenv/testenv.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 720519d70a..ed64107159 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -378,6 +378,10 @@ func createRouterWithDefaultConfig(t testing.TB, cfg *Config) *Environment { require.NoError(t, err) require.NoError(t, router.Start(ctx)) + if cfg.ShutdownDelay == 0 { + cfg.ShutdownDelay = 30 * time.Second + } + e := &Environment{ t: t, graphQLPath: "/graphql", @@ -390,6 +394,7 @@ func createRouterWithDefaultConfig(t testing.TB, cfg *Config) *Environment { RouterClient: http.DefaultClient, CDN: testCdn, shutdown: atomic.NewBool(false), + shutdownDelay: cfg.ShutdownDelay, } require.NoError(t, e.WaitForServer(ctx, e.RouterURL+"/health/ready", 100, 10)) From 2b3de226edf41afc64a1400b053c202b6cee8294 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 00:06:18 +0200 Subject: [PATCH 08/15] feat: add e2e tests using cmd --- demo/go.mod | 2 - router-tests/testenv/testenv.go | 94 ++------------------- router-tests/testenv/testexec.go | 135 +++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 91 deletions(-) create mode 100644 router-tests/testenv/testexec.go diff --git a/demo/go.mod b/demo/go.mod index c4050d3f5a..119dc84fde 100644 --- a/demo/go.mod +++ b/demo/go.mod @@ -2,8 +2,6 @@ module github.com/wundergraph/cosmo/demo go 1.23 -toolchain go1.23.4 - require ( github.com/99designs/gqlgen v0.17.63 github.com/google/uuid v1.6.0 diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index ed64107159..95443a95f2 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "fmt" - routercmd "github.com/wundergraph/cosmo/router/cmd" "io" "log" "math/rand" @@ -119,21 +118,6 @@ func RunWithError(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environm return nil } -// RunBasicRouter runs the test and fails the test if an error occurs -func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { - t.Helper() - env := createRouterWithDefaultConfig(t, cfg) - t.Cleanup(env.Shutdown) - f(t, env) - if cfg.AssertCacheMetrics != nil { - assertCacheMetrics(t, env, cfg.AssertCacheMetrics.BaseGraphAssertions, "") - - for ff, v := range cfg.AssertCacheMetrics.FeatureFlagAssertions { - assertCacheMetrics(t, env, v, ff) - } - } -} - func Bench(b *testing.B, cfg *Config, f func(b *testing.B, xEnv *Environment)) { b.Helper() b.StopTimer() @@ -341,76 +325,6 @@ type LogObservationConfig struct { LogLevel zapcore.Level } -func createRouterWithDefaultConfig(t testing.TB, cfg *Config) *Environment { - t.Helper() - - port := freeport.GetOne(t) - listenerAddr := fmt.Sprintf("localhost:%d", port) - - token, err := GenerateJwtToken() - require.NoError(t, err) - vals := "" - for key, val := range map[string]string{ - "GRAPH_API_TOKEN": token, - "LISTEN_ADDR": listenerAddr, - } { - vals += fmt.Sprintf("%s=%s\n", key, val) - } - envFile := filepath.Join(t.TempDir(), RandString(6)+".env") - require.NoError(t, os.WriteFile(envFile, []byte(vals), os.ModePerm)) - - res, err := config.LoadConfig("", envFile) - require.NoError(t, err) - - res.Config.Telemetry.Metrics.OTLP.Enabled = false - testCdn := SetupCDNServer(t) - - ctx, cancel := context.WithCancelCause(context.Background()) - cdnConfig := config.CDNConfiguration{ - URL: testCdn.URL, - CacheSize: 1024 * 1024, - } - - router, err := routercmd.NewRouter(context.Background(), routercmd.Params{ - Config: &res.Config, - Logger: newTestLogger(), - }, core.WithCDN(cdnConfig)) - require.NoError(t, err) - require.NoError(t, router.Start(ctx)) - - if cfg.ShutdownDelay == 0 { - cfg.ShutdownDelay = 30 * time.Second - } - - e := &Environment{ - t: t, - graphQLPath: "/graphql", - cfg: cfg, - routerConfigVersionMain: res.Config.Version, - Context: ctx, - cancel: cancel, - Router: router, - RouterURL: router.BaseURL(), - RouterClient: http.DefaultClient, - CDN: testCdn, - shutdown: atomic.NewBool(false), - shutdownDelay: cfg.ShutdownDelay, - } - - require.NoError(t, e.WaitForServer(ctx, e.RouterURL+"/health/ready", 100, 10)) - - return e -} - -func newTestLogger() *zap.Logger { - ec := zap.NewProductionEncoderConfig() - ec.EncodeDuration = zapcore.SecondsDurationEncoder - ec.TimeKey = "time" - - syncer := zapcore.AddSync(os.Stderr) - return logging.NewZapLogger(syncer, false, true, zapcore.ErrorLevel) -} - func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { t.Helper() @@ -1209,9 +1123,11 @@ func (e *Environment) Shutdown() { defer cancel() // Gracefully shutdown router - err := e.Router.Shutdown(ctx) - if err != nil && !errors.Is(err, context.DeadlineExceeded) { - e.t.Errorf("could not shutdown router: %s", err) + if e.Router != nil { + err := e.Router.Shutdown(ctx) + if err != nil && !errors.Is(err, context.DeadlineExceeded) { + e.t.Errorf("could not shutdown router: %s", err) + } } // Close all test servers diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go new file mode 100644 index 0000000000..af2d1b6cdf --- /dev/null +++ b/router-tests/testenv/testexec.go @@ -0,0 +1,135 @@ +package testenv + +import ( + "bufio" + "context" + "fmt" + "github.com/hashicorp/consul/sdk/freeport" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" +) + +const routerDir = "../router" + +// RunBasicRouter starts the router binary, sets up the test environment, and runs the provided test function. +func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { + t.Helper() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + buildRouter(t, ctx) + env := RunRouter(t, ctx, cfg, routerDir) + t.Cleanup(env.Shutdown) + + // Execute the test case with the environment + f(t, env) +} + +// BuildRouter runs `make build` inside the router directory and fails the test on error. +func buildRouter(t *testing.T, ctx context.Context) { + t.Helper() + + cmd := exec.Command("make", "build") + cmd.Dir = routerDir + runCmdWithLogs(t, ctx, cmd, true) +} + +// RunRouter starts the router binary and returns an Environment. +func RunRouter(t *testing.T, ctx context.Context, cfg *Config, routerDir string) *Environment { + t.Helper() + + binaryPath := filepath.Join(routerDir, "router") + fullBinPath, err := filepath.Abs(binaryPath) + require.NoError(t, err) + + port := freeport.GetOne(t) + listenerAddr := fmt.Sprintf("localhost:%d", port) + token, err := GenerateJwtToken() + require.NoError(t, err) + testCdn := SetupCDNServer(t) + vals := "" + + for key, val := range map[string]string{ + "GRAPH_API_TOKEN": token, + "LISTEN_ADDR": listenerAddr, + "CDN_URL": testCdn.URL, + "METRICS_OTLP_ENABLED": "false", + "RETRY_ENABLED": "false", + "CDN_CACHE_SIZE": fmt.Sprintf("%d", 1024*1024), + } { + vals += fmt.Sprintf("\n%s=%s", key, val) + } + envFile := filepath.Join(os.TempDir(), RandString(6)+".env") + require.NoError(t, os.WriteFile(envFile, []byte(strings.TrimSpace(vals)), os.ModePerm)) + + cmd := exec.Command(fullBinPath, "--override-env", envFile) + cmd.Dir = t.TempDir() + newCtx, cancel := context.WithCancelCause(context.Background()) + runCmdWithLogs(t, ctx, cmd, false) + + // Graceful shutdown on context cancel + go func() { + <-newCtx.Done() + _ = cmd.Process.Signal(os.Interrupt) + time.Sleep(2 * time.Second) + _ = cmd.Process.Kill() + }() + + // Create test environment + env := &Environment{ + t: t, + RouterURL: "http://" + listenerAddr, + graphQLPath: "/graphql", + RouterClient: http.DefaultClient, + cfg: cfg, + CDN: testCdn, + Context: newCtx, + cancel: cancel, + shutdown: atomic.NewBool(false), + shutdownDelay: 30 * time.Second, + } + + // Wait for server readiness + require.NoError(t, env.WaitForServer(ctx, env.RouterURL+"/health/ready", 600, 60)) + + return env +} + +func runCmdWithLogs(t *testing.T, ctx context.Context, cmd *exec.Cmd, waitToComplete bool) { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + r, w := io.Pipe() + cmd.Stdout = w + cmd.Stderr = w + + // Capture output in real-time + go func() { + scanner := bufio.NewScanner(r) + for scanner.Scan() { + select { + case <-ctx.Done(): // Stop logging after test exits + return + default: + t.Log(scanner.Text()) // Log each line from command output + } + } + if err := scanner.Err(); err != nil { + t.Logf("error reading output: %v", err) + } + }() + + if waitToComplete { + require.NoError(t, cmd.Run(), "Failed to start router") + } else { + require.NoError(t, cmd.Start(), "Failed to start router") + } +} From e1e9a6ce7271d0f8811cbfc5a652d3b07692cfa9 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 10:08:13 +0200 Subject: [PATCH 09/15] refactor and cleanup --- router-tests/integration_test.go | 2 +- router-tests/testenv/testenv.go | 16 ++++++++++++---- router-tests/testenv/testexec.go | 19 ++++++++++--------- router/core/router.go | 1 - 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/router-tests/integration_test.go b/router-tests/integration_test.go index c85d7c40db..3c60b89474 100644 --- a/router-tests/integration_test.go +++ b/router-tests/integration_test.go @@ -52,7 +52,7 @@ func TestSimpleQuery(t *testing.T) { func TestNoSubgraphConfig(t *testing.T) { t.Parallel() - testenv.RunBasicRouter(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { + testenv.RunRouterBinary(t, &testenv.Config{}, func(t *testing.T, xEnv *testenv.Environment) { res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{ Query: `query { hello }`, }) diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 95443a95f2..4bdd56f6e9 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -18,6 +18,7 @@ import ( "net/http/httptest" "net/url" "os" + "os/exec" "path" "path/filepath" "regexp" @@ -545,7 +546,7 @@ func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { cfg.ModifyRouterConfig(&routerConfig) } - cdn := SetupCDNServer(t) + cdn := setupCDNServer(t) if cfg.PrometheusRegistry != nil { cfg.PrometheusPort = ports[0] @@ -726,7 +727,7 @@ func createTestEnv(t testing.TB, cfg *Config) (*Environment, error) { return e, waitErr } -func GenerateJwtToken() (string, error) { +func generateJwtToken() (string, error) { jwtToken := jwt.New(jwt.SigningMethodHS256) jwtToken.Claims = testTokenClaims() return jwtToken.SignedString([]byte("hunter2")) @@ -756,7 +757,7 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node testConfig.ModifyCDNConfig(&cfg.CDN) } - graphApiToken, err := GenerateJwtToken() + graphApiToken, err := generateJwtToken() if err != nil { return nil, err } @@ -1007,7 +1008,7 @@ func makeSafeHttpTestServer(t testing.TB, handler http.Handler) *httptest.Server return s } -func SetupCDNServer(t testing.TB) *httptest.Server { +func setupCDNServer(t testing.TB) *httptest.Server { _, filePath, _, ok := runtime.Caller(0) require.True(t, ok) baseCdnFile := filepath.Join(path.Dir(filePath), "testdata", "cdn") @@ -1076,6 +1077,7 @@ type Environment struct { routerConfigVersionMyFF string metricReader metric.Reader + routerCmd *exec.Cmd } func GetPubSubNameFn(prefix string) func(name string) string { @@ -1168,6 +1170,12 @@ func (e *Environment) Shutdown() { if e.cfg.EnableKafka && e.KafkaClient != nil { e.KafkaClient.Flush(ctx) } + + if e.routerCmd != nil { + if err := e.routerCmd.Process.Kill(); err != nil { + e.t.Logf("could not kill router process: %s", err) + } + } } type SubgraphRequestCount struct { diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go index af2d1b6cdf..0b378158fe 100644 --- a/router-tests/testenv/testexec.go +++ b/router-tests/testenv/testexec.go @@ -19,15 +19,15 @@ import ( const routerDir = "../router" -// RunBasicRouter starts the router binary, sets up the test environment, and runs the provided test function. -func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { +// RunRouterBinary starts the router binary, sets up the test environment, and runs the provided test function. +func RunRouterBinary(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { t.Helper() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - buildRouter(t, ctx) - env := RunRouter(t, ctx, cfg, routerDir) + buildRouterBin(t, ctx) + env := runRouterBin(t, ctx, cfg, routerDir) t.Cleanup(env.Shutdown) // Execute the test case with the environment @@ -35,7 +35,7 @@ func RunBasicRouter(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Enviro } // BuildRouter runs `make build` inside the router directory and fails the test on error. -func buildRouter(t *testing.T, ctx context.Context) { +func buildRouterBin(t *testing.T, ctx context.Context) { t.Helper() cmd := exec.Command("make", "build") @@ -43,8 +43,8 @@ func buildRouter(t *testing.T, ctx context.Context) { runCmdWithLogs(t, ctx, cmd, true) } -// RunRouter starts the router binary and returns an Environment. -func RunRouter(t *testing.T, ctx context.Context, cfg *Config, routerDir string) *Environment { +// runRouterBin starts the router binary and returns an Environment. +func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, routerDir string) *Environment { t.Helper() binaryPath := filepath.Join(routerDir, "router") @@ -53,9 +53,9 @@ func RunRouter(t *testing.T, ctx context.Context, cfg *Config, routerDir string) port := freeport.GetOne(t) listenerAddr := fmt.Sprintf("localhost:%d", port) - token, err := GenerateJwtToken() + token, err := generateJwtToken() require.NoError(t, err) - testCdn := SetupCDNServer(t) + testCdn := setupCDNServer(t) vals := "" for key, val := range map[string]string{ @@ -96,6 +96,7 @@ func RunRouter(t *testing.T, ctx context.Context, cfg *Config, routerDir string) cancel: cancel, shutdown: atomic.NewBool(false), shutdownDelay: 30 * time.Second, + routerCmd: cmd, } // Wait for server readiness diff --git a/router/core/router.go b/router/core/router.go index 28c2869593..4971893f05 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -730,7 +730,6 @@ func (r *Router) NewServer(ctx context.Context) (Server, error) { cfg, err := r.configPoller.GetRouterConfig(ctx) if err != nil { - return nil, fmt.Errorf("failed to get initial execution config: %w", err) } From 6addbd38fcbd5799a85bb1e2549ef60ca14c644f Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 13:19:39 +0200 Subject: [PATCH 10/15] build binary once --- router-tests/testenv/testexec.go | 37 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go index 0b378158fe..9bfb5eafd0 100644 --- a/router-tests/testenv/testexec.go +++ b/router-tests/testenv/testexec.go @@ -13,12 +13,18 @@ import ( "os/exec" "path/filepath" "strings" + "sync" "testing" "time" ) const routerDir = "../router" +var ( + buildOnce sync.Once + routerBin string +) + // RunRouterBinary starts the router binary, sets up the test environment, and runs the provided test function. func RunRouterBinary(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Environment)) { t.Helper() @@ -26,8 +32,8 @@ func RunRouterBinary(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Envir ctx, cancel := context.WithCancel(context.Background()) defer cancel() - buildRouterBin(t, ctx) - env := runRouterBin(t, ctx, cfg, routerDir) + routerPath := getRouterBinary(t, ctx) + env := runRouterBin(t, ctx, cfg, routerPath) t.Cleanup(env.Shutdown) // Execute the test case with the environment @@ -38,16 +44,33 @@ func RunRouterBinary(t *testing.T, cfg *Config, f func(t *testing.T, xEnv *Envir func buildRouterBin(t *testing.T, ctx context.Context) { t.Helper() - cmd := exec.Command("make", "build") - cmd.Dir = routerDir - runCmdWithLogs(t, ctx, cmd, true) + // Ensure we only build once + buildOnce.Do(func() { + t.Log("Building router binary...") + + cmd := exec.Command("make", "build") + cmd.Dir = routerDir + runCmdWithLogs(t, ctx, cmd, true) // Run the build command + + // Determine the binary path after successful build + binPath := filepath.Join(routerDir, "router") // Adjust if needed for Windows + require.FileExists(t, binPath, "Router binary was not found after build") + + routerBin = binPath // Store the path for reuse + t.Logf("Router binary built: %s", routerBin) + }) +} + +// getRouterBinary ensures the router binary is built and returns its path. +func getRouterBinary(t *testing.T, ctx context.Context) string { + buildRouterBin(t, ctx) // Ensure the router is built + return routerBin // Return cached binary path } // runRouterBin starts the router binary and returns an Environment. -func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, routerDir string) *Environment { +func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, binaryPath string) *Environment { t.Helper() - binaryPath := filepath.Join(routerDir, "router") fullBinPath, err := filepath.Abs(binaryPath) require.NoError(t, err) From 1ce7845a51e4bb8ce6a1bc67db5f8a969d3b662b Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 13:47:46 +0200 Subject: [PATCH 11/15] build with race for tests --- router-tests/testenv/testexec.go | 2 +- router/Makefile | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go index 9bfb5eafd0..fa03f33f52 100644 --- a/router-tests/testenv/testexec.go +++ b/router-tests/testenv/testexec.go @@ -48,7 +48,7 @@ func buildRouterBin(t *testing.T, ctx context.Context) { buildOnce.Do(func() { t.Log("Building router binary...") - cmd := exec.Command("make", "build") + cmd := exec.Command("make", "build-race") cmd.Dir = routerDir runCmdWithLogs(t, ctx, cmd, true) // Run the build command diff --git a/router/Makefile b/router/Makefile index 2e50254643..d6a47050cb 100644 --- a/router/Makefile +++ b/router/Makefile @@ -28,6 +28,9 @@ COMMIT?=$(shell git rev-parse HEAD) build: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go +build-race: + CGO_ENABLED=0 go build -trimpath -race -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go + build-custom: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/custom/main.go From ea596b9f8e739a63c43e011802f404a45fc44cdc Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 14:11:06 +0200 Subject: [PATCH 12/15] enable cgo --- router/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/Makefile b/router/Makefile index d6a47050cb..73e2dfd77a 100644 --- a/router/Makefile +++ b/router/Makefile @@ -29,7 +29,7 @@ build: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go build-race: - CGO_ENABLED=0 go build -trimpath -race -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go + CGO_ENABLED=1 go build -trimpath -race -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go build-custom: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/custom/main.go From ea50bd6788af87a87185e09b96b58e009500dc39 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 14:50:27 +0200 Subject: [PATCH 13/15] Fix build command --- router/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/Makefile b/router/Makefile index 73e2dfd77a..896f8b1fe6 100644 --- a/router/Makefile +++ b/router/Makefile @@ -29,7 +29,7 @@ build: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go build-race: - CGO_ENABLED=1 go build -trimpath -race -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go + CGO_ENABLED=1 go build -trimpath -race -ldflags "-X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/router/main.go build-custom: CGO_ENABLED=0 go build -trimpath -ldflags "-extldflags -static -X github.com/wundergraph/cosmo/router/core.Version=$(VERSION) -X github.com/wundergraph/cosmo/router/core.Date=$(DATE) -X github.com/wundergraph/cosmo/router/core.Commit=$(COMMIT)" -a -o router cmd/custom/main.go From 73531db72dc41e7b0b1597d435dbd64e845da553 Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 16:13:54 +0200 Subject: [PATCH 14/15] use shutdown delay, remove sleep --- router-tests/testenv/testexec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go index fa03f33f52..4dccbc48da 100644 --- a/router-tests/testenv/testexec.go +++ b/router-tests/testenv/testexec.go @@ -87,6 +87,7 @@ func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, binaryPath str "CDN_URL": testCdn.URL, "METRICS_OTLP_ENABLED": "false", "RETRY_ENABLED": "false", + "SHUTDOWN_DELAY": "30s", "CDN_CACHE_SIZE": fmt.Sprintf("%d", 1024*1024), } { vals += fmt.Sprintf("\n%s=%s", key, val) @@ -103,7 +104,6 @@ func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, binaryPath str go func() { <-newCtx.Done() _ = cmd.Process.Signal(os.Interrupt) - time.Sleep(2 * time.Second) _ = cmd.Process.Kill() }() From 76482b3b0c018f06afc7f5a3224e1477cca6f18c Mon Sep 17 00:00:00 2001 From: Dave Freilich Date: Thu, 30 Jan 2025 19:26:41 +0200 Subject: [PATCH 15/15] graceful shutdown of router --- router-tests/testenv/testenv.go | 4 ++-- router-tests/testenv/testexec.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/router-tests/testenv/testenv.go b/router-tests/testenv/testenv.go index 4bdd56f6e9..20812d0ec4 100644 --- a/router-tests/testenv/testenv.go +++ b/router-tests/testenv/testenv.go @@ -1172,8 +1172,8 @@ func (e *Environment) Shutdown() { } if e.routerCmd != nil { - if err := e.routerCmd.Process.Kill(); err != nil { - e.t.Logf("could not kill router process: %s", err) + if err := e.routerCmd.Process.Signal(os.Interrupt); err != nil { + e.t.Logf("could not interrupt router process: %s", err) } } } diff --git a/router-tests/testenv/testexec.go b/router-tests/testenv/testexec.go index 4dccbc48da..0f0ed29904 100644 --- a/router-tests/testenv/testexec.go +++ b/router-tests/testenv/testexec.go @@ -104,7 +104,6 @@ func runRouterBin(t *testing.T, ctx context.Context, cfg *Config, binaryPath str go func() { <-newCtx.Done() _ = cmd.Process.Signal(os.Interrupt) - _ = cmd.Process.Kill() }() // Create test environment