From f8fdddd3210052db0d97058494504666510e8075 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Tue, 5 Dec 2023 19:34:49 +0100 Subject: [PATCH] Adds integration tests for the GRPC module --- cmd/tests/cmd_run_grpc_test.go | 129 +++++++++++++++++++++++++++++++++ cmd/tests/grpc.go | 52 +++++++++++++ examples/grpc.js | 21 ------ examples/grpc_invoke.js | 28 +++++++ examples/grpc_reflection.js | 25 ++++--- 5 files changed, 224 insertions(+), 31 deletions(-) create mode 100644 cmd/tests/cmd_run_grpc_test.go create mode 100644 cmd/tests/grpc.go delete mode 100644 examples/grpc.js create mode 100644 examples/grpc_invoke.js diff --git a/cmd/tests/cmd_run_grpc_test.go b/cmd/tests/cmd_run_grpc_test.go new file mode 100644 index 00000000000..684782549f5 --- /dev/null +++ b/cmd/tests/cmd_run_grpc_test.go @@ -0,0 +1,129 @@ +package tests + +import ( + "os" + "path/filepath" + "testing" + + "go.k6.io/k6/cmd" + "go.k6.io/k6/lib/fsext" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const projectRootPath = "../../" + +// TestGRPCInputOutput runs same k6's scripts that we have in example folder +// it check that output contains/not contains cetane things +func TestGRPCInputOutput(t *testing.T) { + t.Parallel() + + tc := map[string]struct { + script string + outputShouldContain []string + outputShouldNotContain []string + }{ + "Server streaming": { + script: projectRootPath + "examples/grpc_server_streaming.js", + outputShouldContain: []string{ + "output: -", + "default: 1 iterations for each of 1 VUs", + "1 complete and 0 interrupted iterations", + "Found feature called", + "grpc_streams", + "grpc_streams_msgs_received", + "grpc_streams_msgs_sent", + "All done", + }, + outputShouldNotContain: []string{ + "Stream Error:", + }, + }, + "Client Streaming": { + script: projectRootPath + "examples/grpc_client_streaming.js", + outputShouldContain: []string{ + "output: -", + "default: 1 iterations for each of 1 VUs", + "1 complete and 0 interrupted iterations", + "Visiting point", + "Finished trip with 5 points", + "Passed 5 feature", + "grpc_streams", + "grpc_streams_msgs_received", + "grpc_streams_msgs_sent", + "All done", + }, + outputShouldNotContain: []string{ + "Stream Error:", + }, + }, + "Invoke": { + script: projectRootPath + "examples/grpc_invoke.js", + outputShouldContain: []string{ + "output: -", + "default: 1 iterations for each of 1 VUs", + "1 complete and 0 interrupted iterations", + "3 Hasta Way, Newton, NJ 07860, USA", + }, + outputShouldNotContain: []string{ + "grpc_streams", + "grpc_streams_msgs_received", + "grpc_streams_msgs_sent", + }, + }, + "Reflection": { + script: projectRootPath + "examples/grpc_reflection.js", + outputShouldContain: []string{ + "output: -", + "default: 1 iterations for each of 1 VUs", + "1 complete and 0 interrupted iterations", + "3 Hasta Way, Newton, NJ 07860, USA", + }, + outputShouldNotContain: []string{ + "grpc_streams", + "grpc_streams_msgs_received", + "grpc_streams_msgs_sent", + }, + }, + } + + // Read the proto file from the testutils package + // it's same that we use in the examples + proto, err := os.ReadFile(projectRootPath + "lib/testutils/grpcservice/route_guide.proto") //nolint:forbidigo + require.NoError(t, err) + + for name, test := range tc { + name := name + test := test + + t.Run(name, func(t *testing.T) { + t.Parallel() + + tb := NewGRPC(t) + + script, err := os.ReadFile(test.script) //nolint:forbidigo + require.NoError(t, err) + + ts := getSingleFileTestState(t, string(script), []string{"-v", "--log-output=stdout", "--no-usage-report"}, 0) + ts.Env = map[string]string{ + "GRPC_ADDR": tb.Addr, + "GRPC_PROTO_PATH": "./proto.proto", + } + require.NoError(t, fsext.WriteFile(ts.FS, filepath.Join(ts.Cwd, "proto.proto"), proto, 0o644)) + + cmd.ExecuteWithGlobalState(ts.GlobalState) + + stdout := ts.Stdout.String() + + for _, s := range test.outputShouldContain { + assert.Contains(t, stdout, s) + } + for _, s := range test.outputShouldNotContain { + assert.NotContains(t, stdout, s) + } + + assert.Empty(t, ts.Stderr.String()) + }) + } +} diff --git a/cmd/tests/grpc.go b/cmd/tests/grpc.go new file mode 100644 index 00000000000..c0c812abc1b --- /dev/null +++ b/cmd/tests/grpc.go @@ -0,0 +1,52 @@ +package tests + +import ( + "net" + "strings" + "testing" + + "go.k6.io/k6/lib/testutils/grpcservice" + + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +// GRPC . +type GRPC struct { + Addr string + ServerGRPC *grpc.Server + Replacer *strings.Replacer +} + +// NewGRPC . +func NewGRPC(t testing.TB) *GRPC { + grpcServer := grpc.NewServer() + + addr := getFreeBindAddr(t) + + lis, err := net.Listen("tcp", addr) + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + + features := grpcservice.LoadFeatures("") + grpcservice.RegisterRouteGuideServer(grpcServer, grpcservice.NewRouteGuideServer(features...)) + grpcservice.RegisterFeatureExplorerServer(grpcServer, grpcservice.NewFeatureExplorerServer(features...)) + reflection.Register(grpcServer) + + go func() { + _ = grpcServer.Serve(lis) + }() + + t.Cleanup(func() { + grpcServer.Stop() + }) + + return &GRPC{ + Addr: addr, + ServerGRPC: grpcServer, + Replacer: strings.NewReplacer( + "GRPCBIN_ADDR", addr, + ), + } +} diff --git a/examples/grpc.js b/examples/grpc.js deleted file mode 100644 index 7a456e1e63c..00000000000 --- a/examples/grpc.js +++ /dev/null @@ -1,21 +0,0 @@ -import grpc from 'k6/net/grpc'; -import { check } from "k6"; - -let client = new grpc.Client(); -client.load([], "../lib/testutils/grpcservice/route_guide.proto") - - -export default () => { - client.connect("127.0.0.1:10000", { plaintext: true }) - - const response = client.invoke("main.FeatureExplorer/GetFeature", { - latitude: 410248224, - longitude: -747127767 - }) - - check(response, { "status is OK": (r) => r && r.status === grpc.StatusOK }); - console.log(JSON.stringify(response.message)) - - client.close() -} - diff --git a/examples/grpc_invoke.js b/examples/grpc_invoke.js new file mode 100644 index 00000000000..016e84fdba6 --- /dev/null +++ b/examples/grpc_invoke.js @@ -0,0 +1,28 @@ +import grpc from 'k6/net/grpc'; +import { check } from "k6"; + +// to run this sample, you need to start the grpc server first. +// to start the grpc server, run the following command in k6 repository's root: +// go run -mod=mod examples/grpc_server/*.go +// (golang should be installed) +const GRPC_ADDR = __ENV.GRPC_ADDR || '127.0.0.1:10000'; +const GRPC_PROTO_PATH = __ENV.GRPC_PROTO_PATH || '../lib/testutils/grpcservice/route_guide.proto'; + +let client = new grpc.Client(); + +client.load([], GRPC_PROTO_PATH); + +export default () => { + client.connect(GRPC_ADDR, { plaintext: true }); + + const response = client.invoke("main.FeatureExplorer/GetFeature", { + latitude: 410248224, + longitude: -747127767 + }) + + check(response, { "status is OK": (r) => r && r.status === grpc.StatusOK }); + console.log(JSON.stringify(response.message)) + + client.close() +} + diff --git a/examples/grpc_reflection.js b/examples/grpc_reflection.js index b5c63a9ff4f..71ec5a38ff7 100644 --- a/examples/grpc_reflection.js +++ b/examples/grpc_reflection.js @@ -1,18 +1,23 @@ import grpc from 'k6/net/grpc'; import {check} from "k6"; +// to run this sample, you need to start the grpc server first. +// to start the grpc server, run the following command in k6 repository's root: +// go run -mod=mod examples/grpc_server/*.go +// (golang should be installed) +const GRPC_ADDR = __ENV.GRPC_ADDR || '127.0.0.1:10000'; + let client = new grpc.Client(); export default () => { - client.connect("127.0.0.1:10000", {plaintext: true, reflect: true}) - const response = client.invoke("main.FeatureExplorer/GetFeature", { - latitude: 410248224, - longitude: -747127767 - }) - - check(response, {"status is OK": (r) => r && r.status === grpc.StatusOK}); - console.log(JSON.stringify(response.message)) + client.connect(GRPC_ADDR, { plaintext: true, reflect: true }); + const response = client.invoke('main.FeatureExplorer/GetFeature', { + latitude: 410248224, + longitude: -747127767, + }); - client.close() -} + check(response, { 'status is OK': (r) => r && r.status === grpc.StatusOK }); + console.log(JSON.stringify(response.message)); + client.close(); +};