Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename gRPC module to k6/net/grpc and track and bundle loaded protobuf files #1706

Merged
merged 3 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion js/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,15 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *
rt.Set("__VU", vuID)
rt.Set("console", common.Bind(rt, newConsole(logger), init.ctxPtr))

*init.ctxPtr = common.WithRuntime(context.Background(), rt)
// TODO: get rid of the unused ctxPtr, use a real external context (so we
// can interrupt), build the common.InitEnvironment earlier and reuse it
initenv := &common.InitEnvironment{
Logger: logger,
FileSystems: init.filesystems,
CWD: init.pwd,
}
ctx := common.WithInitEnv(context.Background(), initenv)
*init.ctxPtr = common.WithRuntime(ctx, rt)
unbindInit := common.BindToGlobal(rt, common.Bind(rt, init, init.ctxPtr))
if _, err := rt.RunProgram(b.Program); err != nil {
return err
Expand Down
17 changes: 17 additions & 0 deletions js/common/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,33 @@ type ctxKey int

const (
ctxKeyRuntime ctxKey = iota
ctxKeyInitEnv
)

// WithRuntime attaches the given goja runtime to the context.
func WithRuntime(ctx context.Context, rt *goja.Runtime) context.Context {
return context.WithValue(ctx, ctxKeyRuntime, rt)
}

// GetRuntime retrieves the attached goja runtime from the given context.
func GetRuntime(ctx context.Context) *goja.Runtime {
v := ctx.Value(ctxKeyRuntime)
if v == nil {
return nil
}
return v.(*goja.Runtime)
}

// WithInitEnv attaches the given init environment to the context.
func WithInitEnv(ctx context.Context, initEnv *InitEnvironment) context.Context {
return context.WithValue(ctx, ctxKeyInitEnv, initEnv)
}

// GetInitEnv retrieves the attached init environment struct from the given context.
func GetInitEnv(ctx context.Context) *InitEnvironment {
v := ctx.Value(ctxKeyInitEnv)
if v == nil {
return nil
}
return v.(*InitEnvironment)
}
6 changes: 6 additions & 0 deletions js/common/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ func TestContextRuntime(t *testing.T) {
func TestContextRuntimeNil(t *testing.T) {
assert.Nil(t, GetRuntime(context.Background()))
}

func TestContextInitEnv(t *testing.T) {
ie := &InitEnvironment{}
assert.Nil(t, GetInitEnv(context.Background()))
assert.Equal(t, ie, GetInitEnv(WithInitEnv(context.Background(), ie)))
}
62 changes: 62 additions & 0 deletions js/common/initenv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
*
* k6 - a next-generation load testing tool
* Copyright (C) 2020 Load Impact
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package common
na-- marked this conversation as resolved.
Show resolved Hide resolved

import (
"net/url"
"path/filepath"

"github.com/sirupsen/logrus"
"github.com/spf13/afero"
)

// InitEnvironment contains properties that can be accessed by Go code executed
// in the k6 init context. It can be accessed by calling common.GetInitEnv().
type InitEnvironment struct {
Logger logrus.FieldLogger
FileSystems map[string]afero.Fs
CWD *url.URL
// TODO: add RuntimeOptions and other properties, goja sources, etc.
// ideally, we should leave this as the only data structure necessary for
// executing the init context for all JS modules
}

// GetAbsFilePath should be used to access the FileSystems, since afero has a
// bug when opening files with relative paths - it caches them from the FS root,
// not the current working directory... So, if necessary, this method will
// transform any relative paths into absolute ones, using the CWD.
//
// TODO: refactor? It was copied from
// https://github.com/loadimpact/k6/blob/c51095ad7304bdd1e82cdb33c91abc331533b886/js/initcontext.go#L211-L222
func (ie *InitEnvironment) GetAbsFilePath(filename string) string {
// Here IsAbs should be enough but unfortunately it doesn't handle absolute paths starting from
// the current drive on windows like `\users\noname\...`. Also it makes it more easy to test and
// will probably be need for archive execution under windows if always consider '/...' as an
// absolute path.
if filename[0] != '/' && filename[0] != '\\' && !filepath.IsAbs(filename) {
filename = filepath.Join(ie.CWD.Path, filename)
}
filename = filepath.Clean(filename)
if filename[0:1] != afero.FilePathSeparator {
filename = afero.FilePathSeparator + filename
}
return filename
}
2 changes: 2 additions & 0 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const openCantBeUsedOutsideInitContextMsg = `The "open()" function is only avail
`(i.e. the global scope), see https://k6.io/docs/using-k6/test-life-cycle for more information`

// InitContext provides APIs for use in the init context.
//
// TODO: refactor most/all of this state away, use common.InitEnvironment instead
type InitContext struct {
// Bound runtime; used to instantiate objects.
runtime *goja.Runtime
Expand Down
26 changes: 20 additions & 6 deletions js/modules/k6/grpc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
Expand Down Expand Up @@ -116,17 +117,26 @@ func (c *Client) Load(ctxPtr *context.Context, importPaths []string, filenames .
return nil, errors.New("load must be called in the init context")
}

f, err := protoparse.ResolveFilenames(importPaths, filenames...)
if err != nil {
return nil, err
initEnv := common.GetInitEnv(*ctxPtr)
if initEnv == nil {
return nil, errors.New("missing init environment")
}

// If no import paths are specified, use the current working directory
if len(importPaths) == 0 {
importPaths = append(importPaths, initEnv.CWD.Path)
}

parser := protoparse.Parser{
ImportPaths: importPaths,
InferImportPaths: len(importPaths) == 0,
InferImportPaths: false,
Accessor: protoparse.FileAccessor(func(filename string) (io.ReadCloser, error) {
absFilePath := initEnv.GetAbsFilePath(filename)
return initEnv.FileSystems["file"].Open(absFilePath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for a follow up PR, but could we use constants / an enum for the FileSystems keys? It wasn't clear to me initially that this is the URL scheme.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we probably should refactor this together with the other TODOs I left...

}),
}

fds, err := parser.ParseFiles(f...)
fds, err := parser.ParseFiles(filenames...)
if err != nil {
return nil, err
}
Expand All @@ -144,7 +154,11 @@ func (c *Client) Load(ctxPtr *context.Context, importPaths []string, filenames .
}

var rtn []MethodInfo
c.mds = make(map[string]protoreflect.MethodDescriptor)
if c.mds == nil {
// This allows us to call load() multiple times, without overwriting the
// previously loaded definitions.
c.mds = make(map[string]protoreflect.MethodDescriptor)
}

files.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
sds := fd.Services()
Expand Down
25 changes: 25 additions & 0 deletions js/modules/k6/grpc/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ package grpc
import (
"bytes"
"context"
"net/url"
"os"
"runtime"
"strings"
"testing"

"github.com/dop251/goja"
"github.com/sirupsen/logrus"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
Expand All @@ -39,11 +44,14 @@ import (

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/fsext"
"github.com/loadimpact/k6/lib/metrics"
"github.com/loadimpact/k6/lib/testutils/httpmultibin"
"github.com/loadimpact/k6/stats"
)

const isWindows = runtime.GOOS == "windows"

func assertMetricEmitted(t *testing.T, metric *stats.Metric, sampleContainers []stats.SampleContainer, url string) {
seenMetric := false

Expand Down Expand Up @@ -87,7 +95,24 @@ func TestClient(t *testing.T) {
},
}

cwd, err := os.Getwd()
require.NoError(t, err)

fs := afero.NewOsFs()
if isWindows {
fs = fsext.NewTrimFilePathSeparatorFs(fs)
}

initEnv := &common.InitEnvironment{
Logger: logrus.New(),
CWD: &url.URL{Path: cwd},
FileSystems: map[string]afero.Fs{
"file": fs,
},
}

ctx := common.WithRuntime(context.Background(), rt)
ctx = common.WithInitEnv(ctx, initEnv)

rt.Set("grpc", common.Bind(rt, New(), &ctx))

Expand Down
2 changes: 1 addition & 1 deletion js/modules/k6/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
)

func init() {
modules.Register("k6/protocols/grpc", New())
modules.Register("k6/net/grpc", New())
}

// GRPC represents the gRPC protocol module for k6
Expand Down
4 changes: 2 additions & 2 deletions js/modules/k6/http/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,11 +542,11 @@ func TestRequestAndBatch(t *testing.T) {
}
t.Run("ocsp_stapled_good", func(t *testing.T) {
_, err := common.RunString(rt, `
var res = http.request("GET", "https://www.microsoft.com/");
var res = http.request("GET", "https://www.microsoft.com/en-us/");
if (res.ocsp.status != http.OCSP_STATUS_GOOD) { throw new Error("wrong ocsp stapled response status: " + res.ocsp.status); }
`)
assert.NoError(t, err)
assertRequestMetricsEmitted(t, stats.GetBufferedSamples(samples), "GET", "https://www.microsoft.com/", "", 200, "")
assertRequestMetricsEmitted(t, stats.GetBufferedSamples(samples), "GET", "https://www.microsoft.com/en-us/", "", 200, "")
})
})
t.Run("Invalid", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions js/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ func TestVURunContext(t *testing.T) {
fnCalled = true

assert.Equal(t, vu.Runtime, common.GetRuntime(*vu.Context), "incorrect runtime in context")
assert.Nil(t, common.GetInitEnv(*vu.Context)) // shouldn't get this in the vu context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be nice for there to be a test it is there in the initcontext though, in the js packages, for example, bundle_test.go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's one test I want to add - I couldn't do it here since getSimpleRunner() can't be used for it


state := lib.GetState(*vu.Context)
if assert.NotNil(t, state) {
Expand Down
2 changes: 1 addition & 1 deletion samples/grpc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import grpc from 'k6/protocols/grpc';
import grpc from 'k6/net/grpc';
import { check } from "k6";

let client = grpc.newClient();
Expand Down