From 3df116e0a3d5a29497c1ecffb96d81fb5ab3716e Mon Sep 17 00:00:00 2001 From: Hendrik Haase Date: Tue, 27 Aug 2024 10:28:12 +0200 Subject: [PATCH] add missing parts --- examples/main.go | 15 +++--- group.go | 53 ++++++++++++++++++ http/http_endpoint.go | 4 +- http/options.go | 38 +++++++------ http/query_param/usage_opts.go | 22 -------- http/url_builder/options.go | 19 +++++++ .../query_param.go} | 54 ++++++++----------- .../query_param_options.go} | 41 +++++++++++--- http/{ => url_builder}/url_builder.go | 15 ++++-- 9 files changed, 172 insertions(+), 89 deletions(-) create mode 100644 group.go delete mode 100644 http/query_param/usage_opts.go create mode 100644 http/url_builder/options.go rename http/{query_param/parameter.go => url_builder/query_param.go} (60%) rename http/{query_param/value_opts.go => url_builder/query_param_options.go} (55%) rename http/{ => url_builder}/url_builder.go (81%) diff --git a/examples/main.go b/examples/main.go index 81cb19f..4442ed2 100644 --- a/examples/main.go +++ b/examples/main.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/HenriBeck/goload" goload_http "github.com/HenriBeck/goload/http" - "github.com/HenriBeck/goload/http/query_param" + "github.com/HenriBeck/goload/http/url_builder" "github.com/HenriBeck/goload/pacer" "time" ) @@ -14,20 +14,21 @@ func main() { goload.WithDuration(5*time.Minute), //goload.WithLinearRampUpPacer(pacer.Rate{Freq: 30, Per: time.Minute}, pacer.Rate{Freq: 2, Per: time.Second}, 1*time.Minute), goload.WithConstantPacer(pacer.Rate{Freq: 2, Per: time.Second}), + goload_http.WithBasePath("http://test.k6.io"), goload.WithExecutors( goload_http.NewEndpoint( goload_http.WithName("test"), - goload_http.WithURL("http://test.k6.io"), + goload_http.WithURL("/"), goload_http.WithValidateResponse(goload_http.Status2xxResponseValidation), ), goload_http.NewEndpoint( goload_http.WithName("pi"), goload_http.WithURLBuilder( - goload_http.WithRawURL("https://test.k6.io/pi.php"), - goload_http.WithQueryParams( - query_param.New( - query_param.WithName("decimals"), - query_param.WithRandomNumberValue(1, 20), + url_builder.WithRawURL("/pi.php"), + url_builder.WithQueryParams( + url_builder.NewQueryParameter( + url_builder.WithParamName("decimals"), + url_builder.WithRandomNumberParamValue(1, 20), ), ), ), diff --git a/group.go b/group.go new file mode 100644 index 0000000..fde128e --- /dev/null +++ b/group.go @@ -0,0 +1,53 @@ +package goload + +import ( + "context" + "github.com/mroth/weightedrand/v2" + "time" +) + +type executorGroup struct { + name string + chooser *weightedrand.Chooser[Executor, int] + weight int + timeout time.Duration +} + +// TODO: add options +func NewGroup(name string, weight int, executors []Executor) Executor { + if len(executors) == 0 { + panic("group can't be empty") + } + choises := make([]weightedrand.Choice[Executor, int], 0, len(executors)) + for _, exec := range executors { + choises = append(choises, weightedrand.NewChoice(exec, exec.Options().Weight)) + } + chooser, err := weightedrand.NewChooser( + choises..., + ) + if err != nil { + panic(err) + } + + return &executorGroup{ + name: name, + chooser: chooser, + weight: weight, + timeout: 0, + } +} + +func (e *executorGroup) Execute(ctx context.Context) ExecutionResponse { + return e.chooser.Pick().Execute(ctx) +} + +func (e *executorGroup) Name() string { + return e.name +} + +func (e *executorGroup) Options() *ExecutorOptions { + return &ExecutorOptions{ + Weight: e.weight, + Timeout: e.timeout, + } +} diff --git a/http/http_endpoint.go b/http/http_endpoint.go index 83c5d5e..5562e7b 100644 --- a/http/http_endpoint.go +++ b/http/http_endpoint.go @@ -11,6 +11,8 @@ import ( "time" ) +var basePath *string + type EndpointOption func(ep *endpoint) func NewEndpoint(opts ...EndpointOption) goload.Executor { @@ -96,7 +98,7 @@ func (e *endpoint) Options() *goload.ExecutorOptions { } func Status2xxResponseValidation(response *http.Response) error { - if response.StatusCode >= 200 && response.StatusCode < 300 { + if response.StatusCode < 200 && response.StatusCode >= 300 { return fmt.Errorf("non 2xx status code: %d", response.StatusCode) } return nil diff --git a/http/options.go b/http/options.go index e8b35dd..e8471cc 100644 --- a/http/options.go +++ b/http/options.go @@ -2,7 +2,8 @@ package goload_http import ( "errors" - "github.com/HenriBeck/goload/http/query_param" + "github.com/HenriBeck/goload" + "github.com/HenriBeck/goload/http/url_builder" "io" "net/http" "net/url" @@ -65,11 +66,18 @@ func WithClient(client http.Client) EndpointOption { func WithURL(rawURL string) EndpointOption { return func(ep *endpoint) { - u, err := url.Parse(rawURL) - if err != nil { - panic(err) - } ep.urlFunc = func() *url.URL { + if basePath != nil { + var err error + rawURL, err = url.JoinPath(*basePath, rawURL) + if err != nil { + panic(err) + } + } + u, err := url.Parse(rawURL) + if err != nil { + panic(err) + } return u } } @@ -121,21 +129,17 @@ func WithValidateResponse(validationFunc func(response *http.Response) error) En } } -func WithURLBuilder(opts ...URLBuilderOption) EndpointOption { - builder := NewURLBuilder(opts) +func WithURLBuilder(opts ...url_builder.URLBuilderOption) EndpointOption { + builder := url_builder.NewURLBuilder(opts) return func(ep *endpoint) { - ep.urlFunc = builder.Build - } -} - -func WithRawURL(rawURL string) URLBuilderOption { - return func(builder *URLBuilder) { - builder.rawURL = rawURL + ep.urlFunc = func() *url.URL { + return builder.Build(basePath) + } } } -func WithQueryParams(queryParams ...query_param.Builder) URLBuilderOption { - return func(builder *URLBuilder) { - builder.queryParams = append(builder.queryParams, queryParams...) +func WithBasePath(path string) goload.LoadTestOption { + return func(_ *goload.LoadTestOptions) { + basePath = &path } } diff --git a/http/query_param/usage_opts.go b/http/query_param/usage_opts.go deleted file mode 100644 index 52a1be3..0000000 --- a/http/query_param/usage_opts.go +++ /dev/null @@ -1,22 +0,0 @@ -package query_param - -import "github.com/mroth/weightedrand/v2" - -func WithUsagePct(pct int) Opt { - if pct > 100 || pct < 0 { - panic("WithUsagePct value must be between 0 and 100") - } - r, err := weightedrand.NewChooser( - weightedrand.NewChoice(true, pct), - weightedrand.NewChoice(true, 100-pct), - ) - if err != nil { - panic(err) - } - - return func(param *QueryParameter) { - param.ShouldBeUsed = func() bool { - return r.Pick() - } - } -} diff --git a/http/url_builder/options.go b/http/url_builder/options.go new file mode 100644 index 0000000..f80fae2 --- /dev/null +++ b/http/url_builder/options.go @@ -0,0 +1,19 @@ +package url_builder + +func WithRawURL(rawURL string) URLBuilderOption { + return func(builder *URLBuilder) { + builder.rawURL = rawURL + } +} + +func WithQueryParams(queryParams ...QueryParamBuilder) URLBuilderOption { + return func(builder *URLBuilder) { + builder.queryParams = append(builder.queryParams, queryParams...) + } +} + +func WithURLParam(key string, values []string) URLBuilderOption { + return func(builder *URLBuilder) { + builder.urlParameterRandomizers = append(builder.urlParameterRandomizers, URLParameterRandomizer{key, values}) + } +} diff --git a/http/query_param/parameter.go b/http/url_builder/query_param.go similarity index 60% rename from http/query_param/parameter.go rename to http/url_builder/query_param.go index 2de44f4..6445ba4 100644 --- a/http/query_param/parameter.go +++ b/http/url_builder/query_param.go @@ -1,4 +1,4 @@ -package query_param +package url_builder import ( "github.com/HenriBeck/goload/utils/random" @@ -6,7 +6,7 @@ import ( "net/url" ) -type Builder interface { +type QueryParamBuilder interface { Build() url.Values } @@ -20,9 +20,9 @@ type QueryParameter struct { Value ValuesFn } -type Opt func(param *QueryParameter) +type QueryParameterOption func(param *QueryParameter) -func New(opts ...Opt) *QueryParameter { +func NewQueryParameter(opts ...QueryParameterOption) *QueryParameter { param := &QueryParameter{ //set defaults ShouldBeUsed: UseAlways(), @@ -31,7 +31,7 @@ func New(opts ...Opt) *QueryParameter { opt(param) } if param.Name == nil || param.Value == nil { - panic("query_param.New must contain opts for name and value") + panic("query_param.NewQueryParameter must contain opts for name and value") } return param } @@ -53,21 +53,29 @@ func UseAlways() ShouldBeUsedFn { } } -func WithName(name string) Opt { - return func(param *QueryParameter) { - param.Name = func() string { - return name - } +type oneOfParam struct { + params []QueryParamBuilder +} + +func WithOneOfParam(params ...QueryParamBuilder) QueryParamBuilder { + if len(params) == 0 { + panic("NewOneOfParam must contain at least one parameter") } + return &oneOfParam{params: params} } -type pctParam struct { +func (p *oneOfParam) Build() url.Values { + index := random.Number(0, int64(len(p.params)-1)) + return p.params[index].Build() +} + +type chanceParam struct { chance int - param Builder + param QueryParamBuilder r *weightedrand.Chooser[bool, int] } -func NewPctParam(chance int, param Builder) Builder { +func NewParamWithUsageChange(chance int, param QueryParamBuilder) QueryParamBuilder { if chance > 100 || chance < 0 { panic("chance value must be between 0 and 100") } @@ -79,28 +87,12 @@ func NewPctParam(chance int, param Builder) Builder { panic(err) } - return &pctParam{chance: chance, param: param, r: r} + return &chanceParam{chance: chance, param: param, r: r} } -func (p *pctParam) Build() url.Values { +func (p *chanceParam) Build() url.Values { if p.r.Pick() { return p.param.Build() } return url.Values{} } - -type oneOfParam struct { - params []Builder -} - -func (o *oneOfParam) Build() url.Values { - index := random.Number(0, int64(len(o.params)-1)) - return o.params[index].Build() -} - -func NewOneOfParam(params ...Builder) Builder { - if len(params) == 0 { - panic("params cant be empty") - } - return &oneOfParam{params: params} -} diff --git a/http/query_param/value_opts.go b/http/url_builder/query_param_options.go similarity index 55% rename from http/query_param/value_opts.go rename to http/url_builder/query_param_options.go index 3ca5f6f..086f9a9 100644 --- a/http/query_param/value_opts.go +++ b/http/url_builder/query_param_options.go @@ -1,4 +1,4 @@ -package query_param +package url_builder import ( "github.com/HenriBeck/goload/utils/random" @@ -6,7 +6,34 @@ import ( "strconv" ) -func WithValue(value string) Opt { +func WithParamName(name string) QueryParameterOption { + return func(param *QueryParameter) { + param.Name = func() string { + return name + } + } +} + +func WithParamUsagePercentage(pct int) QueryParameterOption { + if pct > 100 || pct < 0 { + panic("WithParamUsagePercentage value must be between 0 and 100") + } + r, err := weightedrand.NewChooser( + weightedrand.NewChoice(true, pct), + weightedrand.NewChoice(true, 100-pct), + ) + if err != nil { + panic(err) + } + + return func(param *QueryParameter) { + param.ShouldBeUsed = func() bool { + return r.Pick() + } + } +} + +func WithParamValue(value string) QueryParameterOption { return func(param *QueryParameter) { param.Value = func() []string { return []string{value} @@ -14,7 +41,7 @@ func WithValue(value string) Opt { } } -func WithSampledValues(min int64, max int64, opts []string) Opt { +func WithSampledParamValues(min int64, max int64, opts []string) QueryParameterOption { sampler := random.NewSampler(opts) return func(param *QueryParameter) { n := random.Number(min, max) @@ -29,9 +56,9 @@ type WeightedValueOpt struct { Weight int } -func WithWeightedValue(opts ...WeightedValueOpt) Opt { +func WithWeightedParamValue(opts ...WeightedValueOpt) QueryParameterOption { if len(opts) == 0 { - panic("WithWeightedValue opts can't be empty") + panic("WithWeightedParamValue opts can't be empty") } values := make([]weightedrand.Choice[string, int], 0, len(opts)) for _, opt := range opts { @@ -50,7 +77,7 @@ func WithWeightedValue(opts ...WeightedValueOpt) Opt { } } -func WithOneOfValue(values []string) Opt { +func WithOneOfParamValue(values []string) QueryParameterOption { if len(values) == 0 { panic("one off values needs at least one option") } @@ -62,7 +89,7 @@ func WithOneOfValue(values []string) Opt { } } -func WithRandomNumberValue(min int64, max int64) Opt { +func WithRandomNumberParamValue(min int64, max int64) QueryParameterOption { return func(param *QueryParameter) { param.Value = func() []string { number := random.Number(min, max) diff --git a/http/url_builder.go b/http/url_builder/url_builder.go similarity index 81% rename from http/url_builder.go rename to http/url_builder/url_builder.go index 595daa5..d31f458 100644 --- a/http/url_builder.go +++ b/http/url_builder/url_builder.go @@ -1,7 +1,6 @@ -package goload_http +package url_builder import ( - "github.com/HenriBeck/goload/http/query_param" "github.com/HenriBeck/goload/utils/random" "net/url" "strings" @@ -10,7 +9,7 @@ import ( type URLBuilder struct { rawURL string urlParameterRandomizers []URLParameterRandomizer - queryParams []query_param.Builder + queryParams []QueryParamBuilder } type URLBuilderOption func(*URLBuilder) @@ -29,7 +28,7 @@ func NewURLBuilder(opts []URLBuilderOption) *URLBuilder { return &urlBuilder } -func (builder *URLBuilder) Build() *url.URL { +func (builder *URLBuilder) Build(basePath *string) *url.URL { q := url.Values{} for _, param := range builder.queryParams { @@ -43,6 +42,14 @@ func (builder *URLBuilder) Build() *url.URL { query := q.Encode() rawURL := builder.rawURL + if basePath != nil { + var err error + rawURL, err = url.JoinPath(*basePath, builder.rawURL) + if err != nil { + panic(err) + } + } + for _, u := range builder.urlParameterRandomizers { v := u.GetValue() rawURL = strings.Replace(rawURL, u.key, v, 1)