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

More ArrayBuffer support #1841

Merged
merged 17 commits into from
Apr 29, 2021
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
14 changes: 14 additions & 0 deletions js/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ func ToBytes(data interface{}) ([]byte, error) {
return nil, fmt.Errorf("invalid type %T, expected string, []byte or ArrayBuffer", data)
}
}

// ToString tries to return a string from compatible types.
func ToString(data interface{}) (string, error) {
switch dt := data.(type) {
case []byte:
return string(dt), nil
case string:
return dt, nil
case goja.ArrayBuffer:
return string(dt.Bytes()), nil
default:
return "", fmt.Errorf("invalid type %T, expected string, []byte or ArrayBuffer", data)
}
}
29 changes: 29 additions & 0 deletions js/common/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func TestThrow(t *testing.T) {
}

func TestToBytes(t *testing.T) {
t.Parallel()
rt := goja.New()
b := []byte("hello")
testCases := []struct {
Expand All @@ -70,3 +71,31 @@ func TestToBytes(t *testing.T) {
})
}
}

func TestToString(t *testing.T) {
t.Parallel()
rt := goja.New()
s := "hello"
testCases := []struct {
in interface{}
expOut, expErr string
}{
{s, s, ""},
{"hello", s, ""},
{rt.NewArrayBuffer([]byte(s)), s, ""},
{struct{}{}, "", "invalid type struct {}, expected string, []byte or ArrayBuffer"},
}

for _, tc := range testCases { //nolint: paralleltest // false positive: https://github.com/kunwardeep/paralleltest/issues/8
tc := tc
t.Run(fmt.Sprintf("%T", tc.in), func(t *testing.T) {
t.Parallel()
out, err := ToString(tc.in)
if tc.expErr != "" {
assert.EqualError(t, err, tc.expErr)
return
}
assert.Equal(t, tc.expOut, out)
})
}
}
8 changes: 5 additions & 3 deletions js/initcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,9 @@ func (i *InitContext) compileImport(src, filename string) (*goja.Program, error)
return pgm, err
}

// Open implements open() in the init context and will read and return the contents of a file.
// If the second argument is "b" it returns the data as a binary array, otherwise as a string.
// Open implements open() in the init context and will read and return the
// contents of a file. If the second argument is "b" it returns an ArrayBuffer
// instance, otherwise a string representation.
func (i *InitContext) Open(ctx context.Context, filename string, args ...string) (goja.Value, error) {
if lib.GetState(ctx) != nil {
return nil, errors.New(openCantBeUsedOutsideInitContextMsg)
Expand Down Expand Up @@ -242,7 +243,8 @@ func (i *InitContext) Open(ctx context.Context, filename string, args ...string)
}

if len(args) > 0 && args[0] == "b" {
return i.runtime.ToValue(data), nil
ab := i.runtime.NewArrayBuffer(data)
Copy link
Contributor

@mstoykov mstoykov Apr 15, 2021

Choose a reason for hiding this comment

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

here @imiric , there is no point in having ab ;)

This (for ArrayBuffer) is done in another 5 places probably

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hhmm how would I get the pointer to it otherwise? Parenthesis wouldn't work in this case.

Though I did also spot msg := message.Export() which is not needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I also forget that you can't directly get the pointer from the function call 🤦

On the other hand it seems like ... you don't need to? I mean do you really need to give a pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I err'd on the assumption that it's better to pass around pointers to this data than incur copying penalties, but considering the internal implementation is a pointer I guess it would be safe to return the struct itself.

Do you think this is worthwhile changing?

Copy link
Contributor

Choose a reason for hiding this comment

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

as my original comment suggests, I don't think it is a requirement.

In general, this means that it will now need to dereference 2 pointers in order to get to the actual data which is objectively worse, but not enough to matter in the grand scheme of things

return i.runtime.ToValue(&ab), nil
}
return i.runtime.ToValue(string(data)), nil
}
32 changes: 16 additions & 16 deletions js/initcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,24 +239,22 @@ func TestInitContextRequire(t *testing.T) {
})
}

func createAndReadFile(t *testing.T, file string, content []byte, expectedLength int, binary bool) (*BundleInstance, error) {
func createAndReadFile(t *testing.T, file string, content []byte, expectedLength int, binary string) (*BundleInstance, error) {
t.Helper()
fs := afero.NewMemMapFs()
assert.NoError(t, fs.MkdirAll("/path/to", 0o755))
assert.NoError(t, afero.WriteFile(fs, "/path/to/"+file, content, 0o644))

binaryArg := ""
if binary {
binaryArg = ",\"b\""
}

data := fmt.Sprintf(`
export let data = open("/path/to/%s"%s);
let binArg = "%s";
export let data = open("/path/to/%s", binArg);
var expectedLength = %d;
if (data.length != expectedLength) {
throw new Error("Length not equal, expected: " + expectedLength + ", actual: " + data.length);
var len = binArg === "b" ? "byteLength" : "length";
if (data[len] != expectedLength) {
throw new Error("Length not equal, expected: " + expectedLength + ", actual: " + data[len]);
}
export default function() {}
`, file, binaryArg, expectedLength)
`, binary, file, expectedLength)
b, err := getSimpleBundle(t, "/path/to/script.js", data, fs)

if !assert.NoError(t, err) {
Expand All @@ -282,8 +280,9 @@ func TestInitContextOpen(t *testing.T) {
//{[]byte{00, 36, 32, 127}, "utf-16", 2}, // $€
}
for _, tc := range testCases {
tc := tc
t.Run(tc.file, func(t *testing.T) {
bi, err := createAndReadFile(t, tc.file, tc.content, tc.length, false)
bi, err := createAndReadFile(t, tc.file, tc.content, tc.length, "")
if !assert.NoError(t, err) {
return
}
Expand All @@ -292,12 +291,12 @@ func TestInitContextOpen(t *testing.T) {
}

t.Run("Binary", func(t *testing.T) {
bi, err := createAndReadFile(t, "/path/to/file.bin", []byte("hi!\x0f\xff\x01"), 6, true)
bi, err := createAndReadFile(t, "/path/to/file.bin", []byte("hi!\x0f\xff\x01"), 6, "b")
if !assert.NoError(t, err) {
return
}
bytes := []byte{104, 105, 33, 15, 255, 1}
assert.Equal(t, bytes, bi.Runtime.Get("data").Export())
buf := bi.Runtime.NewArrayBuffer([]byte{104, 105, 33, 15, 255, 1})
assert.Equal(t, buf, bi.Runtime.Get("data").Export())
})

testdata := map[string]string{
Expand All @@ -306,8 +305,9 @@ func TestInitContextOpen(t *testing.T) {
}

for name, loadPath := range testdata {
loadPath := loadPath
t.Run(name, func(t *testing.T) {
_, err := createAndReadFile(t, loadPath, []byte("content"), 7, false)
_, err := createAndReadFile(t, loadPath, []byte("content"), 7, "")
if !assert.NoError(t, err) {
return
}
Expand Down Expand Up @@ -409,7 +409,7 @@ func TestRequestWithBinaryFile(t *testing.T) {

v, err := bi.exports[consts.DefaultFn](goja.Undefined())
assert.NoError(t, err)
assert.NotNil(t, v)
require.NotNil(t, v)
assert.Equal(t, true, v.Export())

<-ch
Expand Down
18 changes: 12 additions & 6 deletions js/modules/k6/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import (
"golang.org/x/crypto/md4"
"golang.org/x/crypto/ripemd160"

"github.com/dop251/goja"

"github.com/loadimpact/k6/js/common"
)

Expand All @@ -52,16 +54,18 @@ func New() *Crypto {
}

// RandomBytes returns random data of the given size.
func (*Crypto) RandomBytes(ctx context.Context, size int) []byte {
func (*Crypto) RandomBytes(ctx context.Context, size int) *goja.ArrayBuffer {
rt := common.GetRuntime(ctx)
if size < 1 {
common.Throw(common.GetRuntime(ctx), errors.New("invalid size"))
common.Throw(rt, errors.New("invalid size"))
}
bytes := make([]byte, size)
_, err := rand.Read(bytes)
if err != nil {
common.Throw(common.GetRuntime(ctx), err)
common.Throw(rt, err)
}
return bytes
ab := rt.NewArrayBuffer(bytes)
return &ab
}

// Md4 returns the MD4 hash of input in the given encoding.
Expand Down Expand Up @@ -171,6 +175,7 @@ func (hasher *Hasher) Update(input interface{}) {
// Digest returns the hash value in the given encoding.
func (hasher *Hasher) Digest(outputEncoding string) interface{} {
sum := hasher.hash.Sum(nil)
rt := common.GetRuntime(hasher.ctx)

switch outputEncoding {
case "base64":
Expand All @@ -186,11 +191,12 @@ func (hasher *Hasher) Digest(outputEncoding string) interface{} {
return hex.EncodeToString(sum)

case "binary":
return sum
ab := rt.NewArrayBuffer(sum)
return &ab

default:
err := errors.New("Invalid output encoding: " + outputEncoding)
common.Throw(common.GetRuntime(hasher.ctx), err)
common.Throw(rt, err)
}

return ""
Expand Down
8 changes: 4 additions & 4 deletions js/modules/k6/crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ func TestCryptoAlgorithms(t *testing.T) {

t.Run("RandomBytesSuccess", func(t *testing.T) {
_, err := rt.RunString(`
var bytes = crypto.randomBytes(5);
if (bytes.length !== 5) {
throw new Error("Incorrect size: " + bytes.length);
var buf = crypto.randomBytes(5);
if (buf.byteLength !== 5) {
throw new Error("Incorrect size: " + buf.byteLength);
}`)

assert.NoError(t, err)
Expand Down Expand Up @@ -303,7 +303,7 @@ func TestOutputEncoding(t *testing.T) {
return true;
}
var resultBinary = hasher.digest("binary");
var resultBinary = new Uint8Array(hasher.digest("binary"));
if (!arraysEqual(resultBinary, correctBinary)) {
throw new Error("Binary encoding mismatch: " + JSON.stringify(resultBinary));
}
Expand Down
18 changes: 14 additions & 4 deletions js/modules/k6/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ func (e *Encoding) B64encode(ctx context.Context, input interface{}, encoding st
}

// B64decode returns the decoded data of the base64 encoded input string using
// the given encoding.
func (e *Encoding) B64decode(ctx context.Context, input string, encoding string) string {
// the given encoding. If format is "s" it returns the data as a string,
// otherwise as an ArrayBuffer.
func (e *Encoding) B64decode(ctx context.Context, input, encoding, format string) interface{} {
var output []byte
var err error

Expand All @@ -73,9 +74,18 @@ func (e *Encoding) B64decode(ctx context.Context, input string, encoding string)
output, err = base64.StdEncoding.DecodeString(input)
}

rt := common.GetRuntime(ctx) //nolint: ifshort
if err != nil {
common.Throw(common.GetRuntime(ctx), err)
common.Throw(rt, err)
}

var out interface{}
if format == "s" {
out = string(output)
} else {
ab := rt.NewArrayBuffer(output)
out = &ab
}

return string(output)
return out
}
37 changes: 27 additions & 10 deletions js/modules/k6/encoding/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
)

func TestEncodingAlgorithms(t *testing.T) {
t.Parallel()
if testing.Short() {
return
}
Expand All @@ -54,9 +55,12 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("DefaultDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "hello world";
var decoded = encoding.b64decode("aGVsbG8gd29ybGQ=");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
var decBin = encoding.b64decode("aGVsbG8gd29ybGQ=");

var decText = String.fromCharCode.apply(null, new Uint8Array(decBin));
decText = decodeURIComponent(escape(decText));
if (decText !== correct) {
throw new Error("Decoding mismatch: " + decText);
}`)
assert.NoError(t, err)
})
Expand All @@ -70,6 +74,17 @@ func TestEncodingAlgorithms(t *testing.T) {
}`)
assert.NoError(t, err)
})
t.Run("DefaultArrayBufferDec", func(t *testing.T) { //nolint: paralleltest // weird that it triggers here, and these tests can't be parallel
_, err := rt.RunString(`
var exp = "hello";
var decBin = encoding.b64decode("aGVsbG8=");
var decText = String.fromCharCode.apply(null, new Uint8Array(decBin));
decText = decodeURIComponent(escape(decText));
if (decText !== exp) {
throw new Error("Decoding mismatch: " + decText);
}`)
assert.NoError(t, err)
})
t.Run("DefaultUnicodeEnc", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "44GT44KT44Gr44Gh44Gv5LiW55WM";
Expand All @@ -82,9 +97,11 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("DefaultUnicodeDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "こんにちは世界";
var decoded = encoding.b64decode("44GT44KT44Gr44Gh44Gv5LiW55WM");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
var decBin = encoding.b64decode("44GT44KT44Gr44Gh44Gv5LiW55WM");
var decText = String.fromCharCode.apply(null, new Uint8Array(decBin));
decText = decodeURIComponent(escape(decText));
if (decText !== correct) {
throw new Error("Decoding mismatch: " + decText);
}`)
assert.NoError(t, err)
})
Expand All @@ -100,7 +117,7 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("StdDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "hello world";
var decoded = encoding.b64decode("aGVsbG8gd29ybGQ=", "std");
var decoded = encoding.b64decode("aGVsbG8gd29ybGQ=", "std", "s");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
}`)
Expand All @@ -118,7 +135,7 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("RawStdDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "hello world";
var decoded = encoding.b64decode("aGVsbG8gd29ybGQ", "rawstd");
var decoded = encoding.b64decode("aGVsbG8gd29ybGQ", "rawstd", "s");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
}`)
Expand All @@ -136,7 +153,7 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("URLDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "小飼弾..";
var decoded = encoding.b64decode("5bCP6aO85by-Li4=", "url");
var decoded = encoding.b64decode("5bCP6aO85by-Li4=", "url", "s");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
}`)
Expand All @@ -154,7 +171,7 @@ func TestEncodingAlgorithms(t *testing.T) {
t.Run("RawURLDec", func(t *testing.T) {
_, err := rt.RunString(`
var correct = "小飼弾..";
var decoded = encoding.b64decode("5bCP6aO85by-Li4", "rawurl");
var decoded = encoding.b64decode("5bCP6aO85by-Li4", "rawurl", "s");
if (decoded !== correct) {
throw new Error("Decoding mismatch: " + decoded);
}`)
Expand Down
2 changes: 2 additions & 0 deletions js/modules/k6/http/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func (h *HTTP) Request(ctx context.Context, method string, url goja.Value, args
if err != nil {
return nil, err
}
processResponse(ctx, resp, req.ResponseType)
return h.responseFromHttpext(resp), nil
}

Expand Down Expand Up @@ -450,6 +451,7 @@ func (h *HTTP) Batch(ctx context.Context, reqsV goja.Value) (goja.Value, error)
errs := httpext.MakeBatchRequests(
ctx, batchReqs, reqCount,
int(state.Options.Batch.Int64), int(state.Options.BatchPerHost.Int64),
processResponse,
)

for i := 0; i < reqCount; i++ {
Expand Down
Loading