1 package ginkgo
2
3 import (
4 "context"
5 "fmt"
6 "reflect"
7 "strings"
8
9 "github.com/onsi/ginkgo/v2/internal"
10 "github.com/onsi/ginkgo/v2/types"
11 )
12
13
24 type EntryDescription string
25
26 func (ed EntryDescription) render(args ...interface{}) string {
27 return fmt.Sprintf(string(ed), args...)
28 }
29
30
47 func DescribeTable(description string, args ...interface{}) bool {
48 GinkgoHelper()
49 generateTable(description, false, args...)
50 return true
51 }
52
53
56 func FDescribeTable(description string, args ...interface{}) bool {
57 GinkgoHelper()
58 args = append(args, internal.Focus)
59 generateTable(description, false, args...)
60 return true
61 }
62
63
66 func PDescribeTable(description string, args ...interface{}) bool {
67 GinkgoHelper()
68 args = append(args, internal.Pending)
69 generateTable(description, false, args...)
70 return true
71 }
72
73
76 var XDescribeTable = PDescribeTable
77
78
112 func DescribeTableSubtree(description string, args ...interface{}) bool {
113 GinkgoHelper()
114 generateTable(description, true, args...)
115 return true
116 }
117
118
121 func FDescribeTableSubtree(description string, args ...interface{}) bool {
122 GinkgoHelper()
123 args = append(args, internal.Focus)
124 generateTable(description, true, args...)
125 return true
126 }
127
128
131 func PDescribeTableSubtree(description string, args ...interface{}) bool {
132 GinkgoHelper()
133 args = append(args, internal.Pending)
134 generateTable(description, true, args...)
135 return true
136 }
137
138
141 var XDescribeTableSubtree = PDescribeTableSubtree
142
143
146 type TableEntry struct {
147 description interface{}
148 decorations []interface{}
149 parameters []interface{}
150 codeLocation types.CodeLocation
151 }
152
153
165 func Entry(description interface{}, args ...interface{}) TableEntry {
166 GinkgoHelper()
167 decorations, parameters := internal.PartitionDecorations(args...)
168 return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)}
169 }
170
171
174 func FEntry(description interface{}, args ...interface{}) TableEntry {
175 GinkgoHelper()
176 decorations, parameters := internal.PartitionDecorations(args...)
177 decorations = append(decorations, internal.Focus)
178 return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)}
179 }
180
181
184 func PEntry(description interface{}, args ...interface{}) TableEntry {
185 GinkgoHelper()
186 decorations, parameters := internal.PartitionDecorations(args...)
187 decorations = append(decorations, internal.Pending)
188 return TableEntry{description: description, decorations: decorations, parameters: parameters, codeLocation: types.NewCodeLocation(0)}
189 }
190
191
194 var XEntry = PEntry
195
196 var contextType = reflect.TypeOf(new(context.Context)).Elem()
197 var specContextType = reflect.TypeOf(new(SpecContext)).Elem()
198
199 func generateTable(description string, isSubtree bool, args ...interface{}) {
200 GinkgoHelper()
201 cl := types.NewCodeLocation(0)
202 containerNodeArgs := []interface{}{cl}
203
204 entries := []TableEntry{}
205 var internalBody interface{}
206 var internalBodyType reflect.Type
207
208 var tableLevelEntryDescription interface{}
209 tableLevelEntryDescription = func(args ...interface{}) string {
210 out := []string{}
211 for _, arg := range args {
212 out = append(out, fmt.Sprint(arg))
213 }
214 return "Entry: " + strings.Join(out, ", ")
215 }
216
217 if len(args) == 1 {
218 exitIfErr(types.GinkgoErrors.MissingParametersForTableFunction(cl))
219 }
220
221 for i, arg := range args {
222 switch t := reflect.TypeOf(arg); {
223 case t == nil:
224 exitIfErr(types.GinkgoErrors.IncorrectParameterTypeForTable(i, "nil", cl))
225 case t == reflect.TypeOf(TableEntry{}):
226 entries = append(entries, arg.(TableEntry))
227 case t == reflect.TypeOf([]TableEntry{}):
228 entries = append(entries, arg.([]TableEntry)...)
229 case t == reflect.TypeOf(EntryDescription("")):
230 tableLevelEntryDescription = arg.(EntryDescription).render
231 case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""):
232 tableLevelEntryDescription = arg
233 case t.Kind() == reflect.Func:
234 if internalBody != nil {
235 exitIfErr(types.GinkgoErrors.MultipleEntryBodyFunctionsForTable(cl))
236 }
237 internalBody = arg
238 internalBodyType = reflect.TypeOf(internalBody)
239 default:
240 containerNodeArgs = append(containerNodeArgs, arg)
241 }
242 }
243
244 containerNodeArgs = append(containerNodeArgs, func() {
245 for _, entry := range entries {
246 var err error
247 entry := entry
248 var description string
249 switch t := reflect.TypeOf(entry.description); {
250 case t == nil:
251 err = validateParameters(tableLevelEntryDescription, entry.parameters, "Entry Description function", entry.codeLocation, false)
252 if err == nil {
253 description = invokeFunction(tableLevelEntryDescription, entry.parameters)[0].String()
254 }
255 case t == reflect.TypeOf(EntryDescription("")):
256 description = entry.description.(EntryDescription).render(entry.parameters...)
257 case t == reflect.TypeOf(""):
258 description = entry.description.(string)
259 case t.Kind() == reflect.Func && t.NumOut() == 1 && t.Out(0) == reflect.TypeOf(""):
260 err = validateParameters(entry.description, entry.parameters, "Entry Description function", entry.codeLocation, false)
261 if err == nil {
262 description = invokeFunction(entry.description, entry.parameters)[0].String()
263 }
264 default:
265 err = types.GinkgoErrors.InvalidEntryDescription(entry.codeLocation)
266 }
267
268 internalNodeArgs := []interface{}{entry.codeLocation}
269 internalNodeArgs = append(internalNodeArgs, entry.decorations...)
270
271 hasContext := false
272 if internalBodyType.NumIn() > 0. {
273 if internalBodyType.In(0).Implements(specContextType) {
274 hasContext = true
275 } else if internalBodyType.In(0).Implements(contextType) && (len(entry.parameters) == 0 || !reflect.TypeOf(entry.parameters[0]).Implements(contextType)) {
276 hasContext = true
277 }
278 }
279
280 if err == nil {
281 err = validateParameters(internalBody, entry.parameters, "Table Body function", entry.codeLocation, hasContext)
282 }
283
284 if hasContext {
285 internalNodeArgs = append(internalNodeArgs, func(c SpecContext) {
286 if err != nil {
287 panic(err)
288 }
289 invokeFunction(internalBody, append([]interface{}{c}, entry.parameters...))
290 })
291 if isSubtree {
292 exitIfErr(types.GinkgoErrors.ContextsCannotBeUsedInSubtreeTables(cl))
293 }
294 } else {
295 internalNodeArgs = append(internalNodeArgs, func() {
296 if err != nil {
297 panic(err)
298 }
299 invokeFunction(internalBody, entry.parameters)
300 })
301 }
302
303 internalNodeType := types.NodeTypeIt
304 if isSubtree {
305 internalNodeType = types.NodeTypeContainer
306 }
307
308 pushNode(internal.NewNode(deprecationTracker, internalNodeType, description, internalNodeArgs...))
309 }
310 })
311
312 pushNode(internal.NewNode(deprecationTracker, types.NodeTypeContainer, description, containerNodeArgs...))
313 }
314
315 func invokeFunction(function interface{}, parameters []interface{}) []reflect.Value {
316 inValues := make([]reflect.Value, len(parameters))
317
318 funcType := reflect.TypeOf(function)
319 limit := funcType.NumIn()
320 if funcType.IsVariadic() {
321 limit = limit - 1
322 }
323
324 for i := 0; i < limit && i < len(parameters); i++ {
325 inValues[i] = computeValue(parameters[i], funcType.In(i))
326 }
327
328 if funcType.IsVariadic() {
329 variadicType := funcType.In(limit).Elem()
330 for i := limit; i < len(parameters); i++ {
331 inValues[i] = computeValue(parameters[i], variadicType)
332 }
333 }
334
335 return reflect.ValueOf(function).Call(inValues)
336 }
337
338 func validateParameters(function interface{}, parameters []interface{}, kind string, cl types.CodeLocation, hasContext bool) error {
339 funcType := reflect.TypeOf(function)
340 limit := funcType.NumIn()
341 offset := 0
342 if hasContext {
343 limit = limit - 1
344 offset = 1
345 }
346 if funcType.IsVariadic() {
347 limit = limit - 1
348 }
349 if len(parameters) < limit {
350 return types.GinkgoErrors.TooFewParametersToTableFunction(limit, len(parameters), kind, cl)
351 }
352 if len(parameters) > limit && !funcType.IsVariadic() {
353 return types.GinkgoErrors.TooManyParametersToTableFunction(limit, len(parameters), kind, cl)
354 }
355 var i = 0
356 for ; i < limit; i++ {
357 actual := reflect.TypeOf(parameters[i])
358 expected := funcType.In(i + offset)
359 if !(actual == nil) && !actual.AssignableTo(expected) {
360 return types.GinkgoErrors.IncorrectParameterTypeToTableFunction(i+1, expected, actual, kind, cl)
361 }
362 }
363 if funcType.IsVariadic() {
364 expected := funcType.In(limit + offset).Elem()
365 for ; i < len(parameters); i++ {
366 actual := reflect.TypeOf(parameters[i])
367 if !(actual == nil) && !actual.AssignableTo(expected) {
368 return types.GinkgoErrors.IncorrectVariadicParameterTypeToTableFunction(expected, actual, kind, cl)
369 }
370 }
371 }
372
373 return nil
374 }
375
376 func computeValue(parameter interface{}, t reflect.Type) reflect.Value {
377 if parameter == nil {
378 return reflect.Zero(t)
379 } else {
380 return reflect.ValueOf(parameter)
381 }
382 }
383
View as plain text