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
#689)

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 9, 2020
1 parent a912ec5 commit 21eaef2
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 23 deletions.
19 changes: 19 additions & 0 deletions extensions/table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ Under the hood, `DescribeTable` simply generates a new Ginkgo `Describe`. Each
It's important to understand that the `Describe`s and `It`s are generated at evaluation time (i.e. when Ginkgo constructs the tree of tests and before the tests run).
Individual Entries can be focused (with FEntry) or marked pending (with PEntry or XEntry). In addition, the entire table can be focused or marked pending with FDescribeTable and PDescribeTable/XDescribeTable.
A description function can be passed to Entry in place of the description. The function is then fed with the entry parameters to generate the description of the It corresponding to that particular Entry.
For example:
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),
)
*/
func DescribeTable(description string, itBody interface{}, entries ...TableEntry) bool {
describeTable(description, itBody, entries, types.FlagTypeNone)
Expand Down
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 21eaef2

Please sign in to comment.