-
Notifications
You must be signed in to change notification settings - Fork 3
/
entry.go
361 lines (324 loc) · 9.06 KB
/
entry.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package zltest
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
)
// KeyStatus represents a status of searching and deserialization
// of a key in log entry.
type KeyStatus string
const (
// KeyFound is used when a field key is found successfully.
KeyFound KeyStatus = "KeyFound"
// KeyBadType is used when a field key is found, but it's not of expected type.
KeyBadType KeyStatus = "KeyBadType"
// KeyMissing is used when a field key is not in the log entry.
KeyMissing KeyStatus = "KeyMissing"
// KeyBadFormat is used when a field key is found but its format is wrong.
KeyBadFormat KeyStatus = "KeyBadFormat"
)
// Entry represents one zerolog log entry.
type Entry struct {
raw string // Entry as it was written to the writer.
m map[string]interface{} // JSON decoded log entry.
t T // Test manager.
}
// String implements fmt.Stringer interface and returns log entry
// as it was written to the writer.
func (ent *Entry) String() string {
return ent.raw
}
// ExpKey tests log entry has a field key.
func (ent *Entry) ExpKey(key string) {
ent.t.Helper()
if _, ok := ent.m[key]; !ok {
ent.t.Errorf("expected %s field to be present", key)
}
}
// NotExpKey tests log entry has no field key.
func (ent *Entry) NotExpKey(key string) {
ent.t.Helper()
if _, ok := ent.m[key]; ok {
ent.t.Errorf("expected %s field to be not present", key)
}
}
// ExpNumKeys tests log entry has n keys.
func (ent *Entry) ExpNumKeys(n int) {
ent.t.Helper()
if len(ent.m) != n {
ent.t.Errorf("expected %d fields but got %d", n, len(ent.m))
}
}
// Str returns log entry field key as a string.
func (ent *Entry) Str(key string) (string, KeyStatus) {
ent.t.Helper()
if itf, ok := ent.m[key]; ok {
if got, ok := itf.(string); ok {
return got, KeyFound
}
return "", KeyBadType
}
return "", KeyMissing
}
// ExpStr tests log entry has a field key, its value is a string,
// and it's equal to exp.
func (ent *Entry) ExpStr(key string, exp string) {
ent.t.Helper()
if err := ent.expStr(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expStr(key string, exp string) string {
ent.t.Helper()
got, status := ent.Str(key)
if status == KeyFound {
if got != exp {
return fmt.Sprintf(
"expected entry key '%s' to have value '%s' but got '%s'",
key,
exp,
got,
)
}
return ""
}
return formatError(ent.t, status, key, "string")
}
// ExpStrContains tests log entry has a field key, its value is a string,
// and it contains exp.
func (ent *Entry) ExpStrContains(key string, exp string) {
ent.t.Helper()
if err := ent.expStrContains(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expStrContains(key string, exp string) string {
ent.t.Helper()
got, status := ent.Str(key)
if status == KeyFound {
if !strings.Contains(got, exp) {
return fmt.Sprintf(
"expected entry key '%s' to contain '%s' but got '%s'",
key,
exp,
got,
)
}
return ""
}
return formatError(ent.t, status, key, "string")
}
// Float64 returns log entry field key as a float64 type.
func (ent *Entry) Float64(key string) (float64, KeyStatus) {
ent.t.Helper()
if itf, ok := ent.m[key]; ok {
if got, ok := itf.(float64); ok {
return got, KeyFound
}
return 0, KeyBadType
}
return 0, KeyMissing
}
// Bool returns log entry field key as a boolean type.
func (ent *Entry) Bool(key string) (bool, KeyStatus) {
ent.t.Helper()
if itf, ok := ent.m[key]; ok {
if got, ok := itf.(bool); ok {
return got, KeyFound
}
return false, KeyBadType
}
return false, KeyMissing
}
// ExpBool tests log entry has a field key, its value is boolean and equal to exp.
func (ent *Entry) ExpBool(key string, exp bool) {
ent.t.Helper()
if err := ent.expBool(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expBool(key string, exp bool) string {
ent.t.Helper()
got, status := ent.Bool(key)
if status == KeyFound {
if got != exp {
return fmt.Sprintf(
"expected entry key '%s' to have value '%v' but got '%v'",
key,
exp,
got,
)
}
return ""
}
return formatError(ent.t, status, key, "bool")
}
// Time returns log entry field key as a time.Time. It uses
// zerolog.TimeFieldFormat to parse the time string representation.
func (ent *Entry) Time(key string) (time.Time, KeyStatus) {
ent.t.Helper()
if itf, ok := ent.m[key]; ok {
if got, ok := itf.(string); ok {
tim, err := time.Parse(zerolog.TimeFieldFormat, got)
if err != nil {
return time.Time{}, KeyBadFormat
}
return tim, KeyFound
}
return time.Time{}, KeyBadType
}
return time.Time{}, KeyMissing
}
// ExpTime tests log entry has a field key, its value is a string representing
// time in zerolog.TimeFieldFormat and it's equal to exp.
func (ent *Entry) ExpTime(key string, exp time.Time) {
ent.t.Helper()
if err := ent.expTime(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expTime(key string, exp time.Time) string {
ent.t.Helper()
got, status := ent.Time(key)
if status == KeyFound {
if !exp.Equal(got) {
return fmt.Sprintf("expected entry '%s' to be '%s' but is '%s'",
key,
exp.Format(zerolog.TimeFieldFormat),
got.Format(zerolog.TimeFieldFormat),
)
}
return ""
}
return formatError(ent.t, status, key, "string")
}
// ExpTimeWithin tests log entry has a field key, its value is a string
// representing time in zerolog.TimeFieldFormat and it's equal to exp time.
// The actual time may be within +/- diff.
func (ent *Entry) ExpTimeWithin(key string, exp time.Time, diff time.Duration) {
ent.t.Helper()
got, status := ent.Time(key)
if status == KeyFound {
gotD := math.Abs(float64(exp.Sub(got)))
if gotD > float64(diff) {
ent.t.Errorf("expected entry '%s' to be within '%s' but difference is '%s'",
key,
diff.String(),
time.Duration(gotD).String(),
)
}
return
}
ent.t.Error(formatError(ent.t, status, key, "string"))
}
// ExpDur tests log entry has a field key and its value is equal to exp
// time.Duration. The duration vale in the entry is multiplied by
// zerolog.DurationFieldUnit before the comparison.
func (ent *Entry) ExpDur(key string, exp time.Duration) {
ent.t.Helper()
if err := ent.expDur(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expDur(key string, exp time.Duration) string {
ent.t.Helper()
got, status := ent.Float64(key)
if status == KeyFound {
gotD := time.Duration(int(got)) * zerolog.DurationFieldUnit
if gotD != exp {
return fmt.Sprintf(
"expected entry key '%s' to have value '%d' (%s) but got '%d' (%s)",
key,
exp/zerolog.DurationFieldUnit,
exp.String(),
gotD/zerolog.DurationFieldUnit,
gotD.String(),
)
}
return ""
}
return formatError(ent.t, status, key, "number")
}
// ExpLoggedWithin tests log entry was logged at exp time. The actual time
// may be within +/- diff.
func (ent *Entry) ExpLoggedWithin(exp time.Time, diff time.Duration) {
ent.t.Helper()
ent.ExpTimeWithin(zerolog.TimestampFieldName, exp, diff)
}
// ExpMsg tests log entry message field (zerolog.MessageFieldName) is
// equal to exp.
func (ent *Entry) ExpMsg(exp string) {
ent.t.Helper()
ent.ExpStr(zerolog.MessageFieldName, exp)
}
// ExpError tests log entry message field (zerolog.ErrorFieldName) is
// equal to exp.
func (ent *Entry) ExpError(exp string) {
ent.t.Helper()
ent.ExpStr(zerolog.ErrorFieldName, exp)
}
// ExpErr tests log entry message field (zerolog.ErrorFieldName) is
// equal to exp error message.
func (ent *Entry) ExpErr(exp error) {
ent.t.Helper()
ent.ExpError(exp.Error())
}
// ExpLevel tests log entry level field (zerolog.LevelFieldName) is
// equal to exp.
func (ent *Entry) ExpLevel(exp zerolog.Level) {
ent.t.Helper()
ent.ExpStr(zerolog.LevelFieldName, exp.String())
}
// ExpNum tests log entry has a field key and its numerical value is equal to exp.
func (ent *Entry) ExpNum(key string, exp float64) {
ent.t.Helper()
if err := ent.expNum(key, exp); err != "" {
ent.t.Error(err)
}
}
func (ent *Entry) expNum(key string, exp float64) string {
ent.t.Helper()
got, status := ent.Float64(key)
if status == KeyFound {
if got != exp {
expS := strconv.FormatFloat(exp, 'f', -1, 64)
gotS := strconv.FormatFloat(got, 'f', -1, 64)
return fmt.Sprintf(
"expected entry key '%s' to have value '%s' but got '%s'",
key,
expS,
gotS,
)
}
return ""
}
return formatError(ent.t, status, key, "number")
}
// Map returns log entry key as a map.
func (ent *Entry) Map(key string) (map[string]interface{}, KeyStatus) {
ent.t.Helper()
if itf, ok := ent.m[key]; ok {
if got, ok := itf.(map[string]interface{}); ok {
return got, KeyFound
}
return nil, KeyBadType
}
return nil, KeyMissing
}
// formatError formats error message based on status of log entry key search.
func formatError(t T, status KeyStatus, key, typ string) string {
t.Helper()
switch status {
case KeyMissing:
return fmt.Sprintf("expected entry to have key '%s'", key)
case KeyBadType:
return fmt.Sprintf("expected entry key '%s' to be '%s'", key, typ)
case KeyBadFormat:
return fmt.Sprintf("key '%s' in a wrong format", key)
default:
return fmt.Sprintf("invalid KeyStatus '%s'", status)
}
}