Skip to content

Commit

Permalink
Add a version of table.Entry that allows dumping the entry parameters.
Browse files Browse the repository at this point in the history
Sometimes, it's useful to have the parameters of a given table entry as part of the description of the It counterpart.
To achieve that, we allow passing a function as the description parameter.
In case a string is passed, the previous behaviour is preserved. In case of function, it is expected to return a string
that will be used as the new description for the generated It.
  • Loading branch information
fedepaol committed Jun 8, 2020
1 parent a912ec5 commit bce59f1
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 23 deletions.
94 changes: 71 additions & 23 deletions extensions/table/table_entry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package table

import (
"fmt"
"reflect"

"github.com/onsi/ginkgo/internal/codelocation"
Expand All @@ -12,7 +13,7 @@ import (
TableEntry represents an entry in a table test. You generally use the `Entry` constructor.
*/
type TableEntry struct {
Description string
Description interface{}
Parameters []interface{}
Pending bool
Focused bool
Expand All @@ -26,33 +27,56 @@ func (t TableEntry) generateIt(itBody reflect.Value) {
t.codeLocation = codelocation.New(5)
}

if t.Pending {
global.Suite.PushItNode(t.Description, func() {}, types.FlagTypePending, t.codeLocation, 0)
return
var description string
descriptionValue := reflect.ValueOf(t.Description)
switch descriptionValue.Kind() {
case reflect.String:
description = descriptionValue.String()
case reflect.Func:
values := castParameters(descriptionValue, t.Parameters)
res := descriptionValue.Call(values)
if len(res) != 1 {
panic(fmt.Sprintf("The describe function should return only a value, returned %d", len(res)))
}
if res[0].Kind() != reflect.String {
panic(fmt.Sprintf("The describe function should return a string, returned %#v", res[0]))
}
description = res[0].String()
default:
panic(fmt.Sprintf("Description can either be a string or a function, got %#v", descriptionValue))
}

values := make([]reflect.Value, len(t.Parameters))
iBodyType := itBody.Type()
for i, param := range t.Parameters {
if param == nil {
inType := iBodyType.In(i)
values[i] = reflect.Zero(inType)
} else {
values[i] = reflect.ValueOf(param)
}
if t.Pending {
global.Suite.PushItNode(description, func() {}, types.FlagTypePending, t.codeLocation, 0)
return
}

values := castParameters(itBody, t.Parameters)
body := func() {
itBody.Call(values)
}

if t.Focused {
global.Suite.PushItNode(t.Description, body, types.FlagTypeFocused, t.codeLocation, global.DefaultTimeout)
global.Suite.PushItNode(description, body, types.FlagTypeFocused, t.codeLocation, global.DefaultTimeout)
} else {
global.Suite.PushItNode(t.Description, body, types.FlagTypeNone, t.codeLocation, global.DefaultTimeout)
global.Suite.PushItNode(description, body, types.FlagTypeNone, t.codeLocation, global.DefaultTimeout)
}
}

func castParameters(function reflect.Value, parameters []interface{}) []reflect.Value {
res := make([]reflect.Value, len(parameters))
funcType := function.Type()
for i, param := range parameters {
if param == nil {
inType := funcType.In(i)
res[i] = reflect.Zero(inType)
} else {
res[i] = reflect.ValueOf(param)
}
}
return res
}

/*
Entry constructs a TableEntry.
Expand All @@ -61,27 +85,51 @@ Subsequent parameters are saved off and sent to the callback passed in to `Descr
Each Entry ends up generating an individual Ginkgo It.
*/
func Entry(description string, parameters ...interface{}) TableEntry {
return TableEntry{description, parameters, false, false, codelocation.New(1)}
func Entry(description interface{}, parameters ...interface{}) TableEntry {
return TableEntry{
Description: description,
Parameters: parameters,
Pending: false,
Focused: false,
codeLocation: codelocation.New(1),
}
}

/*
You can focus a particular entry with FEntry. This is equivalent to FIt.
*/
func FEntry(description string, parameters ...interface{}) TableEntry {
return TableEntry{description, parameters, false, true, codelocation.New(1)}
func FEntry(description interface{}, parameters ...interface{}) TableEntry {
return TableEntry{
Description: description,
Parameters: parameters,
Pending: false,
Focused: true,
codeLocation: codelocation.New(1),
}
}

/*
You can mark a particular entry as pending with PEntry. This is equivalent to PIt.
*/
func PEntry(description string, parameters ...interface{}) TableEntry {
return TableEntry{description, parameters, true, false, codelocation.New(1)}
func PEntry(description interface{}, parameters ...interface{}) TableEntry {
return TableEntry{
Description: description,
Parameters: parameters,
Pending: true,
Focused: false,
codeLocation: codelocation.New(1),
}
}

/*
You can mark a particular entry as pending with XEntry. This is equivalent to XIt.
*/
func XEntry(description string, parameters ...interface{}) TableEntry {
return TableEntry{description, parameters, true, false, codelocation.New(1)}
func XEntry(description interface{}, parameters ...interface{}) TableEntry {
return TableEntry{
Description: description,
Parameters: parameters,
Pending: true,
Focused: false,
codeLocation: codelocation.New(1),
}
}
51 changes: 51 additions & 0 deletions extensions/table/table_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package table_test

import (
"fmt"
"strings"

. "github.com/onsi/ginkgo/extensions/table"
Expand Down Expand Up @@ -62,3 +63,53 @@ var _ = Describe("Table", func() {
Entry("nil", nil),
)
})

var _ = Describe("TableWithParametricDescription", func() {
describe := func(desc string) func(int, int, bool) string {
return func(x, y int, expected bool) string {
return fmt.Sprintf("%s x=%d y=%d expected:%t", desc, x, y, expected)
}
}

DescribeTable("a simple table",
func(x int, y int, expected bool) {
Ω(x > y).Should(Equal(expected))
},
Entry(describe("x > y"), 1, 0, true),
Entry(describe("x == y"), 0, 0, false),
Entry(describe("x < y"), 0, 1, false),
)

type ComplicatedThings struct {
Superstructure string
Substructure string
Count int
}

describeComplicated := func(desc string) func(ComplicatedThings) string {
return func(things ComplicatedThings) string {
return fmt.Sprintf("%s things=%v", desc, things)
}
}

DescribeTable("a more complicated table",
func(c ComplicatedThings) {
Ω(strings.Count(c.Superstructure, c.Substructure)).Should(BeNumerically("==", c.Count))
},
Entry(describeComplicated("with no matching substructures"), ComplicatedThings{
Superstructure: "the sixth sheikh's sixth sheep's sick",
Substructure: "emir",
Count: 0,
}),
Entry(describeComplicated("with one matching substructure"), ComplicatedThings{
Superstructure: "the sixth sheikh's sixth sheep's sick",
Substructure: "sheep",
Count: 1,
}),
Entry(describeComplicated("with many matching substructures"), ComplicatedThings{
Superstructure: "the sixth sheikh's sixth sheep's sick",
Substructure: "si",
Count: 3,
}),
)
})

0 comments on commit bce59f1

Please sign in to comment.