diff --git a/benchttp/internal/metrics/aggregate.go b/benchttp/metrics/aggregate.go similarity index 87% rename from benchttp/internal/metrics/aggregate.go rename to benchttp/metrics/aggregate.go index e01788f..779b14d 100644 --- a/benchttp/internal/metrics/aggregate.go +++ b/benchttp/metrics/aggregate.go @@ -3,19 +3,16 @@ package metrics import ( "time" - "github.com/benchttp/engine/benchttp/internal/metrics/timestats" - "github.com/benchttp/engine/benchttp/internal/recorder" + "github.com/benchttp/engine/benchttp/recorder" ) -type TimeStats = timestats.TimeStats - // Aggregate is an aggregate of metrics computed from // a slice of recorder.Record. type Aggregate struct { // ResponseTimes is the common statistics computed from a // slice recorder.Record. It offers statistics about the // recorder.Record.Time of the records. - ResponseTimes timestats.TimeStats + ResponseTimes TimeStats // StatusCodesDistribution maps each status code received to // its number of occurrence. StatusCodesDistribution map[int]int @@ -23,7 +20,7 @@ type Aggregate struct { // the combination of all recorder.Record.Events of a slice // of recorder.Record. It offers statistics about the // recorder.Events.Time of the records. - RequestEventTimes map[string]timestats.TimeStats + RequestEventTimes map[string]TimeStats // Records lists each response time received during the run. // It offers raw informarion. Records []struct { @@ -51,7 +48,7 @@ func NewAggregate(records []recorder.Record) (agg Aggregate) { } } - agg.ResponseTimes = timestats.New(times) + agg.ResponseTimes = NewTimeStats(times) agg.StatusCodesDistribution = computeStatusCodesDistribution(records) @@ -77,7 +74,7 @@ func (agg Aggregate) RequestSuccessCount() int { // Special compute helpers. -func computeRequestEventTimes(records []recorder.Record) map[string]timestats.TimeStats { +func computeRequestEventTimes(records []recorder.Record) map[string]TimeStats { events := flattenRelativeTimeEvents(records) timesByEvent := map[string][]time.Duration{} @@ -86,10 +83,10 @@ func computeRequestEventTimes(records []recorder.Record) map[string]timestats.Ti timesByEvent[e.Name] = append(timesByEvent[e.Name], e.Time) } - statsByEvent := map[string]timestats.TimeStats{} + statsByEvent := map[string]TimeStats{} for e, times := range timesByEvent { - statsByEvent[e] = timestats.New(times) + statsByEvent[e] = NewTimeStats(times) } return statsByEvent diff --git a/benchttp/internal/metrics/aggregate_test.go b/benchttp/metrics/aggregate_test.go similarity index 87% rename from benchttp/internal/metrics/aggregate_test.go rename to benchttp/metrics/aggregate_test.go index b8a2251..1d0d4d7 100644 --- a/benchttp/internal/metrics/aggregate_test.go +++ b/benchttp/metrics/aggregate_test.go @@ -5,14 +5,13 @@ import ( "testing" "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/metrics/timestats" - "github.com/benchttp/engine/benchttp/internal/recorder" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/recorder" ) func TestNewAggregate(t *testing.T) { - // Test for "response times stats" is delegated to timestats.New because - // metrics.NewAggregate does not have any specific behavior aound it. + // Test for "response times stats" is delegated to metrics.NewTimeStats + // because metrics.NewAggregate does not have any specific behavior aound it. t.Run("events times stats", func(t *testing.T) { eventsStub := func(t1, t2 time.Duration) []recorder.Event { @@ -26,7 +25,7 @@ func TestNewAggregate(t *testing.T) { {Events: eventsStub(400, 500)}, } - want := map[string]timestats.TimeStats{ + want := map[string]metrics.TimeStats{ "1": {Min: 100, Max: 400, Mean: 250, Median: 250}, "2": {Min: 200, Max: 500, Mean: 325, Median: 300}, } diff --git a/benchttp/internal/metrics/compare.go b/benchttp/metrics/compare.go similarity index 100% rename from benchttp/internal/metrics/compare.go rename to benchttp/metrics/compare.go diff --git a/benchttp/internal/metrics/field.go b/benchttp/metrics/field.go similarity index 100% rename from benchttp/internal/metrics/field.go rename to benchttp/metrics/field.go diff --git a/benchttp/internal/metrics/field_test.go b/benchttp/metrics/field_test.go similarity index 96% rename from benchttp/internal/metrics/field_test.go rename to benchttp/metrics/field_test.go index fb430ae..19f4003 100644 --- a/benchttp/internal/metrics/field_test.go +++ b/benchttp/metrics/field_test.go @@ -3,7 +3,7 @@ package metrics_test import ( "testing" - "github.com/benchttp/engine/benchttp/internal/metrics" + "github.com/benchttp/engine/benchttp/metrics" ) func TestField_Type(t *testing.T) { diff --git a/benchttp/internal/metrics/metrics.go b/benchttp/metrics/metrics.go similarity index 97% rename from benchttp/internal/metrics/metrics.go rename to benchttp/metrics/metrics.go index 0f3abe7..d280d12 100644 --- a/benchttp/internal/metrics/metrics.go +++ b/benchttp/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "strings" - "github.com/benchttp/engine/benchttp/internal/reflectpath" + "github.com/benchttp/engine/internal/reflectpath" ) // Value is a concrete metric value, e.g. 120 or 3 * time.Second. diff --git a/benchttp/internal/metrics/metrics_test.go b/benchttp/metrics/metrics_test.go similarity index 93% rename from benchttp/internal/metrics/metrics_test.go rename to benchttp/metrics/metrics_test.go index dd2c983..f89faee 100644 --- a/benchttp/internal/metrics/metrics_test.go +++ b/benchttp/metrics/metrics_test.go @@ -4,8 +4,7 @@ import ( "testing" "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/metrics/timestats" + "github.com/benchttp/engine/benchttp/metrics" ) func TestMetric_Compare(t *testing.T) { @@ -87,7 +86,7 @@ func TestAggregate_MetricOf(t *testing.T) { name: "get metrics from nested struct", fieldID: "ResponseTimes.Mean", agg: metrics.Aggregate{ - ResponseTimes: timestats.TimeStats{ + ResponseTimes: metrics.TimeStats{ Mean: 100 * time.Millisecond, }, }, @@ -114,7 +113,7 @@ func TestAggregate_MetricOf(t *testing.T) { name: "get metrics from string map", fieldID: "RequestEventTimes.FirstResponseByte.Mean", agg: metrics.Aggregate{ - RequestEventTimes: map[string]timestats.TimeStats{ + RequestEventTimes: map[string]metrics.TimeStats{ "FirstResponseByte": {Mean: 100 * time.Millisecond}, }, }, @@ -132,7 +131,7 @@ func TestAggregate_MetricOf(t *testing.T) { name: "case insensitive", fieldID: "responsetimes.MEAN", agg: metrics.Aggregate{ - ResponseTimes: timestats.TimeStats{ + ResponseTimes: metrics.TimeStats{ Mean: 100 * time.Millisecond, }, }, diff --git a/benchttp/internal/metrics/timestats/timestats.go b/benchttp/metrics/timestats.go similarity index 96% rename from benchttp/internal/metrics/timestats/timestats.go rename to benchttp/metrics/timestats.go index ea3f883..d00b6c2 100644 --- a/benchttp/internal/metrics/timestats/timestats.go +++ b/benchttp/metrics/timestats.go @@ -1,4 +1,4 @@ -package timestats +package metrics import ( "math" @@ -17,7 +17,7 @@ type TimeStats struct { Deciles []time.Duration } -func New(times []time.Duration) TimeStats { +func NewTimeStats(times []time.Duration) TimeStats { n := len(times) if n == 0 { return TimeStats{} diff --git a/benchttp/internal/metrics/timestats/sort.go b/benchttp/metrics/timestats_sort.go similarity index 94% rename from benchttp/internal/metrics/timestats/sort.go rename to benchttp/metrics/timestats_sort.go index 391cbf8..c7d97b4 100644 --- a/benchttp/internal/metrics/timestats/sort.go +++ b/benchttp/metrics/timestats_sort.go @@ -1,4 +1,4 @@ -package timestats +package metrics import ( "time" diff --git a/benchttp/internal/metrics/timestats/timestats_test.go b/benchttp/metrics/timestats_test.go similarity index 83% rename from benchttp/internal/metrics/timestats/timestats_test.go rename to benchttp/metrics/timestats_test.go index 522208a..de2fad7 100644 --- a/benchttp/internal/metrics/timestats/timestats_test.go +++ b/benchttp/metrics/timestats_test.go @@ -1,15 +1,15 @@ -package timestats_test +package metrics_test import ( "testing" "time" - "github.com/benchttp/engine/benchttp/internal/metrics/timestats" + "github.com/benchttp/engine/benchttp/metrics" ) func TestCompute(t *testing.T) { t.Run("happy path", func(t *testing.T) { - want := timestats.TimeStats{ + want := metrics.TimeStats{ Min: 100, Max: 400, Mean: 230, @@ -21,7 +21,7 @@ func TestCompute(t *testing.T) { data := []time.Duration{100, 100, 200, 300, 400, 200, 100, 200, 300, 400, 100, 100, 200, 300, 400, 200, 100, 200, 300, 400} - got := timestats.New(data) + got := metrics.NewTimeStats(data) for _, stat := range []struct { name string @@ -62,7 +62,7 @@ func TestCompute(t *testing.T) { t.Run("few values", func(t *testing.T) { data := []time.Duration{100, 300} - got := timestats.New(data) + got := metrics.NewTimeStats(data) if got.Deciles != nil { t.Errorf("deciles: want nil, got %v", got.Deciles) @@ -77,8 +77,3 @@ func TestCompute(t *testing.T) { } }) } - -// approxEqual returns true if val is equal to target with a margin of error. -func approxEqualTime(val, target, margin time.Duration) bool { - return val >= target-margin && val <= target+margin -} diff --git a/benchttp/internal/recorder/error.go b/benchttp/recorder/error.go similarity index 100% rename from benchttp/internal/recorder/error.go rename to benchttp/recorder/error.go diff --git a/benchttp/internal/recorder/httputil.go b/benchttp/recorder/httputil.go similarity index 100% rename from benchttp/internal/recorder/httputil.go rename to benchttp/recorder/httputil.go diff --git a/benchttp/internal/recorder/progress.go b/benchttp/recorder/progress.go similarity index 100% rename from benchttp/internal/recorder/progress.go rename to benchttp/recorder/progress.go diff --git a/benchttp/internal/recorder/recorder.go b/benchttp/recorder/recorder.go similarity index 98% rename from benchttp/internal/recorder/recorder.go rename to benchttp/recorder/recorder.go index 1e9be56..1bbd139 100644 --- a/benchttp/internal/recorder/recorder.go +++ b/benchttp/recorder/recorder.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/benchttp/engine/internal/dispatcher" + "github.com/benchttp/engine/internal/workerpool" ) const ( @@ -91,7 +91,7 @@ func (r *Recorder) Record(ctx context.Context, req *http.Request) ([]Record, err r.start = time.Now() go r.tickProgress() - err := dispatcher. + err := workerpool. New(numWorker). Do(ctx, maxIter, r.recordSingle(req, interval)) diff --git a/benchttp/internal/recorder/recorder_internal_test.go b/benchttp/recorder/recorder_internal_test.go similarity index 97% rename from benchttp/internal/recorder/recorder_internal_test.go rename to benchttp/recorder/recorder_internal_test.go index bc79fe8..162e4c4 100644 --- a/benchttp/internal/recorder/recorder_internal_test.go +++ b/benchttp/recorder/recorder_internal_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/benchttp/engine/internal/dispatcher" + "github.com/benchttp/engine/internal/workerpool" ) var errTest = errors.New("test-generated error") @@ -32,7 +32,7 @@ func TestRecorder_Run(t *testing.T) { exp: ErrConnection, }, { - label: "return dispatcher.ErrInvalidValue early on bad dispatcher value", + label: "return workerpool.ErrInvalidValue early on bad workerpool value", req: withNoopTransport(New(Config{ Requests: 1, Concurrency: 2, // bad: Concurrency > Requests @@ -40,7 +40,7 @@ func TestRecorder_Run(t *testing.T) { GlobalTimeout: 3 * time.Second, }, )), - exp: dispatcher.ErrInvalidValue, + exp: workerpool.ErrInvalidValue, }, } diff --git a/benchttp/internal/recorder/tracer.go b/benchttp/recorder/tracer.go similarity index 100% rename from benchttp/internal/recorder/tracer.go rename to benchttp/recorder/tracer.go diff --git a/benchttp/internal/recorder/tracer_internal_test.go b/benchttp/recorder/tracer_internal_test.go similarity index 100% rename from benchttp/internal/recorder/tracer_internal_test.go rename to benchttp/recorder/tracer_internal_test.go diff --git a/benchttp/report.go b/benchttp/report.go index 08b2589..9b9cda5 100644 --- a/benchttp/report.go +++ b/benchttp/report.go @@ -3,15 +3,15 @@ package benchttp import ( "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/tests" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/testsuite" ) // Report represents a run result as exported by the runner. type Report struct { Metadata Metadata Metrics metrics.Aggregate - Tests tests.SuiteResult + Tests testsuite.Result } // Metadata contains contextual information about a run. @@ -26,7 +26,7 @@ func newReport( r Runner, d time.Duration, m metrics.Aggregate, - t tests.SuiteResult, + t testsuite.Result, ) *Report { return &Report{ Metrics: m, diff --git a/benchttp/runner.go b/benchttp/runner.go index e8d1d11..bd0bebe 100644 --- a/benchttp/runner.go +++ b/benchttp/runner.go @@ -8,35 +8,11 @@ import ( "net/http" "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/recorder" - "github.com/benchttp/engine/benchttp/internal/tests" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/recorder" + "github.com/benchttp/engine/benchttp/testsuite" ) -type ( - RecordingProgress = recorder.Progress - RecordingStatus = recorder.Status - - MetricsAggregate = metrics.Aggregate - MetricsField = metrics.Field - MetricsValue = metrics.Value - MetricsTimeStats = metrics.TimeStats - - TestCase = tests.Case - TestPredicate = tests.Predicate - TestSuiteResults = tests.SuiteResult - TestCaseResult = tests.CaseResult -) - -const ( - StatusRunning = recorder.StatusRunning - StatusCanceled = recorder.StatusCanceled - StatusTimeout = recorder.StatusTimeout - StatusDone = recorder.StatusDone -) - -var ErrCanceled = recorder.ErrCanceled - type Runner struct { Request *http.Request @@ -46,9 +22,9 @@ type Runner struct { RequestTimeout time.Duration GlobalTimeout time.Duration - Tests []tests.Case + Tests []testsuite.Case - OnProgress func(RecordingProgress) + OnProgress func(recorder.Progress) recorder *recorder.Recorder } @@ -103,7 +79,7 @@ func (r Runner) Run(ctx context.Context) (*Report, error) { agg := metrics.NewAggregate(records) - testResults := tests.Run(agg, r.Tests) + testResults := testsuite.Run(agg, r.Tests) return newReport(r, duration, agg, testResults), nil } diff --git a/benchttp/internal/tests/predicate.go b/benchttp/testsuite/predicate.go similarity index 94% rename from benchttp/internal/tests/predicate.go rename to benchttp/testsuite/predicate.go index dbdee79..8fd4169 100644 --- a/benchttp/internal/tests/predicate.go +++ b/benchttp/testsuite/predicate.go @@ -1,9 +1,9 @@ -package tests +package testsuite import ( "errors" - "github.com/benchttp/engine/benchttp/internal/metrics" + "github.com/benchttp/engine/benchttp/metrics" "github.com/benchttp/engine/internal/errorutil" ) diff --git a/benchttp/internal/tests/predicate_test.go b/benchttp/testsuite/predicate_test.go similarity index 78% rename from benchttp/internal/tests/predicate_test.go rename to benchttp/testsuite/predicate_test.go index bbb29b6..7704553 100644 --- a/benchttp/internal/tests/predicate_test.go +++ b/benchttp/testsuite/predicate_test.go @@ -1,11 +1,11 @@ -package tests_test +package testsuite_test import ( "testing" "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/tests" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/testsuite" ) func TestPredicate(t *testing.T) { @@ -16,37 +16,37 @@ func TestPredicate(t *testing.T) { ) testcases := []struct { - Predicate tests.Predicate + Predicate testsuite.Predicate PassValues []int FailValues []int }{ { - Predicate: tests.EQ, + Predicate: testsuite.EQ, PassValues: []int{base}, FailValues: []int{more, less}, }, { - Predicate: tests.NEQ, + Predicate: testsuite.NEQ, PassValues: []int{less, more}, FailValues: []int{base}, }, { - Predicate: tests.LT, + Predicate: testsuite.LT, PassValues: []int{more}, FailValues: []int{base, less}, }, { - Predicate: tests.LTE, + Predicate: testsuite.LTE, PassValues: []int{more, base}, FailValues: []int{less}, }, { - Predicate: tests.GT, + Predicate: testsuite.GT, PassValues: []int{less}, FailValues: []int{base, more}, }, { - Predicate: tests.GTE, + Predicate: testsuite.GTE, PassValues: []int{less, base}, FailValues: []int{more}, }, @@ -68,7 +68,7 @@ func TestPredicate(t *testing.T) { func expectPredicatePass( t *testing.T, - p tests.Predicate, + p testsuite.Predicate, src, tar int, ) { t.Helper() @@ -77,7 +77,7 @@ func expectPredicatePass( func expectPredicateFail( t *testing.T, - p tests.Predicate, + p testsuite.Predicate, src, tar int, ) { t.Helper() @@ -86,7 +86,7 @@ func expectPredicateFail( func expectPredicateResult( t *testing.T, - p tests.Predicate, + p testsuite.Predicate, src, tar int, expPass bool, ) { @@ -98,7 +98,7 @@ func expectPredicateResult( }, src), } - result := tests.Run(agg, []tests.Case{{ + result := testsuite.Run(agg, []testsuite.Case{{ Predicate: p, Field: "RequestCount", Target: tar, diff --git a/benchttp/internal/tests/tests.go b/benchttp/testsuite/testsuite.go similarity index 80% rename from benchttp/internal/tests/tests.go rename to benchttp/testsuite/testsuite.go index 05a1a1e..6afc0af 100644 --- a/benchttp/internal/tests/tests.go +++ b/benchttp/testsuite/testsuite.go @@ -1,9 +1,9 @@ -package tests +package testsuite import ( "fmt" - "github.com/benchttp/engine/benchttp/internal/metrics" + "github.com/benchttp/engine/benchttp/metrics" ) type Case struct { @@ -13,10 +13,6 @@ type Case struct { Target metrics.Value } -type SuiteResult struct { - Pass bool - Results []CaseResult -} type CaseResult struct { Input Case @@ -25,7 +21,12 @@ type CaseResult struct { Summary string } -func Run(agg metrics.Aggregate, cases []Case) SuiteResult { +type Result struct { + Pass bool + OfCases []CaseResult +} + +func Run(agg metrics.Aggregate, cases []Case) Result { allpass := true results := make([]CaseResult, len(cases)) for i, input := range cases { @@ -35,9 +36,9 @@ func Run(agg metrics.Aggregate, cases []Case) SuiteResult { allpass = false } } - return SuiteResult{ + return Result{ Pass: allpass, - Results: results, + OfCases: results, } } diff --git a/benchttp/internal/tests/tests_test.go b/benchttp/testsuite/testsuite_test.go similarity index 75% rename from benchttp/internal/tests/tests_test.go rename to benchttp/testsuite/testsuite_test.go index a9114ce..251a430 100644 --- a/benchttp/internal/tests/tests_test.go +++ b/benchttp/testsuite/testsuite_test.go @@ -1,42 +1,41 @@ -package tests_test +package testsuite_test import ( "fmt" "testing" "time" - "github.com/benchttp/engine/benchttp/internal/metrics" - "github.com/benchttp/engine/benchttp/internal/metrics/timestats" - "github.com/benchttp/engine/benchttp/internal/tests" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/testsuite" ) func TestRun(t *testing.T) { testcases := []struct { label string inputAgg metrics.Aggregate - inputCases []tests.Case - expGlobalPass bool - expCaseResults []tests.CaseResult + inputCases []testsuite.Case + expPass bool + expCasesResults []testsuite.CaseResult }{ { label: "pass if all cases pass", inputAgg: metricsWithMeanResponseTime(ms(100)), - inputCases: []tests.Case{ + inputCases: []testsuite.Case{ { Name: "average response time below 120ms (pass)", - Predicate: tests.LT, + Predicate: testsuite.LT, Field: "ResponseTimes.Mean", Target: ms(120), }, { Name: "average response time is above 80ms (pass)", - Predicate: tests.GT, + Predicate: testsuite.GT, Field: "ResponseTimes.Mean", Target: ms(80), }, }, - expGlobalPass: true, - expCaseResults: []tests.CaseResult{ + expPass: true, + expCasesResults: []testsuite.CaseResult{ {Pass: true, Got: ms(100), Summary: "want ResponseTimes.Mean < 120ms, got 100ms"}, {Pass: true, Got: ms(100), Summary: "want ResponseTimes.Mean > 80ms, got 100ms"}, }, @@ -44,22 +43,22 @@ func TestRun(t *testing.T) { { label: "fail if at least one case fails", inputAgg: metricsWithMeanResponseTime(ms(200)), - inputCases: []tests.Case{ + inputCases: []testsuite.Case{ { Name: "average response time below 120ms (fail)", - Predicate: tests.LT, + Predicate: testsuite.LT, Field: "ResponseTimes.Mean", Target: ms(120), }, { Name: "average response time is above 80ms (pass)", - Predicate: tests.GT, + Predicate: testsuite.GT, Field: "ResponseTimes.Mean", Target: ms(80), }, }, - expGlobalPass: false, - expCaseResults: []tests.CaseResult{ + expPass: false, + expCasesResults: []testsuite.CaseResult{ {Pass: false, Got: ms(200), Summary: "want ResponseTimes.Mean < 120ms, got 200ms"}, {Pass: true, Got: ms(200), Summary: "want ResponseTimes.Mean > 80ms, got 200ms"}, }, @@ -68,10 +67,10 @@ func TestRun(t *testing.T) { for _, tc := range testcases { t.Run(tc.label, func(t *testing.T) { - suiteResult := tests.Run(tc.inputAgg, tc.inputCases) + suiteResult := testsuite.Run(tc.inputAgg, tc.inputCases) - assertGlobalPass(t, suiteResult.Pass, tc.expGlobalPass) - assertEqualCaseResults(t, tc.expCaseResults, suiteResult.Results) + assertGlobalPass(t, suiteResult.Pass, tc.expPass) + assertEqualCasesResults(t, tc.expCasesResults, suiteResult.OfCases) }) } } @@ -89,7 +88,7 @@ func assertGlobalPass(t *testing.T, got, exp bool) { }) } -func assertEqualCaseResults(t *testing.T, exp, got []tests.CaseResult) { +func assertEqualCasesResults(t *testing.T, exp, got []testsuite.CaseResult) { t.Helper() if gotLen, expLen := len(got), len(exp); gotLen != expLen { @@ -131,7 +130,7 @@ func assertEqualCaseResults(t *testing.T, exp, got []tests.CaseResult) { func metricsWithMeanResponseTime(d time.Duration) metrics.Aggregate { return metrics.Aggregate{ - ResponseTimes: timestats.TimeStats{ + ResponseTimes: metrics.TimeStats{ Mean: d, }, } diff --git a/cli/errorutil/errorutil.go b/cli/errorutil/errorutil.go deleted file mode 100644 index d733a93..0000000 --- a/cli/errorutil/errorutil.go +++ /dev/null @@ -1,24 +0,0 @@ -package errorutil - -import ( - "fmt" - "strings" -) - -// WithDetails returns an error wrapping err, appended with a string -// representation of details separated by ": ". -// -// Example -// -// var ErrNotFound = errors.New("not found") -// err := WithDetails(ErrNotFound, "abc.jpg", "deleted yesterday") -// -// errors.Is(err, ErrNotFound) == true -// err.Error() == "not found: abc.jpg: deleted yesterday" -func WithDetails(base error, details ...interface{}) error { - detailsStr := make([]string, len(details)) - for i := range details { - detailsStr[i] = fmt.Sprint(details[i]) - } - return fmt.Errorf("%w: %s", base, strings.Join(detailsStr, ": ")) -} diff --git a/cli/render/progress.go b/cli/render/progress.go index 3e97a1f..00209f5 100644 --- a/cli/render/progress.go +++ b/cli/render/progress.go @@ -6,14 +6,14 @@ import ( "strconv" "strings" - "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/recorder" "github.com/benchttp/engine/cli/render/ansi" ) // Progress renders a fancy representation of a runner.RecordingProgress // and writes the result to w. -func Progress(w io.Writer, p benchttp.RecordingProgress) (int, error) { +func Progress(w io.Writer, p recorder.Progress) (int, error) { return fmt.Fprint(w, progressString(p)) } @@ -21,7 +21,7 @@ func Progress(w io.Writer, p benchttp.RecordingProgress) (int, error) { // for a fancy display in a CLI: // // RUNNING ◼︎◼︎◼︎◼︎◼︎◼︎◼︎◼︎◼︎◼︎ 50% | 50/100 requests | 27s timeout -func progressString(p benchttp.RecordingProgress) string { +func progressString(p recorder.Progress) string { var ( countdown = p.Timeout - p.Elapsed reqmax = strconv.Itoa(p.MaxCount) @@ -68,20 +68,20 @@ func renderTimeline(pctdone int) string { // renderStatus returns a string representing the status, // depending on whether the run is done or not and the value // of its context error. -func renderStatus(status benchttp.RecordingStatus) string { +func renderStatus(status recorder.Status) string { styled := statusStyle(status) return styled(string(status)) } -func statusStyle(status benchttp.RecordingStatus) ansi.StyleFunc { +func statusStyle(status recorder.Status) ansi.StyleFunc { switch status { - case benchttp.StatusRunning: + case recorder.StatusRunning: return ansi.Yellow - case benchttp.StatusDone: + case recorder.StatusDone: return ansi.Green - case benchttp.StatusCanceled: + case recorder.StatusCanceled: return ansi.Red - case benchttp.StatusTimeout: + case recorder.StatusTimeout: return ansi.Cyan } return ansi.Grey // should not occur diff --git a/cli/render/report_test.go b/cli/render/report_test.go index 72ba406..a52f22d 100644 --- a/cli/render/report_test.go +++ b/cli/render/report_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/metrics" "github.com/benchttp/engine/cli/render" "github.com/benchttp/engine/cli/render/ansi" @@ -29,13 +30,13 @@ func TestReport_String(t *testing.T) { // helpers -func metricsStub() (agg benchttp.MetricsAggregate, total time.Duration) { - return benchttp.MetricsAggregate{ +func metricsStub() (agg metrics.Aggregate, total time.Duration) { + return metrics.Aggregate{ RequestFailures: make([]struct { Reason string }, 1), Records: make([]struct{ ResponseTime time.Duration }, 3), - ResponseTimes: benchttp.MetricsTimeStats{ + ResponseTimes: metrics.TimeStats{ Min: 4 * time.Second, Max: 6 * time.Second, Mean: 5 * time.Second, diff --git a/cli/render/testsuite.go b/cli/render/testsuite.go index 63714fd..b0cc1ef 100644 --- a/cli/render/testsuite.go +++ b/cli/render/testsuite.go @@ -4,18 +4,18 @@ import ( "io" "strings" - "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/testsuite" "github.com/benchttp/engine/cli/render/ansi" ) -func TestSuite(w io.Writer, suite benchttp.TestSuiteResults) (int, error) { - return w.Write([]byte(TestSuiteString(suite))) +func TestSuite(w io.Writer, result testsuite.Result) (int, error) { + return w.Write([]byte(TestSuiteString(result))) } // String returns a default summary of the Report as a string. -func TestSuiteString(suite benchttp.TestSuiteResults) string { - if len(suite.Results) == 0 { +func TestSuiteString(result testsuite.Result) string { + if len(result.OfCases) == 0 { return "" } @@ -24,10 +24,10 @@ func TestSuiteString(suite benchttp.TestSuiteResults) string { b.WriteString(ansi.Bold("→ Test suite")) b.WriteString("\n") - writeResultString(&b, suite.Pass) + writeResultString(&b, result.Pass) b.WriteString("\n") - for _, tr := range suite.Results { + for _, tr := range result.OfCases { writeIndent(&b, 1) writeResultString(&b, tr.Pass) b.WriteString(" ") diff --git a/cli/testutil/http.go b/cli/testutil/http.go deleted file mode 100644 index 34b660b..0000000 --- a/cli/testutil/http.go +++ /dev/null @@ -1,15 +0,0 @@ -package testutil - -import ( - "bytes" - "net/http" -) - -func MustMakeRequest(method, uri string, header http.Header, body []byte) *http.Request { - req, err := http.NewRequest(method, uri, bytes.NewReader(body)) - if err != nil { - panic("testutil.MustMakeRequest: " + err.Error()) - } - req.Header = header - return req -} diff --git a/cmd/benchttp/run.go b/cmd/benchttp/run.go index ce84726..79fc505 100644 --- a/cmd/benchttp/run.go +++ b/cmd/benchttp/run.go @@ -9,6 +9,7 @@ import ( "os" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/recorder" "github.com/benchttp/engine/configio" "github.com/benchttp/engine/cli/configflag" @@ -93,16 +94,16 @@ func runBenchmark(runner benchttp.Runner, silent bool) (*benchttp.Report, error) return report, nil } -func onRecordingProgress(silent bool) func(benchttp.RecordingProgress) { +func onRecordingProgress(silent bool) func(recorder.Progress) { if silent { - return func(benchttp.RecordingProgress) {} + return func(recorder.Progress) {} } // hack: write a blank line as render.Progress always // erases the previous line fmt.Println() - return func(progress benchttp.RecordingProgress) { + return func(progress recorder.Progress) { render.Progress(os.Stdout, progress) //nolint: errcheck } } diff --git a/configio/builder.go b/configio/builder.go index 2256ab1..76cef6d 100644 --- a/configio/builder.go +++ b/configio/builder.go @@ -7,6 +7,7 @@ import ( "time" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/testsuite" ) // A Builder is used to incrementally build a benchttp.Runner @@ -165,7 +166,7 @@ func (b *Builder) SetGlobalTimeout(v time.Duration) { // SetTests adds a mutation that sets a runner's // Tests field to v. -func (b *Builder) SetTests(v []benchttp.TestCase) { +func (b *Builder) SetTests(v []testsuite.Case) { b.append(func(runner *benchttp.Runner) { runner.Tests = v }) @@ -173,7 +174,7 @@ func (b *Builder) SetTests(v []benchttp.TestCase) { // SetTests adds a mutation that appends the given benchttp.TestCases // to a runner's Tests field. -func (b *Builder) AddTests(v ...benchttp.TestCase) { +func (b *Builder) AddTests(v ...testsuite.Case) { b.append(func(runner *benchttp.Runner) { runner.Tests = append(runner.Tests, v...) }) diff --git a/configio/builder_test.go b/configio/builder_test.go index 7749e12..6da9d45 100644 --- a/configio/builder_test.go +++ b/configio/builder_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/testsuite" "github.com/benchttp/engine/benchttptest" "github.com/benchttp/engine/configio" ) @@ -103,7 +104,7 @@ func TestBuilder_Set(t *testing.T) { t.Run("test cases", func(t *testing.T) { want := benchttp.Runner{ - Tests: []benchttp.TestCase{ + Tests: []testsuite.Case{ { Name: "maximum response time", Field: "ResponseTimes.Max", @@ -126,7 +127,7 @@ func TestBuilder_Set(t *testing.T) { } b := configio.Builder{} - b.SetTests([]benchttp.TestCase{want.Tests[0]}) + b.SetTests([]testsuite.Case{want.Tests[0]}) b.AddTests(want.Tests[1:]...) benchttptest.AssertEqualRunners(t, want, b.Runner()) diff --git a/configio/internal/testdata/testdata.go b/configio/internal/testdata/testdata.go index 5ed0f58..6e0efa6 100644 --- a/configio/internal/testdata/testdata.go +++ b/configio/internal/testdata/testdata.go @@ -8,6 +8,7 @@ import ( "time" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/testsuite" ) // ConfigFile represents a testdata configuration file. @@ -128,7 +129,7 @@ func fullRunner() benchttp.Runner { RequestTimeout: 2 * time.Second, GlobalTimeout: 60 * time.Second, - Tests: []benchttp.TestCase{ + Tests: []testsuite.Case{ { Name: "maximum response time", Field: "ResponseTimes.Max", diff --git a/configio/representation.go b/configio/representation.go index bf851a1..c60ec38 100644 --- a/configio/representation.go +++ b/configio/representation.go @@ -11,6 +11,8 @@ import ( "time" "github.com/benchttp/engine/benchttp" + "github.com/benchttp/engine/benchttp/metrics" + "github.com/benchttp/engine/benchttp/testsuite" "github.com/benchttp/engine/internal/errorutil" ) @@ -145,7 +147,7 @@ func (repr representation) parseTestsInto(dst *benchttp.Runner) error { return nil } - cases := make([]benchttp.TestCase, len(testSuite)) + cases := make([]testsuite.Case, len(testSuite)) for i, t := range testSuite { fieldPath := func(caseField string) string { return fmt.Sprintf("tests[%d].%s", i, caseField) @@ -160,12 +162,12 @@ func (repr representation) parseTestsInto(dst *benchttp.Runner) error { return err } - field := benchttp.MetricsField(*t.Field) + field := metrics.Field(*t.Field) if err := field.Validate(); err != nil { return fmt.Errorf("%s: %s", fieldPath("field"), err) } - predicate := benchttp.TestPredicate(*t.Predicate) + predicate := testsuite.Predicate(*t.Predicate) if err := predicate.Validate(); err != nil { return fmt.Errorf("%s: %s", fieldPath("predicate"), err) } @@ -175,7 +177,7 @@ func (repr representation) parseTestsInto(dst *benchttp.Runner) error { return fmt.Errorf("%s: %s", fieldPath("target"), err) } - cases[i] = benchttp.TestCase{ + cases[i] = testsuite.Case{ Name: *t.Name, Field: field, Predicate: predicate, @@ -222,11 +224,11 @@ func parseOptionalDuration(raw string) (time.Duration, error) { } func parseMetricValue( - field benchttp.MetricsField, + field metrics.Field, inputValue string, -) (benchttp.MetricsValue, error) { +) (metrics.Value, error) { fieldType := field.Type() - handleError := func(v interface{}, err error) (benchttp.MetricsValue, error) { + handleError := func(v interface{}, err error) (metrics.Value, error) { if err != nil { return nil, fmt.Errorf( "value %q is incompatible with field %s (want %s)", diff --git a/benchttp/internal/reflectpath/resolver.go b/internal/reflectpath/resolver.go similarity index 100% rename from benchttp/internal/reflectpath/resolver.go rename to internal/reflectpath/resolver.go diff --git a/benchttp/internal/reflectpath/type.go b/internal/reflectpath/type.go similarity index 100% rename from benchttp/internal/reflectpath/type.go rename to internal/reflectpath/type.go diff --git a/benchttp/internal/reflectpath/value.go b/internal/reflectpath/value.go similarity index 100% rename from benchttp/internal/reflectpath/value.go rename to internal/reflectpath/value.go diff --git a/internal/dispatcher/dispatcher.go b/internal/workerpool/workerpool.go similarity index 89% rename from internal/dispatcher/dispatcher.go rename to internal/workerpool/workerpool.go index b02eba5..3088b09 100644 --- a/internal/dispatcher/dispatcher.go +++ b/internal/workerpool/workerpool.go @@ -1,4 +1,4 @@ -package dispatcher +package workerpool import ( "context" @@ -15,7 +15,7 @@ type Dispatcher interface { Do(ctx context.Context, maxIter int, callback func()) error } -type dispatcher struct { +type workerpool struct { numWorker int sem *semaphore.Weighted } @@ -26,7 +26,7 @@ func New(numWorker int) Dispatcher { panic(fmt.Sprintf("invalid numWorker value: must be > 1, got %d", numWorker)) } sem := semaphore.NewWeighted(int64(numWorker)) - return dispatcher{sem: sem, numWorker: numWorker} + return workerpool{sem: sem, numWorker: numWorker} } // Do concurrently executes callback at most maxIter times or until ctx is done @@ -39,7 +39,7 @@ func New(numWorker int) Dispatcher { // callback == nil // // Else it returns the context error if any or nil. -func (d dispatcher) Do(ctx context.Context, maxIter int, callback func()) error { +func (d workerpool) Do(ctx context.Context, maxIter int, callback func()) error { if err := d.validate(maxIter, callback); err != nil { return err } @@ -72,7 +72,7 @@ func (d dispatcher) Do(ctx context.Context, maxIter int, callback func()) error return err } -func (d dispatcher) validate(maxIter int, callback func()) error { +func (d workerpool) validate(maxIter int, callback func()) error { if maxIter < 1 && maxIter != -1 { return fmt.Errorf("%w: maxIter: must be -1 or >= 1, got %d", ErrInvalidValue, maxIter) } diff --git a/internal/dispatcher/dispatcher_test.go b/internal/workerpool/workerpool_test.go similarity index 91% rename from internal/dispatcher/dispatcher_test.go rename to internal/workerpool/workerpool_test.go index 6b5d9bd..f8b7221 100644 --- a/internal/dispatcher/dispatcher_test.go +++ b/internal/workerpool/workerpool_test.go @@ -1,4 +1,4 @@ -package dispatcher_test +package workerpool_test import ( "context" @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/benchttp/engine/internal/dispatcher" + "github.com/benchttp/engine/internal/workerpool" ) func TestNew(t *testing.T) { @@ -31,7 +31,7 @@ func TestNew(t *testing.T) { } }() - if d := dispatcher.New(numWorker); d != nil { + if d := workerpool.New(numWorker); d != nil { t.Error("returned a non-nil Dispatcher") } }(numWorker) @@ -39,7 +39,7 @@ func TestNew(t *testing.T) { }) t.Run("return valid Dispatcher if numWorker > 0", func(t *testing.T) { - if d := dispatcher.New(10); d == nil { + if d := workerpool.New(10); d == nil { t.Error("returned nil Dispatcher") } }) @@ -55,7 +55,7 @@ func TestDo(t *testing.T) { gotIter := 0 - dispatcher.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck + workerpool.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck gotIter++ }) @@ -85,7 +85,7 @@ func TestDo(t *testing.T) { var gotErr error gotDuration := timeFunc(func() { - gotErr = dispatcher.New(numWorker).Do(ctx, maxIter, func() { + gotErr = workerpool.New(numWorker).Do(ctx, maxIter, func() { gotIter++ time.Sleep(interval) }) @@ -134,7 +134,7 @@ func TestDo(t *testing.T) { var gotErr error gotDuration := timeFunc(func() { - gotErr = dispatcher.New(numWorker).Do(ctx, maxIter, func() { + gotErr = workerpool.New(numWorker).Do(ctx, maxIter, func() { gotIter++ time.Sleep(interval) }) @@ -177,7 +177,7 @@ func TestDo(t *testing.T) { gotNumGoroutines = make([]int, 0, maxIter) ) - dispatcher.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck + workerpool.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck mu.Lock() gotNumGoroutines = append(gotNumGoroutines, runtime.NumGoroutine()-baseNumGoroutine) mu.Unlock() @@ -213,7 +213,7 @@ func TestDo(t *testing.T) { ) start := time.Now() - dispatcher.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck + workerpool.New(numWorker).Do(context.Background(), maxIter, func() { //nolint:errcheck mu.Lock() elapsedTimes = append(elapsedTimes, time.Since(start)) mu.Unlock() @@ -267,28 +267,28 @@ func TestValidate(t *testing.T) { }{ { label: "return error if maxIter == 0", - exp: dispatcher.ErrInvalidValue, + exp: workerpool.ErrInvalidValue, numWorker: 10, maxIter: 0, callback: func() {}, }, { label: "return error if maxIter == -2", - exp: dispatcher.ErrInvalidValue, + exp: workerpool.ErrInvalidValue, numWorker: 10, maxIter: -2, callback: func() {}, }, { label: "return error if maxIter < numWorker", - exp: dispatcher.ErrInvalidValue, + exp: workerpool.ErrInvalidValue, numWorker: 10, maxIter: 5, callback: func() {}, }, { label: "return error if callback == nil", - exp: dispatcher.ErrInvalidValue, + exp: workerpool.ErrInvalidValue, numWorker: 10, maxIter: 20, callback: nil, @@ -314,7 +314,7 @@ func TestValidate(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() - gotErr := dispatcher.New(tc.numWorker).Do(ctx, tc.maxIter, tc.callback) + gotErr := workerpool.New(tc.numWorker).Do(ctx, tc.maxIter, tc.callback) if !errors.Is(gotErr, tc.exp) { t.Errorf("unexpected error:\nexp %v\ngot %v", tc.exp, gotErr) }