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

Implement non application error codes fix #895 #907

Merged
merged 11 commits into from
Mar 11, 2019
Merged
25 changes: 21 additions & 4 deletions js/common/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ var (
)
)

// Returns the JS name for an exported struct field. The name is snake_cased, with respect for
// if a fieldName is the key of this map exactly than the value for the given key should be used as
// the name of the field in js
var fieldNameExceptions = map[string]string{
"OCSP": "ocsp",
}

// FieldName Returns the JS name for an exported struct field. The name is snake_cased, with respect for
// certain common initialisms (URL, ID, HTTP, etc).
func FieldName(t reflect.Type, f reflect.StructField) string {
// PkgPath is non-empty for unexported fields.
Expand All @@ -61,18 +67,24 @@ func FieldName(t reflect.Type, f reflect.StructField) string {
return tag
}

if exception, ok := fieldNameExceptions[f.Name]; ok {
return exception
}

// Default to lowercasing the first character of the field name.
return snaker.CamelToSnake(f.Name)
}

// if a methodName is the key of this map exactly than the value for the given key should be used as
// the name of the method in js
var methodNameExceptions map[string]string = map[string]string{
var methodNameExceptions = map[string]string{
"JSON": "json",
"HTML": "html",
"URL": "url",
"OCSP": "ocsp",
}

// Returns the JS name for an exported method. The first letter of the method's name is
// MethodName Returns the JS name for an exported method. The first letter of the method's name is
// lowercased, otherwise it is unaltered.
func MethodName(t reflect.Type, m reflect.Method) string {
// A field with a name beginning with an X is a constructor, and just gets the prefix stripped.
Expand All @@ -91,11 +103,15 @@ func MethodName(t reflect.Type, m reflect.Method) string {
// FieldNameMapper for goja.Runtime.SetFieldNameMapper()
type FieldNameMapper struct{}

// FieldName is part of the goja.FieldNameMapper interface
// https://godoc.org/github.com/dop251/goja#FieldNameMapper
func (FieldNameMapper) FieldName(t reflect.Type, f reflect.StructField) string { return FieldName(t, f) }

// MethodName is part of the goja.FieldNameMapper interface
// https://godoc.org/github.com/dop251/goja#FieldNameMapper
func (FieldNameMapper) MethodName(t reflect.Type, m reflect.Method) string { return MethodName(t, m) }

// Binds an object's members to the global scope. Returns a function that un-binds them.
// BindToGlobal Binds an object's members to the global scope. Returns a function that un-binds them.
// Note that this will panic if passed something that isn't a struct; please don't do that.
func BindToGlobal(rt *goja.Runtime, data map[string]interface{}) func() {
keys := make([]string, len(data))
Expand All @@ -113,6 +129,7 @@ func BindToGlobal(rt *goja.Runtime, data map[string]interface{}) func() {
}
}

// Bind the provided value v to the provided runtime
func Bind(rt *goja.Runtime, v interface{}, ctxPtr *context.Context) map[string]interface{} {
exports := make(map[string]interface{})

Expand Down
15 changes: 1 addition & 14 deletions js/common/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,9 @@ import (
type ctxKey int

const (
ctxKeyState ctxKey = iota
ctxKeyRuntime
ctxKeyRuntime ctxKey = iota
)

func WithState(ctx context.Context, state *State) context.Context {
return context.WithValue(ctx, ctxKeyState, state)
}

func GetState(ctx context.Context) *State {
v := ctx.Value(ctxKeyState)
if v == nil {
return nil
}
return v.(*State)
}

func WithRuntime(ctx context.Context, rt *goja.Runtime) context.Context {
return context.WithValue(ctx, ctxKeyRuntime, rt)
}
Expand Down
9 changes: 0 additions & 9 deletions js/common/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,6 @@ import (
"github.com/stretchr/testify/assert"
)

func TestContextState(t *testing.T) {
st := &State{}
assert.Equal(t, st, GetState(WithState(context.Background(), st)))
}

func TestContextStateNil(t *testing.T) {
assert.Nil(t, GetState(context.Background()))
}

func TestContextRuntime(t *testing.T) {
rt := goja.New()
assert.Equal(t, rt, GetRuntime(WithRuntime(context.Background(), rt)))
Expand Down
4 changes: 2 additions & 2 deletions js/initcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ func TestRequestWithBinaryFile(t *testing.T) {
logger.Level = log.DebugLevel
logger.Out = ioutil.Discard

state := &common.State{
state := &lib.State{
Options: lib.Options{},
Logger: logger,
Group: root,
Expand All @@ -406,7 +406,7 @@ func TestRequestWithBinaryFile(t *testing.T) {
}

ctx := context.Background()
ctx = common.WithState(ctx, state)
ctx = lib.WithState(ctx, state)
ctx = common.WithRuntime(ctx, bi.Runtime)
*bi.Context = ctx

Expand Down
12 changes: 6 additions & 6 deletions js/modules/k6/crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,10 @@ func TestStreamingApi(t *testing.T) {
rt.SetFieldNameMapper(common.FieldNameMapper{})

root, _ := lib.NewGroup("", nil)
state := &common.State{Group: root}
state := &lib.State{Group: root}

ctx := context.Background()
ctx = common.WithState(ctx, state)
ctx = lib.WithState(ctx, state)
ctx = common.WithRuntime(ctx, rt)

rt.Set("crypto", common.Bind(rt, New(), &ctx))
Expand Down Expand Up @@ -249,10 +249,10 @@ func TestOutputEncoding(t *testing.T) {
rt.SetFieldNameMapper(common.FieldNameMapper{})

root, _ := lib.NewGroup("", nil)
state := &common.State{Group: root}
state := &lib.State{Group: root}

ctx := context.Background()
ctx = common.WithState(ctx, state)
ctx = lib.WithState(ctx, state)
ctx = common.WithRuntime(ctx, rt)

rt.Set("crypto", common.Bind(rt, New(), &ctx))
Expand Down Expand Up @@ -310,10 +310,10 @@ func TestHMac(t *testing.T) {
rt.SetFieldNameMapper(common.FieldNameMapper{})

root, _ := lib.NewGroup("", nil)
state := &common.State{Group: root}
state := &lib.State{Group: root}

ctx := context.Background()
ctx = common.WithState(ctx, state)
ctx = lib.WithState(ctx, state)
ctx = common.WithRuntime(ctx, rt)

rt.Set("crypto", common.Bind(rt, New(), &ctx))
Expand Down
7 changes: 3 additions & 4 deletions js/modules/k6/http/cookiejar.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/pkg/errors"
)

// HTTPCookieJar is cookiejar.Jar wrapper to be used in js scripts
type HTTPCookieJar struct {
jar *cookiejar.Jar
ctx *context.Context
Expand All @@ -46,6 +47,7 @@ func newCookieJar(ctxPtr *context.Context) *HTTPCookieJar {
return &HTTPCookieJar{jar, ctxPtr}
}

// CookiesForURL return the cookies for a given url as a map of key and values
func (j HTTPCookieJar) CookiesForURL(url string) map[string][]string {
u, err := neturl.Parse(url)
if err != nil {
Expand All @@ -60,6 +62,7 @@ func (j HTTPCookieJar) CookiesForURL(url string) map[string][]string {
return objs
}

// Set sets a cookie for a particular url with the given name value and additional opts
func (j HTTPCookieJar) Set(url, name, value string, opts goja.Value) (bool, error) {
rt := common.GetRuntime(*j.ctx)

Expand All @@ -74,10 +77,6 @@ func (j HTTPCookieJar) Set(url, name, value string, opts goja.Value) (bool, erro
params := paramsV.ToObject(rt)
for _, k := range params.Keys() {
switch strings.ToLower(k) {
case "name":
c.Name = params.Get(k).String()
case "value":
c.Value = params.Get(k).String()
case "path":
c.Path = params.Get(k).String()
case "domain":
Expand Down
68 changes: 2 additions & 66 deletions js/modules/k6/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,10 @@ package http

import (
"context"
"net/http"
"net/http/cookiejar"

"fmt"
"net/http/httputil"

"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/lib"
"github.com/loadimpact/k6/lib/netext"
log "github.com/sirupsen/logrus"
)

const (
Expand All @@ -46,18 +41,6 @@ const (
// ErrJarForbiddenInInitContext is used when a cookie jar was made in the init context
var ErrJarForbiddenInInitContext = common.NewInitContextError("Making cookie jars in the init context is not supported")

type HTTPCookie struct {
Name, Value, Domain, Path string
HttpOnly, Secure bool
MaxAge int
Expires int64
}

type HTTPRequestCookie struct {
Name, Value string
Replace bool
}

type HTTP struct {
SSL_3_0 string `js:"SSL_3_0"`
TLS_1_0 string `js:"TLS_1_0"`
Expand Down Expand Up @@ -108,56 +91,9 @@ func (*HTTP) XCookieJar(ctx *context.Context) *HTTPCookieJar {
}

func (*HTTP) CookieJar(ctx context.Context) (*HTTPCookieJar, error) {
state := common.GetState(ctx)
state := lib.GetState(ctx)
if state == nil {
return nil, ErrJarForbiddenInInitContext
}
return &HTTPCookieJar{state.CookieJar, &ctx}, nil
}

func (*HTTP) mergeCookies(req *http.Request, jar *cookiejar.Jar, reqCookies map[string]*HTTPRequestCookie) map[string][]*HTTPRequestCookie {
allCookies := make(map[string][]*HTTPRequestCookie)
for _, c := range jar.Cookies(req.URL) {
allCookies[c.Name] = append(allCookies[c.Name], &HTTPRequestCookie{Name: c.Name, Value: c.Value})
}
for key, reqCookie := range reqCookies {
if jc := allCookies[key]; jc != nil && reqCookie.Replace {
allCookies[key] = []*HTTPRequestCookie{{Name: key, Value: reqCookie.Value}}
} else {
allCookies[key] = append(allCookies[key], &HTTPRequestCookie{Name: key, Value: reqCookie.Value})
}
}
return allCookies
}

func (*HTTP) setRequestCookies(req *http.Request, reqCookies map[string][]*HTTPRequestCookie) {
for _, cookies := range reqCookies {
for _, c := range cookies {
req.AddCookie(&http.Cookie{Name: c.Name, Value: c.Value})
}
}
}

func (*HTTP) debugRequest(state *common.State, req *http.Request, description string) {
if state.Options.HttpDebug.String != "" {
dump, err := httputil.DumpRequestOut(req, state.Options.HttpDebug.String == "full")
if err != nil {
log.Fatal(err)
}
logDump(description, dump)
}
}

func (*HTTP) debugResponse(state *common.State, res *http.Response, description string) {
if state.Options.HttpDebug.String != "" && res != nil {
dump, err := httputil.DumpResponse(res, state.Options.HttpDebug.String == "full")
if err != nil {
log.Fatal(err)
}
logDump(description, dump)
}
}

func logDump(description string, dump []byte) {
fmt.Printf("%s:\n%s\n", description, dump)
}
8 changes: 4 additions & 4 deletions js/modules/k6/http/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
package http

import (
"net/url"
"testing"

"github.com/dop251/goja"
"github.com/loadimpact/k6/js/common"
"github.com/loadimpact/k6/lib/netext/httpext"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestTagURL(t *testing.T) {
Expand All @@ -43,9 +44,8 @@ func TestTagURL(t *testing.T) {
}
for expr, data := range testdata {
t.Run("expr="+expr, func(t *testing.T) {
u, err := url.Parse(data.u)
assert.NoError(t, err)
tag := URL{u, data.n, data.u}
tag, err := httpext.NewURL(data.u, data.n)
require.NoError(t, err)
v, err := common.RunString(rt, "http.url`"+expr+"`")
if assert.NoError(t, err) {
assert.Equal(t, tag, v.Export())
Expand Down
24 changes: 8 additions & 16 deletions js/modules/k6/http/http_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,30 @@ package http

import (
"fmt"
"net/url"

"github.com/dop251/goja"
"github.com/loadimpact/k6/lib/netext/httpext"
)

// A URL wraps net.URL, and preserves the template (if any) the URL was constructed from.
type URL struct {
URL *url.URL `js:"-"`
Name string `js:"name"` // http://example.com/thing/${}/
URLString string `js:"url"` // http://example.com/thing/1234/
}

// ToURL tries to convert anything passed to it to a k6 URL struct
func ToURL(u interface{}) (URL, error) {
func ToURL(u interface{}) (httpext.URL, error) {
switch tu := u.(type) {
case URL:
case httpext.URL:
// Handling of http.url`http://example.com/{$id}`
return tu, nil
case string:
// Handling of "http://example.com/"
u, err := url.Parse(tu)
return URL{u, tu, tu}, err
return httpext.NewURL(tu, tu)
case goja.Value:
// Unwrap goja values
return ToURL(tu.Export())
default:
return URL{}, fmt.Errorf("invalid URL value '%#v'", u)
return httpext.URL{}, fmt.Errorf("invalid URL value '%#v'", u)
}
}

func (http *HTTP) Url(parts []string, pieces ...string) (URL, error) {
// URL creates new URL from the provided parts
func (http *HTTP) URL(parts []string, pieces ...string) (httpext.URL, error) {
var name, urlstr string
for i, part := range parts {
name += part
Expand All @@ -62,6 +55,5 @@ func (http *HTTP) Url(parts []string, pieces ...string) (URL, error) {
urlstr += pieces[i]
}
}
u, err := url.Parse(urlstr)
return URL{u, name, urlstr}, err
return httpext.NewURL(urlstr, name)
}
Loading