diff --git a/.golangci.yml b/.golangci.yml index 9578f8c..b99e10b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,7 +19,6 @@ linters: - gochecknoglobals - lll - wrapcheck - - gochecknoinits # deprecated - deadcode - nosnakecase diff --git a/g0/assert.go b/g0/addon/assert.go similarity index 88% rename from g0/assert.go rename to g0/addon/assert.go index 8f04347..fd1ba07 100644 --- a/g0/assert.go +++ b/g0/addon/assert.go @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -package g0 +package addon import ( "errors" @@ -18,11 +18,22 @@ import ( "go.k6.io/k6/metrics" ) +type TestingT interface { + assert.TestingT + require.TestingT + + Check(string, bool) +} + type checker struct { vu modules.VU fail bool } +func NewTestingT(vu modules.VU, fail bool) TestingT { + return &checker{vu: vu, fail: fail} +} + func (c *checker) Errorf(format string, args ...interface{}) { state := c.vu.State() if state == nil { @@ -97,8 +108,7 @@ func (c *checker) FailNow() { } var ( - _ assert.TestingT = (*checker)(nil) - _ require.TestingT = (*checker)(nil) + _ TestingT = (*checker)(nil) errAssertionFailed = errors.New("assertion failed") ) diff --git a/g0/addon/transport.go b/g0/addon/transport.go new file mode 100644 index 0000000..834630e --- /dev/null +++ b/g0/addon/transport.go @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +package addon + +import ( + "bytes" + "io" + "net/http" + "strconv" + "strings" + "time" + + "go.k6.io/k6/js/modules" + khttp "go.k6.io/k6/js/modules/k6/http" + "go.k6.io/k6/lib/netext/httpext" +) + +type tripware struct { + vu modules.VU +} + +var _ http.RoundTripper = (*tripware)(nil) + +func NewTransport(vu modules.VU) http.RoundTripper { + return &tripware{vu: vu} +} + +const ( + httpTimeout = 60 * time.Second + protoFields = 2 +) + +func (t *tripware) toParsedRequest(req *http.Request) (*httpext.ParsedHTTPRequest, error) { + state := t.vu.State() + if state == nil { + return nil, khttp.ErrHTTPForbiddenInInitContext + } + + u := req.URL.String() + + url, err := httpext.NewURL(u, u) + if err != nil { + return nil, err + } + + preq := &httpext.ParsedHTTPRequest{ // nolint:exhaustruct + URL: &url, + Req: req, + Timeout: httpTimeout, + Throw: state.Options.Throw.Bool, + Redirects: state.Options.MaxRedirects, + Cookies: make(map[string]*httpext.HTTPRequestCookie), + TagsAndMeta: t.vu.State().Tags.GetCurrentValues(), + } + + if state.Options.DiscardResponseBodies.Bool { + preq.ResponseType = httpext.ResponseTypeNone + } else { + preq.ResponseType = httpext.ResponseTypeBinary + } + + if req.Body != nil { + data, err := io.ReadAll(req.Body) + if err != nil { + return nil, err + } + + preq.Body = bytes.NewBuffer(data) + + req.Body.Close() + } + + preq.Req.Header.Set("User-Agent", state.Options.UserAgent.String) + + return preq, nil +} + +func (t *tripware) toResponse(req *http.Request, eresp *httpext.Response) (*http.Response, error) { + resp := new(http.Response) + + resp.Request = req + + resp.Header = http.Header{} + + for k, v := range eresp.Headers { + resp.Header.Set(k, v) + } + + resp.Proto = eresp.Proto + fields := strings.SplitN(resp.Proto, "/", protoFields) + + if major, err := strconv.Atoi(fields[0]); err == nil { + resp.ProtoMajor = major + } + + if len(fields) > 0 { + if minor, err := strconv.Atoi(fields[1]); err == nil { + resp.ProtoMinor = minor + } + } + + if eresp.Body != nil { + if data, ok := eresp.Body.([]byte); ok { + resp.Body = io.NopCloser(bytes.NewBuffer(data)) + } + } + + resp.Status = eresp.StatusText + resp.StatusCode = eresp.Status + + return resp, nil +} + +func (t *tripware) RoundTrip(req *http.Request) (*http.Response, error) { + preq, err := t.toParsedRequest(req) + if err != nil { + return nil, err + } + + resp, err := httpext.MakeRequest(t.vu.Context(), t.vu.State(), preq) + if err != nil { + return nil, err + } + + return t.toResponse(req, resp) +} diff --git a/g0/bootstrap.go b/g0/bootstrap.go new file mode 100644 index 0000000..454b41e --- /dev/null +++ b/g0/bootstrap.go @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Iván Szkiba +// +// SPDX-License-Identifier: MIT + +package g0 + +import ( + "github.com/szkiba/xk6-g0/internal/builtin/gjson" + "github.com/szkiba/xk6-g0/internal/builtin/gofakeit" + "github.com/szkiba/xk6-g0/internal/builtin/goquery" + "github.com/szkiba/xk6-g0/internal/builtin/jsonpath" + "github.com/szkiba/xk6-g0/internal/builtin/jsonschema" + "github.com/szkiba/xk6-g0/internal/builtin/logrus" + "github.com/szkiba/xk6-g0/internal/builtin/resty" + "github.com/szkiba/xk6-g0/internal/builtin/stdlib" + "github.com/szkiba/xk6-g0/internal/builtin/testify" + "go.k6.io/k6/js/modules" +) + +func registerBuiltins() { + RegisterExports(stdlib.Exports) + RegisterExports(logrus.Exports) + RegisterExports(resty.Exports) + RegisterExports(testify.Exports) + RegisterExports(goquery.Exports) + RegisterExports(gjson.Exports) + RegisterExports(jsonpath.Exports) + RegisterExports(jsonschema.Exports) + RegisterExports(gofakeit.Exports) +} + +func registerExtension() { + modules.Register("k6/x/g0", New()) +} + +func Bootstrap() { + registerBuiltins() + redirectStdin() + registerExtension() +} diff --git a/g0/cli.go b/g0/cli.go index 2cd7954..e5b1389 100644 --- a/g0/cli.go +++ b/g0/cli.go @@ -37,7 +37,7 @@ func isRunCommand(args []string) (bool, int) { return true, scriptIndex } -func RedirectStdin() { +func redirectStdin() { isRun, scriptIndex := isRunCommand(os.Args) if !isRun { return diff --git a/g0/exports.go b/g0/exports.go index 86e9667..404d41b 100644 --- a/g0/exports.go +++ b/g0/exports.go @@ -8,15 +8,6 @@ import ( "sync" "github.com/imdario/mergo" - "github.com/szkiba/xk6-g0/internal/builtin/gjson" - "github.com/szkiba/xk6-g0/internal/builtin/gofakeit" - "github.com/szkiba/xk6-g0/internal/builtin/goquery" - "github.com/szkiba/xk6-g0/internal/builtin/jsonpath" - jsonschema "github.com/szkiba/xk6-g0/internal/builtin/jsonscema" - "github.com/szkiba/xk6-g0/internal/builtin/logrus" - "github.com/szkiba/xk6-g0/internal/builtin/resty" - "github.com/szkiba/xk6-g0/internal/builtin/stdlib" - "github.com/szkiba/xk6-g0/internal/builtin/testify" "github.com/traefik/yaegi/interp" "go.k6.io/k6/js/modules" ) @@ -55,15 +46,3 @@ func (r *exportsRegistry) merge(vu modules.VU) (interp.Exports, error) { //nolin return symbols, nil } - -func init() { - RegisterExports(stdlib.Exports) - RegisterExports(logrus.Exports) - RegisterExports(resty.Exports) - RegisterExports(testify.Exports) - RegisterExports(goquery.Exports) - RegisterExports(gjson.Exports) - RegisterExports(jsonpath.Exports) - RegisterExports(jsonschema.Exports) - RegisterExports(gofakeit.Exports) -} diff --git a/g0/module.go b/g0/module.go index a70a7d1..290d4d8 100644 --- a/g0/module.go +++ b/g0/module.go @@ -7,6 +7,7 @@ package g0 import ( "os" + "github.com/szkiba/xk6-g0/g0/addon" "github.com/szkiba/xk6-g0/internal/builtin/testify/assertions" "github.com/szkiba/xk6-g0/internal/builtin/testify/requirements" "github.com/traefik/yaegi/interp" @@ -27,8 +28,8 @@ func (root *RootModule) NewModuleInstance(vu modules.VU) modules.Instance { // n mod := &Module{ //nolint:exhaustruct vu: vu, yaegi: yaegi, - assert: assertions.New(&checker{fail: false, vu: vu}), - require: requirements.New(&checker{fail: true, vu: vu}), + assert: assertions.New(addon.NewTestingT(vu, false)), + require: requirements.New(addon.NewTestingT(vu, true)), } if err := mod.init(os.Getenv(envScript)); err != nil { diff --git a/register.go b/init.go similarity index 59% rename from register.go rename to init.go index 2cbee6b..80f66c9 100644 --- a/register.go +++ b/init.go @@ -6,14 +6,8 @@ package g0 import ( "github.com/szkiba/xk6-g0/g0" - "go.k6.io/k6/js/modules" ) func init() { //nolint:gochecknoinits - register() - g0.RedirectStdin() -} - -func register() { - modules.Register("k6/x/g0", g0.New()) + g0.Bootstrap() } diff --git a/internal/builtin/goquery/exports.go b/internal/builtin/goquery/exports.go index 22314ac..c3c6840 100644 --- a/internal/builtin/goquery/exports.go +++ b/internal/builtin/goquery/exports.go @@ -10,7 +10,7 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/imdario/mergo" - "github.com/szkiba/xk6-g0/internal/builtin/stdlib" + "github.com/szkiba/xk6-g0/g0/addon" "github.com/traefik/yaegi/interp" "go.k6.io/k6/js/modules" ) @@ -21,7 +21,7 @@ var Symbols = interp.Exports{} func Exports(vu modules.VU) interp.Exports { newDocument := func(url string) (*goquery.Document, error) { - client := &http.Client{Transport: stdlib.NewTransport(vu)} + client := &http.Client{Transport: addon.NewTransport(vu)} resp, err := client.Get(url) if err != nil { diff --git a/internal/builtin/resty/exports.go b/internal/builtin/resty/exports.go index 5ea479f..17dba99 100644 --- a/internal/builtin/resty/exports.go +++ b/internal/builtin/resty/exports.go @@ -9,7 +9,7 @@ import ( "github.com/go-resty/resty/v2" "github.com/imdario/mergo" - "github.com/szkiba/xk6-g0/internal/builtin/stdlib" + "github.com/szkiba/xk6-g0/g0/addon" "github.com/traefik/yaegi/interp" "go.k6.io/k6/js/modules" ) @@ -19,7 +19,7 @@ var Symbols = interp.Exports{} //go:generate yaegi extract -name resty github.com/go-resty/resty/v2 func Exports(vu modules.VU) interp.Exports { - transport := stdlib.NewTransport(vu) + transport := addon.NewTransport(vu) newClient := func() *resty.Client { client := resty.New() diff --git a/internal/builtin/stdlib/http.go b/internal/builtin/stdlib/http.go index b360cdc..cfd72c7 100644 --- a/internal/builtin/stdlib/http.go +++ b/internal/builtin/stdlib/http.go @@ -5,132 +5,16 @@ package stdlib import ( - "bytes" - "io" "net/http" "reflect" - "strconv" - "strings" - "time" + "github.com/szkiba/xk6-g0/g0/addon" "github.com/traefik/yaegi/interp" "go.k6.io/k6/js/modules" - khttp "go.k6.io/k6/js/modules/k6/http" - "go.k6.io/k6/lib/netext/httpext" ) -type tripware struct { - vu modules.VU -} - -const ( - httpTimeout = 60 * time.Second - protoFields = 2 -) - -func (t *tripware) toParsedRequest(req *http.Request) (*httpext.ParsedHTTPRequest, error) { - state := t.vu.State() - if state == nil { - return nil, khttp.ErrHTTPForbiddenInInitContext - } - - u := req.URL.String() - - url, err := httpext.NewURL(u, u) - if err != nil { - return nil, err - } - - preq := &httpext.ParsedHTTPRequest{ - URL: &url, - Req: req, - Timeout: httpTimeout, - Throw: state.Options.Throw.Bool, - Redirects: state.Options.MaxRedirects, - Cookies: make(map[string]*httpext.HTTPRequestCookie), - TagsAndMeta: t.vu.State().Tags.GetCurrentValues(), - } - - if state.Options.DiscardResponseBodies.Bool { - preq.ResponseType = httpext.ResponseTypeNone - } else { - preq.ResponseType = httpext.ResponseTypeBinary - } - - if req.Body != nil { - data, err := io.ReadAll(req.Body) - if err != nil { - return nil, err - } - - preq.Body = bytes.NewBuffer(data) - - req.Body.Close() - } - - preq.Req.Header.Set("User-Agent", state.Options.UserAgent.String) - - return preq, nil -} - -func (t *tripware) toResponse(req *http.Request, eresp *httpext.Response) (*http.Response, error) { - resp := new(http.Response) - - resp.Request = req - - resp.Header = http.Header{} - - for k, v := range eresp.Headers { - resp.Header.Set(k, v) - } - - resp.Proto = eresp.Proto - fields := strings.SplitN(resp.Proto, "/", protoFields) - - if major, err := strconv.Atoi(fields[0]); err == nil { - resp.ProtoMajor = major - } - - if len(fields) > 0 { - if minor, err := strconv.Atoi(fields[1]); err == nil { - resp.ProtoMinor = minor - } - } - - if eresp.Body != nil { - if data, ok := eresp.Body.([]byte); ok { - resp.Body = io.NopCloser(bytes.NewBuffer(data)) - } - } - - resp.Status = eresp.StatusText - resp.StatusCode = eresp.Status - - return resp, nil -} - -func (t *tripware) RoundTrip(req *http.Request) (*http.Response, error) { - preq, err := t.toParsedRequest(req) - if err != nil { - return nil, err - } - - resp, err := httpext.MakeRequest(t.vu.Context(), t.vu.State(), preq) - if err != nil { - return nil, err - } - - return t.toResponse(req, resp) -} - -var _ http.RoundTripper = (*tripware)(nil) - -func NewTransport(vu modules.VU) http.RoundTripper { - return &tripware{vu: vu} -} - func httpExports(vu modules.VU) interp.Exports { - transport := NewTransport(vu) + transport := addon.NewTransport(vu) client := &http.Client{Transport: transport} return interp.Exports{ diff --git a/register_test.go b/register_test.go deleted file mode 100644 index 1b7b542..0000000 --- a/register_test.go +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Iván Szkiba -// -// SPDX-License-Identifier: MIT - -package g0 - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRegister(t *testing.T) { - t.Parallel() - - assert.Panics(t, register) // already registered -}