diff --git a/histogram/values.go b/histogram/values.go index 3775fc2..99ba7c4 100644 --- a/histogram/values.go +++ b/histogram/values.go @@ -7,7 +7,12 @@ import ( "strings" ) -// StringifyValues turns +// StringifyValues formats histogram values into a string. +// The result is a comma-separated list of "value:count" pairs. +// Each value is a non-negative int64 value, each count is a +// greater or equal to 1 int. +// If count is 1, the ":count" part is left out, so "42:1" and +// "42" are sematically equal. func StringifyValues(values []int64) string { n := len(values) if n == 0 { @@ -48,6 +53,10 @@ func StringifyValues(values []int64) string { return strings.Join(toks, ",") } +// ParseValues parses a "value:count" comma-separated +// list of histogram values into a slice of int64 values. +// The resulting slice contains each value ":count" times. +// The resulting slice is sorted in ascending order. func ParseValues(s string) ([]int64, error) { if s == "" { return nil, nil @@ -55,7 +64,7 @@ func ParseValues(s string) ([]int64, error) { var values []int64 toks := strings.Split(s, ",") for itok, tok := range toks { - v, c, err := parseValue(tok) + v, c, err := parseValueAndCount(tok) if err != nil { return nil, fmt.Errorf("cannot parse token #%d %q: %w", itok+1, tok, err) } @@ -70,7 +79,7 @@ func ParseValues(s string) ([]int64, error) { return values, nil } -func parseValue(s string) (int64, int, error) { +func parseValueAndCount(s string) (int64, int, error) { parts := strings.Split(s, ":") np := len(parts) if np < 1 || 2 < np { diff --git a/histogram/values_test.go b/histogram/values_test.go index d5962fa..4498151 100644 --- a/histogram/values_test.go +++ b/histogram/values_test.go @@ -1,6 +1,8 @@ package histogram import ( + "strconv" + "strings" "testing" "github.com/cvilsmeier/monibot-go/internal/assert" @@ -17,33 +19,37 @@ func TestStringifyValues(t *testing.T) { } func TestParseValues(t *testing.T) { - strErr := func(err error) string { - if err == nil { - return "nil" + str := func(values []int64, err error) string { + if err != nil { + return err.Error() } - return err.Error() + var ss []string + for _, v := range values { + ss = append(ss, strconv.FormatInt(v, 10)) + } + return strings.Join(ss, ",") } ass := assert.New(t) - mustParse := func(s string) []int64 { - v, err := ParseValues(s) - ass.Nil(err) - return v - } - ass.Eq("", StringifyValues(mustParse(""))) - ass.Eq("1", StringifyValues(mustParse("1"))) - ass.Eq("1", StringifyValues(mustParse("1:1"))) - ass.Eq("1:2", StringifyValues(mustParse("1:2"))) - ass.Eq("1,2,3:2", StringifyValues(mustParse("3:2,2:1,1"))) - _, err := ParseValues("-3:2") - ass.Eq("cannot parse token #1 \"-3:2\": invalid value -3", strErr(err)) - _, err = ParseValues("-3:2") - ass.Eq("cannot parse token #1 \"-3:2\": invalid value -3", strErr(err)) - _, err = ParseValues("3:0") - ass.Eq("cannot parse token #1 \"3:0\": invalid count 0", strErr(err)) - _, err = ParseValues("foo") - ass.Eq("cannot parse token #1 \"foo\": cannot parse value \"foo\": strconv.ParseInt: parsing \"foo\": invalid syntax", strErr(err)) - _, err = ParseValues("foo:1") - ass.Eq("cannot parse token #1 \"foo:1\": cannot parse value \"foo\": strconv.ParseInt: parsing \"foo\": invalid syntax", strErr(err)) - _, err = ParseValues("1:foo") - ass.Eq("cannot parse token #1 \"1:foo\": cannot parse count \"foo\": strconv.Atoi: parsing \"foo\": invalid syntax", strErr(err)) + v, err := ParseValues("") + ass.Eq("", str(v, err)) + v, err = ParseValues("1") + ass.Eq("1", str(v, err)) + v, err = ParseValues("1:1") + ass.Eq("1", str(v, err)) + v, err = ParseValues("1:2") + ass.Eq("1,1", str(v, err)) + v, err = ParseValues("3:2,2:1,1") + ass.Eq("1,2,3,3", str(v, err)) + v, err = ParseValues("-3:2") + ass.Eq("cannot parse token #1 \"-3:2\": invalid value -3", str(v, err)) + v, err = ParseValues("-3:2") + ass.Eq("cannot parse token #1 \"-3:2\": invalid value -3", str(v, err)) + v, err = ParseValues("3:0") + ass.Eq("cannot parse token #1 \"3:0\": invalid count 0", str(v, err)) + v, err = ParseValues("foo") + ass.Eq("cannot parse token #1 \"foo\": cannot parse value \"foo\": strconv.ParseInt: parsing \"foo\": invalid syntax", str(v, err)) + v, err = ParseValues("foo:1") + ass.Eq("cannot parse token #1 \"foo:1\": cannot parse value \"foo\": strconv.ParseInt: parsing \"foo\": invalid syntax", str(v, err)) + v, err = ParseValues("1:foo") + ass.Eq("cannot parse token #1 \"1:foo\": cannot parse count \"foo\": strconv.Atoi: parsing \"foo\": invalid syntax", str(v, err)) }