Skip to content

Commit 1485969

Browse files
committed
feat: improve the way time.Time is stringified in spew
Now we vendored go-spew we can modify how time.Time values are handled. We can remove the imperfect fix we had in place before.
1 parent c7007d3 commit 1485969

7 files changed

Lines changed: 150 additions & 20 deletions

File tree

assert/assertions.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1951,9 +1951,6 @@ func diff(expected interface{}, actual interface{}) string {
19511951
case reflect.TypeOf(""):
19521952
e = reflect.ValueOf(expected).String()
19531953
a = reflect.ValueOf(actual).String()
1954-
case reflect.TypeOf(time.Time{}):
1955-
e = spewConfigStringerEnabled.Sdump(expected)
1956-
a = spewConfigStringerEnabled.Sdump(actual)
19571954
default:
19581955
e = spewConfig.Sdump(expected)
19591956
a = spewConfig.Sdump(actual)
@@ -1985,14 +1982,7 @@ var spewConfig = spew.ConfigState{
19851982
DisableCapacities: true,
19861983
SortKeys: true,
19871984
DisableMethods: true,
1988-
MaxDepth: 10,
1989-
}
1990-
1991-
var spewConfigStringerEnabled = spew.ConfigState{
1992-
Indent: " ",
1993-
DisablePointerAddresses: true,
1994-
DisableCapacities: true,
1995-
SortKeys: true,
1985+
EnableTimeStringer: true,
19961986
MaxDepth: 10,
19971987
}
19981988

assert/assertions_test.go

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3020,6 +3020,9 @@ Diff:
30203020
)
30213021
Equal(t, expected, actual)
30223022

3023+
timeA := time.Date(2020, 9, 24, 0, 0, 0, 0, time.UTC)
3024+
timeB := time.Date(2020, 9, 25, 0, 0, 0, 0, time.UTC)
3025+
30233026
expected = `
30243027
30253028
Diff:
@@ -3028,14 +3031,117 @@ Diff:
30283031
@@ -1,2 +1,2 @@
30293032
-(time.Time) 2020-09-24 00:00:00 +0000 UTC
30303033
+(time.Time) 2020-09-25 00:00:00 +0000 UTC
3031-
3034+
` + " \n"
3035+
3036+
actual = diff(timeA, timeB)
3037+
Equal(t, expected, actual)
3038+
3039+
expected = `
3040+
3041+
Diff:
3042+
--- Expected
3043+
+++ Actual
3044+
@@ -1,2 +1,2 @@
3045+
-(*time.Time)(2020-09-24 00:00:00 +0000 UTC)
3046+
+(*time.Time)(2020-09-25 00:00:00 +0000 UTC)
3047+
` + " \n"
3048+
3049+
actual = diff(&timeA, &timeB)
3050+
Equal(t, expected, actual)
3051+
3052+
expected = `
3053+
3054+
Diff:
3055+
--- Expected
3056+
+++ Actual
3057+
@@ -1,3 +1,3 @@
3058+
(assert.someStruct) {
3059+
- t: (time.Time) 2020-09-24 00:00:00 +0000 UTC
3060+
+ t: (time.Time) 2020-09-25 00:00:00 +0000 UTC
3061+
}
30323062
`
30333063

3064+
type someStruct struct {
3065+
t time.Time
3066+
}
3067+
30343068
actual = diff(
3035-
time.Date(2020, 9, 24, 0, 0, 0, 0, time.UTC),
3036-
time.Date(2020, 9, 25, 0, 0, 0, 0, time.UTC),
3069+
someStruct{t: timeA},
3070+
someStruct{t: timeB},
30373071
)
3072+
3073+
Equal(t, expected, actual)
3074+
3075+
// here we test the diff is stable even if the order of map keys is not
3076+
expected = `
3077+
3078+
Diff:
3079+
--- Expected
3080+
+++ Actual
3081+
@@ -1,4 +1,4 @@
3082+
(map[time.Time]int) (len=3) {
3083+
- (time.Time) 2020-09-24 00:00:00 +0000 UTC: (int) 1,
3084+
- (time.Time) 2020-09-25 00:00:00 +0000 UTC: (int) 42,
3085+
+ (time.Time) 2020-09-24 00:00:00 +0000 UTC: (int) 2,
3086+
+ (time.Time) 2020-09-26 00:00:00 +0000 UTC: (int) 42,
3087+
(time.Time) 2020-09-27 00:00:00 +0000 UTC: (int) 100
3088+
`
3089+
3090+
timeC := time.Date(2020, 9, 26, 0, 0, 0, 0, time.UTC)
3091+
timeD := time.Date(2020, 9, 27, 0, 0, 0, 0, time.UTC)
3092+
3093+
mapTimeA := map[time.Time]int{
3094+
timeA: 1,
3095+
timeB: 42,
3096+
timeD: 100,
3097+
}
3098+
3099+
mapTimeB := map[time.Time]int{
3100+
timeA: 2,
3101+
timeC: 42,
3102+
timeD: 100,
3103+
}
3104+
3105+
actual = diff(mapTimeA, mapTimeB)
30383106
Equal(t, expected, actual)
3107+
3108+
// here we test the time are ordered against the time.Time.Before() and not the time.Time.String()
3109+
expected = `
3110+
3111+
Diff:
3112+
--- Expected
3113+
+++ Actual
3114+
@@ -1,5 +1,5 @@
3115+
(map[time.Time]int) (len=3) {
3116+
- (time.Time) 2020-09-24 00:00:00 +0000 UTC: (int) 1,
3117+
- (time.Time) 2020-09-25 00:00:00 +0900 JST: (int) 100,
3118+
- (time.Time) 2020-09-25 00:00:00 +0000 UTC: (int) 42
3119+
+ (time.Time) 2020-09-24 00:00:00 +0900 JST: (int) 42,
3120+
+ (time.Time) 2020-09-24 00:00:00 +0000 UTC: (int) 2,
3121+
+ (time.Time) 2020-09-25 00:00:00 +0900 JST: (int) 100
3122+
}
3123+
`
3124+
3125+
loc := time.FixedZone("JST", 9*60*60)
3126+
3127+
timeE := time.Date(2020, 9, 24, 0, 0, 0, 0, loc)
3128+
timeF := time.Date(2020, 9, 25, 0, 0, 0, 0, loc)
3129+
3130+
mapTimeLocA := map[time.Time]int{
3131+
timeA: 1,
3132+
timeB: 42,
3133+
timeF: 100,
3134+
}
3135+
3136+
mapTimeLocB := map[time.Time]int{
3137+
timeA: 2,
3138+
timeE: 42,
3139+
timeF: 100,
3140+
}
3141+
3142+
actual = diff(mapTimeLocA, mapTimeLocB)
3143+
Equal(t, expected, actual)
3144+
30393145
}
30403146

30413147
func TestTimeEqualityErrorFormatting(t *testing.T) {

internal/spew/common.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"reflect"
2424
"sort"
2525
"strconv"
26+
"time"
2627
)
2728

2829
// Some constants in the form of bytes to avoid string overhead. This mirrors
@@ -227,7 +228,7 @@ type valuesSorter struct {
227228
// ConfigState to decide if and how to populate those surrogate keys.
228229
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
229230
vs := &valuesSorter{values: values, cs: cs}
230-
if canSortSimply(vs.values[0].Kind()) {
231+
if canSortSimply(vs.values[0]) {
231232
return vs
232233
}
233234
if !cs.DisableMethods {
@@ -253,9 +254,9 @@ func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
253254
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
254255
// directly, or whether it should be considered for sorting by surrogate keys
255256
// (if the ConfigState allows it).
256-
func canSortSimply(kind reflect.Kind) bool {
257+
func canSortSimply(v reflect.Value) bool {
257258
// This switch parallels valueSortLess, except for the default case.
258-
switch kind {
259+
switch v.Kind() {
259260
case reflect.Bool:
260261
return true
261262
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
@@ -271,7 +272,8 @@ func canSortSimply(kind reflect.Kind) bool {
271272
case reflect.Array:
272273
return true
273274
}
274-
return false
275+
276+
return isTime(v)
275277
}
276278

277279
// Len returns the number of values in the slice. It is part of the
@@ -318,6 +320,13 @@ func valueSortLess(a, b reflect.Value) bool {
318320
return valueSortLess(av, bv)
319321
}
320322
}
323+
324+
if isTime(a) && a.CanInterface() && b.CanInterface() {
325+
timeA, okA := a.Interface().(time.Time)
326+
timeB, okB := b.Interface().(time.Time)
327+
return okA && okB && timeA.Before(timeB)
328+
}
329+
321330
return a.String() < b.String()
322331
}
323332

@@ -339,3 +348,8 @@ func sortValues(values []reflect.Value, cs *ConfigState) {
339348
}
340349
sort.Sort(newValuesSorter(values, cs))
341350
}
351+
352+
// isTime returns whether the passed reflect.Value is a [time.Time] struct.
353+
func isTime(v reflect.Value) bool {
354+
return v.Kind() == reflect.Struct && v.Type().PkgPath() == "time" && v.Type().Name() == "Time"
355+
}

internal/spew/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ type ConfigState struct {
5353
// invoked for types that implement them.
5454
DisableMethods bool
5555

56+
// EnableTimeStringer specifies whether to invoke the Stringer interface on
57+
// time.Time values even when DisableMethods is true. This is useful to get
58+
// human-readable output for time.Time values while keeping method calls
59+
// disabled for other types.
60+
EnableTimeStringer bool
61+
5662
// DisablePointerMethods specifies whether or not to check for and invoke
5763
// error and Stringer interfaces on types which only accept a pointer
5864
// receiver when the current type is not a pointer.

internal/spew/dump.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func (d *dumpState) dump(v reflect.Value) {
301301

302302
// Call Stringer/error interfaces if they exist and the handle methods flag
303303
// is enabled
304-
if !d.cs.DisableMethods {
304+
if !d.cs.DisableMethods || (d.cs.EnableTimeStringer && isTime(v)) {
305305
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
306306
if handled := handleMethods(d.cs, d.w, v); handled {
307307
return

internal/spew/format.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (f *formatState) format(v reflect.Value) {
222222

223223
// Call Stringer/error interfaces if they exist and the handle methods
224224
// flag is enabled.
225-
if !f.cs.DisableMethods {
225+
if !f.cs.DisableMethods || (f.cs.EnableTimeStringer && isTime(v)) {
226226
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
227227
if handled := handleMethods(f.cs, f.fs, v); handled {
228228
return

internal/spew/spew_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"io/ioutil"
2323
"os"
2424
"testing"
25+
"time"
2526

2627
"github.com/stretchr/testify/internal/spew"
2728
)
@@ -127,6 +128,7 @@ func initSpewTests() {
127128
// Config states with various settings.
128129
scsDefault := spew.NewDefaultConfig()
129130
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
131+
scsNoMethodsButTimeStringer := &spew.ConfigState{Indent: " ", DisableMethods: true, EnableTimeStringer: true}
130132
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
131133
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
132134
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
@@ -138,6 +140,8 @@ func initSpewTests() {
138140
ts := stringer("test")
139141
tps := pstringer("test")
140142

143+
tm := time.Date(2006, time.January, 2, 15, 4, 5, 999999999, time.UTC)
144+
141145
type ptrTester struct {
142146
s *struct{}
143147
}
@@ -203,6 +207,16 @@ func initSpewTests() {
203207
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
204208
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
205209
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
210+
211+
// time.Time formatting:
212+
{scsDefault, fCSFprint, "", tm, "2006-01-02 15:04:05.999999999 +0000 UTC"},
213+
{scsNoMethods, fCSFprint, "", tm, scsNoMethods.Sprint(tm)},
214+
{scsNoMethodsButTimeStringer, fCSFprint, "", tm, "2006-01-02 15:04:05.999999999 +0000 UTC"},
215+
216+
// *time.Time formatting:
217+
{scsDefault, fCSFprint, "", &tm, "<*>2006-01-02 15:04:05.999999999 +0000 UTC"},
218+
{scsNoMethods, fCSFprint, "", &tm, scsNoMethods.Sprint(&tm)},
219+
{scsNoMethodsButTimeStringer, fCSFprint, "", &tm, "<*>2006-01-02 15:04:05.999999999 +0000 UTC"},
206220
}
207221
}
208222

0 commit comments

Comments
 (0)