diff --git a/config.json b/config.json index c52df25..01159fd 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,8 @@ { - "Interval": "6h0m0s", + "Interval": { + "OK": "6h0m0s", + "Error": "6h" + }, "States": [ "GA" ], diff --git a/config/config.go b/config/config.go index 29a2b50..947cbc8 100644 --- a/config/config.go +++ b/config/config.go @@ -10,11 +10,14 @@ import ( ) type Config struct { - Interval Duration - States []State - Storage []string - Client string - Message struct { + Interval struct { + OK Duration + Error Duration + } + States []State + Storage []string + Client string + Message struct { Matrix struct { Mock bool Homeserver string diff --git a/config/duration.go b/config/duration.go index 3094dad..d290c1a 100644 --- a/config/duration.go +++ b/config/duration.go @@ -2,17 +2,29 @@ package config import ( "encoding/json" + "errors" + "math/rand" + "strings" "time" ) -type Duration time.Duration +type Duration struct { + least time.Duration + most time.Duration +} func (d Duration) Get() time.Duration { - return time.Duration(d) + jitter := d.most - d.least + if jitter >= time.Second { + jitter = time.Second * time.Duration(rand.Int()%int(jitter.Seconds())) + } else { + jitter = 0 + } + return time.Duration(d.least + jitter) } func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(d.Get().String()) + return json.Marshal(d.least.String() + ".." + d.most.String()) } func (d *Duration) UnmarshalJSON(b []byte) error { @@ -20,10 +32,25 @@ func (d *Duration) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &s); err != nil { return err } - d2, err := time.ParseDuration(s) + if !strings.Contains(s, "..") { + s = s + ".." + s + } + splits := strings.Split(s, "..") + if len(splits) != 2 { + return errors.New("unexpected ..") + } + least, err := time.ParseDuration(splits[0]) if err != nil { return err } - *d = Duration(d2) + most, err := time.ParseDuration(splits[1]) + if err != nil { + return err + } + if least > most { + most, least = least, most + } + d.most = most + d.least = least return nil } diff --git a/config/duration_test.go b/config/duration_test.go new file mode 100644 index 0000000..006956c --- /dev/null +++ b/config/duration_test.go @@ -0,0 +1,105 @@ +package config + +import ( + "encoding/json" + "testing" + "time" +) + +func TestDurationMarshal(t *testing.T) { + cases := map[string]struct { + in string + want Duration + wantJS string + }{ + "empty string": { + wantJS: `"0s..0s"`, + }, + "random in": { + in: "abc", + want: Duration{}, + wantJS: `"0s..0s"`, + }, + "1 dur": { + in: "1m", + want: Duration{ + least: time.Minute, + most: time.Minute, + }, + wantJS: `"1m0s..1m0s"`, + }, + "descending": { + in: "2m..1m", + want: Duration{ + least: time.Minute, + most: 2 * time.Minute, + }, + wantJS: `"1m0s..2m0s"`, + }, + "happy span": { + in: "1m..2m", + want: Duration{ + least: time.Minute, + most: 2 * time.Minute, + }, + wantJS: `"1m0s..2m0s"`, + }, + "multi unit descending": { + in: "1h1m..2m3ms", + want: Duration{ + least: 2*time.Minute + 3*time.Millisecond, + most: time.Hour + time.Minute, + }, + wantJS: `"2m0.003s..1h1m0s"`, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + var got Duration + err := json.Unmarshal([]byte(`"`+c.in+`"`), &got) + if got != c.want { + t.Fatalf("err %v: want %+v, got %+v", err, c.want, got) + } + js, _ := json.Marshal(got) + if string(js) != c.wantJS { + t.Fatalf("want marshalled %s, got %s", c.wantJS, js) + } + var got2 Duration + json.Unmarshal(js, &got2) + if got != got2 { + t.Fatalf("want unmarshal-marshal-unmarshal %+v, got %+v", got, got2) + } + }) + } +} + +func TestDurationGet(t *testing.T) { + t.Run("same", func(t *testing.T) { + d := Duration{least: time.Second, most: time.Second} + for i := 0; i < 50; i++ { + if got := d.Get(); got != time.Second { + t.Error(got) + } + } + }) + + t.Run("1ms span", func(t *testing.T) { + d := Duration{least: time.Second, most: time.Second + time.Millisecond} + for i := 0; i < 50; i++ { + if got := d.Get(); got != time.Second { + t.Error(got) + } + } + }) + + t.Run("5s span", func(t *testing.T) { + d := Duration{least: time.Second, most: 6 * time.Second} + for i := 0; i < 50; i++ { + if got := d.Get(); got > time.Second*6 || got < time.Second { + t.Error(got) + } + } + }) +}