-
Notifications
You must be signed in to change notification settings - Fork 15
/
fakefill.go
138 lines (127 loc) · 3.79 KB
/
fakefill.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package testingx
import (
"math/rand"
"reflect"
"sync"
"time"
)
// FakeFiller fills specific data structures with random data. The only
// exception to this behaviour is time.Time, which is instead filled
// with the current time plus a small random number of seconds.
//
// We use this implementation to initialize data in our model. The code
// has been written with that in mind. It will require some hammering in
// case we extend the model with new field types.
//
// Caveat: this kind of fillter does not support filling interfaces
// and channels and other complex types. The current behavior when this
// kind of data types is encountered is to just ignore them.
//
// This struct is quite limited in scope and we can fill only the
// structures you typically send over as JSONs.
//
// As part of future work, we aim to investigate whether we can
// replace this implementation with https://go.dev/blog/fuzz-beta.
type FakeFiller struct {
// mu provides mutual exclusion
mu sync.Mutex
// Now is OPTIONAL and allows to mock the current time
Now func() time.Time
// rnd is the random number generator and is
// automatically initialized on first use
rnd *rand.Rand
}
func (ff *FakeFiller) getRandLocked() *rand.Rand {
if ff.rnd == nil {
now := time.Now
if ff.Now != nil {
now = ff.Now
}
ff.rnd = rand.New(rand.NewSource(now().UnixNano())) // #nosec G404 -- used for testing
}
return ff.rnd
}
func (ff *FakeFiller) getRandomString() string {
defer ff.mu.Unlock()
ff.mu.Lock()
rnd := ff.getRandLocked()
n := rnd.Intn(63) + 1
// See https://stackoverflow.com/a/31832326
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rnd.Intn(len(letterRunes))]
}
return string(b)
}
func (ff *FakeFiller) getRandomInt64() int64 {
defer ff.mu.Unlock()
ff.mu.Lock()
rnd := ff.getRandLocked()
return rnd.Int63()
}
func (ff *FakeFiller) getRandomBool() bool {
defer ff.mu.Unlock()
ff.mu.Lock()
rnd := ff.getRandLocked()
return rnd.Float64() >= 0.5
}
func (ff *FakeFiller) getRandomSmallPositiveInt() int {
defer ff.mu.Unlock()
ff.mu.Lock()
rnd := ff.getRandLocked()
return int(rnd.Int63n(8)) + 1 // safe cast
}
func (ff *FakeFiller) doFill(v reflect.Value) {
for v.Type().Kind() == reflect.Ptr {
if v.IsNil() {
// if the pointer is nil, allocate an element
v.Set(reflect.New(v.Type().Elem()))
}
// switch to the element
v = v.Elem()
}
switch v.Type().Kind() {
case reflect.String:
v.SetString(ff.getRandomString())
case reflect.Int64:
v.SetInt(ff.getRandomInt64())
case reflect.Bool:
v.SetBool(ff.getRandomBool())
case reflect.Struct:
if v.Type().String() == "time.Time" {
// Implementation note: we treat the time specially
// and we avoid attempting to set its fields.
v.Set(reflect.ValueOf(time.Now().Add(
time.Duration(ff.getRandomSmallPositiveInt()) * time.Second)))
return
}
for idx := 0; idx < v.NumField(); idx++ {
ff.doFill(v.Field(idx)) // visit all fields
}
case reflect.Slice:
kind := v.Type().Elem()
total := ff.getRandomSmallPositiveInt()
for idx := 0; idx < total; idx++ {
value := reflect.New(kind) // make a new element
ff.doFill(value)
v.Set(reflect.Append(v, value.Elem())) // append to slice
}
case reflect.Map:
if v.Type().Key().Kind() != reflect.String {
panic("fakefill: we only support string key types")
}
v.Set(reflect.MakeMap(v.Type())) // we need to init the map
total := ff.getRandomSmallPositiveInt()
kind := v.Type().Elem()
for idx := 0; idx < total; idx++ {
value := reflect.New(kind)
ff.doFill(value)
v.SetMapIndex(reflect.ValueOf(ff.getRandomString()), value.Elem())
}
}
}
// Fill fills the input structure or pointer with random data.
func (ff *FakeFiller) Fill(in interface{}) {
ff.doFill(reflect.ValueOf(in))
}