@@ -27,6 +27,12 @@ func (s *setups) append(setup Setuper) {
27
27
* s = append (* s , setup )
28
28
}
29
29
30
+ func (s * setups ) tryAppend (val any ) {
31
+ if setup , ok := val .(Setuper ); ok {
32
+ s .append (setup )
33
+ }
34
+ }
35
+
30
36
func (s * NullSetup ) Setup () {}
31
37
32
38
type Fixture struct {
@@ -35,26 +41,6 @@ type Fixture struct {
35
41
36
42
func (f * Fixture ) SetTB (tb testing.TB ) { f .TB = tb }
37
43
38
- func typePkgPath (t reflect.Type ) (string , bool ) {
39
- k := t .Kind ()
40
- if k == reflect .Pointer {
41
- t = t .Elem ()
42
- k = t .Kind ()
43
- }
44
- if k == reflect .Struct {
45
- return t .PkgPath (), true
46
- }
47
- return "" , false
48
- }
49
-
50
- // valuePkgPath is specifically for filtering struct types based on their
51
- // package path. The use of an ok return value is solely for this specific
52
- // purpose alone; and the function may not be a good fit in other applications.
53
- func valuePkgPath (val reflect.Value ) (string , bool ) {
54
- t := val .Type ()
55
- return typePkgPath (t )
56
- }
57
-
58
44
func (f * FixtureSetup [T ]) Setup () Setuper {
59
45
vType := reflect .TypeFor [T ]()
60
46
f .TB .Helper ()
@@ -63,19 +49,19 @@ func (f *FixtureSetup[T]) Setup() Setuper {
63
49
}
64
50
vType = vType .Elem ()
65
51
f .pkgPath = vType .PkgPath ()
52
+ f .depVals = make ([]reflect.Value , len (f .Deps ))
53
+ for i , d := range f .Deps {
54
+ f .depVals [i ] = reflect .ValueOf (d )
55
+ }
66
56
return f .setup (reflect .ValueOf (f .Fixture ))
67
57
}
68
58
69
59
func (f FixtureSetup [T ]) defaultInclude (val reflect.Value ) bool {
70
- var currPath , expPath string
71
- var ok bool
72
- if currPath , ok = valuePkgPath (val ); ok {
73
- expPath , ok = typePkgPath (reflect .TypeFor [T ]())
74
- }
75
- if ! ok {
76
- return false
60
+ var underlyingType = val .Type ()
61
+ if underlyingType .Kind () == reflect .Pointer {
62
+ underlyingType = underlyingType .Elem ()
77
63
}
78
- return strings .HasPrefix ( currPath , expPath )
64
+ return strings .HasSuffix ( underlyingType . Name (), "Fixture" )
79
65
}
80
66
81
67
func (f FixtureSetup [T ]) include (val reflect.Value ) bool {
@@ -91,53 +77,79 @@ type FixtureSetup[T FixtureInit] struct {
91
77
Include func (val reflect.Value ) bool
92
78
Deps []any
93
79
80
+ depVals []reflect.Value
94
81
pkgPath string
95
82
}
96
83
97
- func (f FixtureSetup [T ]) setup (val reflect.Value ) Setuper {
84
+ func (f * FixtureSetup [T ]) setupStruct (val reflect.Value , typ reflect. Type ) * setups {
98
85
var setups = new (setups )
99
- if ! f .include (val ) {
100
- return setups
86
+ fields:
87
+ for _ , field := range reflect .VisibleFields (typ ) {
88
+ if len (field .Index ) > 1 || ! field .IsExported () {
89
+ // Don't set fields of embedded or unexported fields
90
+ continue
91
+ }
92
+ fieldVal := val .FieldByIndex (field .Index )
93
+ if ! f .include (fieldVal ) {
94
+ continue
95
+ }
96
+ for _ , depVal := range f .depVals {
97
+ if field .Type == depVal .Type () {
98
+ fieldVal .Set (depVal )
99
+ continue fields
100
+ }
101
+ }
102
+ if field .Type .Kind () == reflect .Pointer && fieldVal .IsNil () {
103
+ fieldVal .Set (reflect .New (field .Type .Elem ()))
104
+ f .depVals = append (f .depVals , fieldVal )
105
+ }
106
+ setups .append (f .setup (fieldVal ))
101
107
}
108
+ return setups
109
+ }
110
+
111
+ func (f * FixtureSetup [T ]) setup (val reflect.Value ) Setuper {
112
+ var setups = new (setups )
102
113
if val .Kind () == reflect .Pointer {
103
114
if val .IsNil () {
104
115
return & NullSetup {}
105
116
}
106
117
val = val .Elem ()
107
118
}
119
+
108
120
typ := val .Type ()
109
- for _ , field := range reflect .VisibleFields (typ ) {
110
- if len (field .Index ) > 1 { // Don't set fields of embedded members
111
- continue
112
- }
113
- for _ , dep := range f .Deps {
114
- depVal := reflect .ValueOf (dep )
115
- if field .Type == reflect .TypeOf (dep ) {
116
- val .FieldByIndex (field .Index ).Set (depVal )
117
- continue
118
- }
119
- }
120
- setups .append (f .setup (val .FieldByIndex (field .Index )))
121
+ if typ .Kind () == reflect .Struct {
122
+ setups .append (f .setupStruct (val , typ ))
121
123
}
122
- actualVal := val .Interface ()
123
- if setup , ok := actualVal .(Setuper ); ok {
124
- setups .append (setup )
125
- } else if val .CanAddr () {
126
- if setup , ok := val .Addr ().Interface ().(Setuper ); ok {
127
- setups .append (setup )
128
- }
129
- }
130
- if init , ok := actualVal .(FixtureInit ); ok {
131
- init .SetTB (f .TB )
132
- } else if val .CanAddr () {
133
- if init , ok := val .Addr ().Interface ().(FixtureInit ); ok {
134
- init .SetTB (f .TB )
135
- }
124
+
125
+ if ! val .CanAddr () {
126
+ asAny := val .Interface ()
127
+ setups .tryAppend (asAny )
128
+ f .tryInit (asAny )
129
+ } else {
130
+ // val must be addressable, as both Setup and Init are mutating functions.
131
+ //
132
+ // Val itself may be a non-pointer field in a pointer struct, which
133
+ // means val.Interface() itself is not a Setuper or Initer*, but we can
134
+ // still get a pointer using Addr() because it's inside an addressable
135
+ // struct.
136
+ //
137
+ // \* While a non-pointer val may _implement_ the interfaces, the
138
+ // implementation would be wrong, as they couldn't mutate.
139
+
140
+ asAny := val .Addr ().Interface ()
141
+ setups .tryAppend (asAny )
142
+ f .tryInit (asAny )
136
143
}
137
- // var res *NullSetup
138
144
return setups
139
145
}
140
146
147
+ func (s * FixtureSetup [T ]) tryInit (val any ) {
148
+ if init , ok := val .(FixtureInit ); ok {
149
+ init .SetTB (s .TB )
150
+ }
151
+ }
152
+
141
153
func InitFixture [T FixtureInit ](
142
154
t testing.TB ,
143
155
fixture T ,
0 commit comments