Skip to content
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
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
## About

`benchttp/engine` runs benchmarks on specified endpoints.
Highly configurable, it can serve many purposes such as monitoring (paired with our [webapp](https://www.benchttp.app)), CI tool (with output templates) or as a simple testing tool at development time.
Highly configurable, it can serve many purposes such as monitoring (paired with our [webapp](https://www.benchttp.app)), CI integration (with test suites) or as a simple testing tool at development time.

## Installation

Expand Down Expand Up @@ -90,11 +90,6 @@ Note: the expected format for durations is `<int><unit>`, with `unit` being any

#### Output options

| CLI flag | File option | Description | Usage example |
| ----------- | ----------------- | ------------------------------- | -------------------------------- |
| `-silent` | `output.silent` | Remove convenience prints | `-silent` / `-silent=false` |
| `-template` | `output.template` | Custom output when using stdout | `-template '{{ .Metrics.Avg }}'` |

Note: the template uses Go's powerful templating engine.
To take full advantage of it, see our [templating docs](./examples/output/templating.md)
for the available fields and functions, with usage examples.
| CLI flag | File option | Description | Usage example |
| --------- | --------------- | ------------------------- | --------------------------- |
| `-silent` | `output.silent` | Remove convenience prints | `-silent` / `-silent=false` |
1 change: 0 additions & 1 deletion examples/config/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ runner:

output:
silent: true
template: "{{ .Metrics.Avg }}"
67 changes: 0 additions & 67 deletions examples/output/templating.md

This file was deleted.

6 changes: 0 additions & 6 deletions internal/cli/configflags/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,4 @@ func Bind(flagset *flag.FlagSet, dst *runner.Config) {
dst.Output.Silent,
runner.ConfigFieldsUsage[runner.ConfigFieldSilent],
)
// output template
flagset.StringVar(&dst.Output.Template,
runner.ConfigFieldTemplate,
dst.Output.Template,
runner.ConfigFieldsUsage[runner.ConfigFieldTemplate],
)
}
4 changes: 1 addition & 3 deletions internal/cli/configflags/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ func TestBind(t *testing.T) {
"-requestTimeout", "4s",
"-globalTimeout", "5s",
"-silent",
"-template", "{{ .Report.Length }}",
}

cfg := runner.Config{}
Expand All @@ -63,8 +62,7 @@ func TestBind(t *testing.T) {
GlobalTimeout: 5 * time.Second,
},
Output: runner.OutputConfig{
Silent: true,
Template: "{{ .Report.Length }}",
Silent: true,
},
}

Expand Down
8 changes: 1 addition & 7 deletions internal/configparse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ type UnmarshaledConfig struct {
} `yaml:"runner" json:"runner"`

Output struct {
Silent *bool `yaml:"silent" json:"silent"`
Template *string `yaml:"template" json:"template"`
Silent *bool `yaml:"silent" json:"silent"`
} `yaml:"output" json:"output"`

Tests []struct {
Expand Down Expand Up @@ -256,11 +255,6 @@ func newParsedConfig(uconf UnmarshaledConfig) (parsedConfig, error) { //nolint:g
pconf.add(runner.ConfigFieldSilent)
}

if template := uconf.Output.Template; template != nil {
cfg.Output.Template = *template
pconf.add(runner.ConfigFieldTemplate)
}

testSuite := uconf.Tests
if len(testSuite) == 0 {
return pconf, nil
Expand Down
3 changes: 1 addition & 2 deletions internal/configparse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,7 @@ func newExpConfig() runner.Config {
GlobalTimeout: 60 * time.Second,
},
Output: runner.OutputConfig{
Silent: true,
Template: "{{ .Metrics.Avg }}",
Silent: true,
},
Tests: []runner.TestCase{
{
Expand Down
3 changes: 1 addition & 2 deletions internal/configparse/testdata/valid/benchttp.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
"globalTimeout": "60s"
},
"output": {
"silent": true,
"template": "{{ .Metrics.Avg }}"
"silent": true
},
"tests": [
{
Expand Down
1 change: 0 additions & 1 deletion internal/configparse/testdata/valid/benchttp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ runner:

output:
silent: true
template: "{{ .Metrics.Avg }}"

tests:
- name: minimum response time
Expand Down
1 change: 0 additions & 1 deletion internal/configparse/testdata/valid/benchttp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ runner:

output:
silent: true
template: "{{ .Metrics.Avg }}"

tests:
- name: minimum response time
Expand Down
5 changes: 1 addition & 4 deletions runner/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ type Runner struct {

// Output contains options relative to the output.
type Output struct {
Silent bool
Template string
Silent bool
}

// Global represents the global configuration of the runner.
Expand Down Expand Up @@ -126,8 +125,6 @@ func (cfg Global) Override(c Global, fields ...string) Global {
cfg.Runner.GlobalTimeout = c.Runner.GlobalTimeout
case FieldSilent:
cfg.Output.Silent = c.Output.Silent
case FieldTemplate:
cfg.Output.Template = c.Output.Template
case FieldTests:
cfg.Tests = c.Tests
}
Expand Down
3 changes: 1 addition & 2 deletions runner/internal/config/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ var defaultConfig = Global{
GlobalTimeout: 30 * time.Second,
},
Output: Output{
Silent: false,
Template: "",
Silent: false,
},
}

Expand Down
2 changes: 0 additions & 2 deletions runner/internal/config/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const (
FieldRequestTimeout = "requestTimeout"
FieldGlobalTimeout = "globalTimeout"
FieldSilent = "silent"
FieldTemplate = "template"
FieldTests = "tests"
)

Expand All @@ -27,7 +26,6 @@ var FieldsUsage = map[string]string{
FieldRequestTimeout: "Timeout for each HTTP request",
FieldGlobalTimeout: "Max duration of test",
FieldSilent: "Silent mode (no write to stdout)",
FieldTemplate: "Output template",
FieldTests: "Test suite",
}

Expand Down
1 change: 0 additions & 1 deletion runner/internal/config/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func TestIsField(t *testing.T) {
{In: config.FieldRequestTimeout, Exp: true},
{In: config.FieldGlobalTimeout, Exp: true},
{In: config.FieldSilent, Exp: true},
{In: config.FieldTemplate, Exp: true},
{In: "notafield", Exp: false},
}).Run(t)
}
21 changes: 0 additions & 21 deletions runner/internal/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package report

import (
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
Expand All @@ -20,8 +19,6 @@ type Report struct {
Metrics metrics.Aggregate
Metadata Metadata
Tests tests.SuiteResult

errTemplateFailTriggered error
}

// Metadata contains contextual information about a run.
Expand Down Expand Up @@ -52,26 +49,8 @@ func New(
// String returns a default summary of the Report as a string.
func (rep *Report) String() string {
var b strings.Builder

s, err := rep.applyTemplate(rep.Metadata.Config.Output.Template)
switch {
case err == nil:
// template is non-empty and correctly executed,
// return its result instead of default summary.
rep.writeTestsResult(&b)
return s
case errors.Is(err, errTemplateSyntax):
// template is non-empty but has syntax errors,
// inform the user about it and fallback to default summary.
b.WriteString(err.Error())
b.WriteString("\nFalling back to default summary:\n")
case errors.Is(err, errTemplateEmpty):
// template is empty, use default summary.
}

rep.writeDefaultSummary(&b)
rep.writeTestsResult(&b)

return b.String()
}

Expand Down
58 changes: 11 additions & 47 deletions runner/internal/report/report_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package report_test

import (
"net/url"
"strconv"
"strings"
"testing"
"time"

Expand All @@ -15,66 +12,33 @@ import (
)

func TestReport_String(t *testing.T) {
const d = 15 * time.Second
t.Run("returns metrics summary", func(t *testing.T) {
agg, d := metricsStub()
cfg := configStub()

t.Run("return default summary if template is empty", func(t *testing.T) {
const tpl = ""

rep := report.New(newMetrics(), newConfigWithTemplate(tpl), d, tests.SuiteResult{})
rep := report.New(agg, cfg, d, tests.SuiteResult{})
checkSummary(t, rep.String())
})

t.Run("return executed template if valid", func(t *testing.T) {
const tpl = "{{ .Metrics.TotalCount }}"

m := newMetrics()
rep := report.New(m, newConfigWithTemplate(tpl), d, tests.SuiteResult{})

if got, exp := rep.String(), strconv.Itoa(m.TotalCount); got != exp {
t.Errorf("\nunexpected output\nexp %s\ngot %s", exp, got)
}
})

t.Run("fallback to default summary if template is invalid", func(t *testing.T) {
const tpl = "{{ .Marcel.Patulacci }}"

rep := report.New(newMetrics(), newConfigWithTemplate(tpl), d, tests.SuiteResult{})
got := rep.String()
split := strings.Split(got, "Falling back to default summary:\n")

if len(split) != 2 {
t.Fatalf("\nunexpected output:\n%s", got)
}

errMsg, summary := split[0], split[1]
if !strings.Contains(errMsg, "template syntax error") {
t.Errorf("\nexp template syntax error\ngot %s", errMsg)
}

checkSummary(t, summary)
})
}

// helpers

func newMetrics() metrics.Aggregate {
func metricsStub() (agg metrics.Aggregate, total time.Duration) {
return metrics.Aggregate{
FailureCount: 1,
SuccessCount: 2,
TotalCount: 3,
Min: 4 * time.Second,
Max: 6 * time.Second,
Avg: 5 * time.Second,
}
}, 15 * time.Second
}

func newConfigWithTemplate(tpl string) config.Global {
urlURL, _ := url.ParseRequestURI("https://a.b.com")
return config.Global{
Request: config.Request{URL: urlURL},
Runner: config.Runner{Requests: -1},
Output: config.Output{Template: tpl},
}
func configStub() config.Global {
cfg := config.Global{}
cfg.Request = cfg.Request.WithURL("https://a.b.com")
cfg.Runner.Requests = -1
return cfg
}

func checkSummary(t *testing.T, summary string) {
Expand Down
Loading