Skip to content

Commit 47d7a34

Browse files
committed
random.Random QoL update
Align Random.FloatBetween to include min and max values: To maintain consistency, FloatBetween now includes both min and max values when generating results, similar to the behaviour of IntBetween or DurationBetween. This ensures uniformity across numeric range functions. Allow inverse ranges for Between functions: - DurationBetween - TimeBetween - FloatBetween
1 parent d41e760 commit 47d7a34

File tree

2 files changed

+110
-30
lines changed

2 files changed

+110
-30
lines changed

random/Random.go

+27-7
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,20 @@ func (r *Random) FloatN(n float64) float64 {
6868
return r.Float64() * n
6969
}
7070

71-
// FloatBetween returns a float between the given min and max value range.
71+
// FloatBetween returns a float between the given min and max value range. [min, max]
7272
func (r *Random) FloatBetween(min, max float64) float64 {
73-
return min + r.Float64()*(max-min)
73+
const (
74+
whenMin = 0
75+
whenMax = 1000
76+
)
77+
switch r.IntB(whenMin, whenMax) {
78+
case whenMin:
79+
return min
80+
case whenMax:
81+
return max
82+
default: // when between min and max
83+
return min + r.Float64()*(max-min)
84+
}
7485
}
7586

7687
// FloatB returns a float between the given min and max value range.
@@ -80,6 +91,7 @@ func (r *Random) FloatB(min, max float64) float64 {
8091

8192
// IntBetween returns an int based on the received int range's [min,max].
8293
func (r *Random) IntBetween(min, max int) int {
94+
min, max = correct(min, max)
8395
return min + r.IntN((max+1)-min)
8496
}
8597

@@ -90,6 +102,7 @@ func (r *Random) DurationB(min, max time.Duration) time.Duration {
90102

91103
// DurationBetween returns an duration based on the received duration range's [min,max].
92104
func (r *Random) DurationBetween(min, max time.Duration) time.Duration {
105+
93106
return time.Duration(r.IntBetween(int(min), int(max)))
94107
}
95108

@@ -151,14 +164,10 @@ func (r *Random) StringNWithCharset(length int, charset string) string {
151164
return string(bytes)
152165
}
153166

154-
const panicInvalidTimeRangeMessage = `invalid time range given for TimeBetween, [to] time is earlier in time than [from] time.
155-
[from]: %s
156-
[to]: %s`
157-
158167
// TimeBetween returns, as an time.Time, a non-negative pseudo-random time in [from,to].
159168
func (r *Random) TimeBetween(from, to time.Time) time.Time {
160169
if to.Before(from) {
161-
panic(fmt.Sprintf(panicInvalidTimeRangeMessage, from.Format(time.RFC3339), to.Format(time.RFC3339)))
170+
from, to = to, from
162171
}
163172
return time.Unix(int64(r.IntBetween(int(from.Unix()), int(to.Unix()))), 0).UTC()
164173
}
@@ -278,3 +287,14 @@ func (r *Random) Repeat(min, max int, do func()) int {
278287
func (r *Random) Domain() string {
279288
return r.Pick(fixtureStrings.domains).(string)
280289
}
290+
291+
type number interface {
292+
int
293+
}
294+
295+
func correct[N number](min, max N) (N, N) {
296+
if max < min {
297+
return max, min
298+
}
299+
return min, max
300+
}

random/Random_test.go

+83-23
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ import (
2121

2222
func TestRandom(t *testing.T) {
2323
s := testcase.NewSpec(t)
24+
25+
source := let.Var(s, func(t *testcase.T) rand.Source {
26+
return rand.NewSource(int64(t.Random.Int()))
27+
})
28+
2429
rnd := testcase.Let(s, func(t *testcase.T) *random.Random {
25-
return &random.Random{Source: rand.NewSource(time.Now().Unix())}
30+
return &random.Random{Source: source.Get(t)}
2631
})
2732

2833
SpecRandomMethods(s, rnd)
@@ -54,6 +59,10 @@ func TestRandom(t *testing.T) {
5459
t.Must.Equal(u1, u2)
5560
})
5661
})
62+
63+
s.Context("between-methods-behaviour", func(s *testcase.Spec) {
64+
SpecRandomBetweenMethodBehaviour(s, rnd)
65+
})
5766
}
5867

5968
func SpecRandomMethods(s *testcase.Spec, rnd testcase.Var[*random.Random]) {
@@ -825,6 +834,21 @@ func specFloatBetween(s *testcase.Spec, subject func(t *testcase.T, min, max flo
825834
assert.Must(t).True(out <= max.Get(t), `expected that out is <= than max`)
826835
})
827836

837+
s.Then("min and max are possible results", func(t *testcase.T) {
838+
var gotMin, gotMax bool
839+
t.Eventually(func(t *testcase.T) {
840+
out := act(t)
841+
if out == min.Get(t) {
842+
gotMin = true
843+
}
844+
if out == max.Get(t) {
845+
gotMax = true
846+
}
847+
assert.True(t, gotMin, "expected that min is part of the possible results")
848+
assert.True(t, gotMax, "expected that max is part of the possible results")
849+
})
850+
})
851+
828852
s.And(`min and max is in the negative range`, func(s *testcase.Spec) {
829853
min.LetValue(s, -128)
830854
max.LetValue(s, -64)
@@ -836,12 +860,20 @@ func specFloatBetween(s *testcase.Spec, subject func(t *testcase.T, min, max flo
836860
})
837861
})
838862

839-
s.And(`min and max equal`, func(s *testcase.Spec) {
840-
max.Let(s, min.Get)
841-
842-
s.Then(`it returns the min and max value since the range can only have one value`, func(t *testcase.T) {
843-
t.Must.Equal(max.Get(t), act(t))
844-
})
863+
s.Test("smoke", func(t *testcase.T) {
864+
var smoke = func(min, max float64) {
865+
t.Eventually(func(t *testcase.T) {
866+
out := subject(t, min, max)
867+
assert.NotEqual(t, out, min)
868+
assert.NotEqual(t, out, max)
869+
assert.True(t, min < out)
870+
assert.True(t, out < max)
871+
})
872+
}
873+
smoke(0, 0.1)
874+
smoke(0, 0.01)
875+
smoke(0, 0.001)
876+
smoke(0, 0.0001)
845877
})
846878
}
847879

@@ -970,18 +1002,18 @@ func SpecTimeBetween(s *testcase.Spec, rnd testcase.Var[*random.Random], sbj fun
9701002
toTime := testcase.Let(s, func(t *testcase.T) time.Time {
9711003
return fromTime.Get(t).Add(24 * time.Hour)
9721004
})
973-
var subject = func(t *testcase.T) time.Time {
1005+
act := let.Act(func(t *testcase.T) time.Time {
9741006
return sbj(t)(fromTime.Get(t), toTime.Get(t))
975-
}
1007+
})
9761008

9771009
s.Then(`it will return a date between the given time range including 'from' and excluding 'to'`, func(t *testcase.T) {
978-
out := subject(t)
1010+
out := act(t)
9791011
assert.Must(t).True(fromTime.Get(t).Unix() <= out.Unix(), `expected that from <= than out`)
9801012
assert.Must(t).True(out.Unix() < toTime.Get(t).Unix(), `expected that out is < than to`)
9811013
})
9821014

9831015
s.Then(`it will generate different time on each call`, func(t *testcase.T) {
984-
assert.Must(t).NotEqual(subject(t), subject(t))
1016+
assert.Must(t).NotEqual(act(t), act(t))
9851017
})
9861018

9871019
s.And(`from is before 1970-01-01 (unix timestamp 0)`, func(s *testcase.Spec) {
@@ -993,14 +1025,14 @@ func SpecTimeBetween(s *testcase.Spec, rnd testcase.Var[*random.Random], sbj fun
9931025
})
9941026

9951027
s.Then(`it will generate a random time between 'from' and 'to'`, func(t *testcase.T) {
996-
out := subject(t)
1028+
out := act(t)
9971029
assert.Must(t).True(fromTime.Get(t).Unix() <= out.Unix(), `expected that from <= than out`)
9981030
assert.Must(t).True(out.Unix() < toTime.Get(t).Unix(), `expected that out is < than to`)
9991031
})
10001032
})
10011033

10021034
s.Then(`result is safe to format into RFC3339`, func(t *testcase.T) {
1003-
t1 := subject(t)
1035+
t1 := act(t)
10041036
t2, _ := time.Parse(time.RFC3339, t1.Format(time.RFC3339))
10051037
t.Must.Equal(t1.UTC(), t2.UTC())
10061038
})
@@ -1013,16 +1045,10 @@ func SpecTimeBetween(s *testcase.Spec, rnd testcase.Var[*random.Random], sbj fun
10131045
return fromTime.Get(t).Add(-1 * time.Second)
10141046
})
10151047

1016-
s.Then("", func(t *testcase.T) {
1017-
out := assert.Panic(t, func() { subject(t) })
1018-
assert.NotNil(t, out)
1019-
panicMessage := fmt.Sprintf("%v", out)
1020-
assert.Contain(t, panicMessage, `invalid`)
1021-
assert.Contain(t, panicMessage, `[to]`)
1022-
assert.Contain(t, panicMessage, `earlier`)
1023-
assert.Contain(t, panicMessage, `[from]`)
1024-
assert.Contain(t, panicMessage, fromTime.Get(t).Format(time.RFC3339))
1025-
assert.Contain(t, panicMessage, toTime.Get(t).Format(time.RFC3339))
1048+
s.Then("to and from swapped", func(t *testcase.T) {
1049+
out := act(t)
1050+
assert.True(t, toTime.Get(t).Before(out) || toTime.Get(t).Equal(out))
1051+
assert.True(t, fromTime.Get(t).Equal(out) || fromTime.Get(t).After(out))
10261052
})
10271053
})
10281054
}
@@ -1094,3 +1120,37 @@ func TestPick(t *testing.T) {
10941120
})
10951121
})
10961122
}
1123+
1124+
func SpecRandomBetweenMethodBehaviour(s *testcase.Spec, rnd testcase.Var[*random.Random]) {
1125+
s.Test("IntBetween", func(t *testcase.T) {
1126+
min := -100
1127+
max := 100
1128+
out := rnd.Get(t).IntBetween(max, min)
1129+
assert.True(t, min <= out)
1130+
assert.True(t, out <= max)
1131+
})
1132+
1133+
s.Test("DurationBetween", func(t *testcase.T) {
1134+
min := -time.Hour
1135+
max := -time.Second
1136+
out := rnd.Get(t).DurationBetween(max, min)
1137+
assert.True(t, min <= out)
1138+
assert.True(t, out <= max)
1139+
})
1140+
1141+
s.Test("FloatBetween", func(t *testcase.T) {
1142+
min := -10.0
1143+
max := 10.0
1144+
out := rnd.Get(t).FloatBetween(max, min)
1145+
assert.True(t, min <= out)
1146+
assert.True(t, out <= max)
1147+
})
1148+
1149+
s.Test("TimeBetween", func(t *testcase.T) {
1150+
min := time.Now()
1151+
max := min.AddDate(0, 0, 1)
1152+
out := rnd.Get(t).TimeBetween(max, min)
1153+
assert.True(t, min.Before(out) || min.Equal(out))
1154+
assert.True(t, max.Equal(out) || max.After(out))
1155+
})
1156+
}

0 commit comments

Comments
 (0)