This repository has been archived by the owner on Mar 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 86
/
testfixture.go
255 lines (239 loc) · 10.9 KB
/
testfixture.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package testfixture
import (
"context"
"testing"
"github.com/fabric8-services/fabric8-wit/query"
"github.com/fabric8-services/fabric8-wit/account"
"github.com/fabric8-services/fabric8-wit/area"
"github.com/fabric8-services/fabric8-wit/codebase"
"github.com/fabric8-services/fabric8-wit/comment"
"github.com/fabric8-services/fabric8-wit/iteration"
"github.com/fabric8-services/fabric8-wit/label"
"github.com/fabric8-services/fabric8-wit/remoteworkitem"
"github.com/fabric8-services/fabric8-wit/resource"
"github.com/fabric8-services/fabric8-wit/space"
"github.com/fabric8-services/fabric8-wit/spacetemplate"
"github.com/fabric8-services/fabric8-wit/workitem"
"github.com/fabric8-services/fabric8-wit/workitem/link"
"github.com/jinzhu/gorm"
errs "github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
// A TestFixture object is the result of a call to
// NewFixture()
// or
// NewFixtureIsolated()
//
// Don't create one on your own!
type TestFixture struct {
info map[kind]*createInfo
db *gorm.DB
isolatedCreation bool
ctx context.Context
checkFuncs []func() error
customLinkCreation bool // on when you've used WorkItemLinksCustom in your recipe
normalLinkCreation bool // on when you've used WorkItemLinks in your recipe
Users []*account.User // Users (if any) that were created for this test fixture.
Identities []*account.Identity // Identities (if any) that were created for this test fixture.
Iterations []*iteration.Iteration // Iterations (if any) that were created for this test fixture.
Areas []*area.Area // Areas (if any) that were created for this test fixture.
Spaces []*space.Space // Spaces (if any) that were created for this test fixture.
Codebases []*codebase.Codebase // Codebases (if any) that were created for this test fixture.
WorkItems []*workitem.WorkItem // Work items (if any) that were created for this test fixture.
Comments []*comment.Comment // Comments (if any) that were created for this test fixture.
WorkItemTypes []*workitem.WorkItemType // Work item types (if any) that were created for this test fixture.
WorkItemLinkTypes []*link.WorkItemLinkType // Work item link types (if any) that were created for this test fixture.
WorkItemLinks []*link.WorkItemLink // Work item links (if any) that were created for this test fixture.
Labels []*label.Label // Labels (if any) that were created for this test fixture.
Trackers []*remoteworkitem.Tracker // Remote work item tracker (if any) that were created for this test fixture.
TrackerQueries []*remoteworkitem.TrackerQuery // Remote work item tracker query (if any) that were created for this test fixture.
Queries []*query.Query // Queries (if any) that were created for this test fixture.
SpaceTemplates []*spacetemplate.SpaceTemplate // Space templates (if any) that were created for this test fixture.
WorkItemTypeGroups []*workitem.WorkItemTypeGroup // Work item types groups (if any) that were created for this test fixture.
WorkItemBoards []*workitem.Board // Work item boards (if any) that were created for this test fixture.
}
// NewFixture will create a test fixture by executing the recipies from the
// given recipe functions. If recipeFuncs is empty, nothing will happen.
//
// For example
// NewFixture(db, Comments(100))
// will create a work item (and everything required in order to create it) and
// author 100 comments for it. They will all be created by the same user if you
// don't tell the system to do it differently. For example, to create 100
// comments from 100 different users we can do the following:
// NewFixture(db, Identities(100), Comments(100, func(fxt *TestFixture, idx int) error{
// fxt.Comments[idx].Creator = fxt.Identities[idx].ID
// return nil
// }))
// That will create 100 identities and 100 comments and for each comment we're
// using the ID of one of the identities that have been created earlier. There's
// one important observation to make with this example: there's an order to how
// entities get created in the test fixture. That order is basically defined by
// the number of dependencies that each entity has. For example an identity has
// no dependency, so it will be created first and then can be accessed safely by
// any of the other entity creation functions. A comment for example depends on
// a work item which itself depends on a work item type and a space. The NewFixture
// function does take care of recursively resolving those dependcies first.
//
// If you just want to create 100 identities and 100 work items but don't care
// about resolving the dependencies automatically you can create the entities in
// isolation:
// NewFixtureIsolated(db, Identities(100), Comments(100, func(fxt *TestFixture, idx int) error{
// fxt.Comments[idx].Creator = fxt.Identities[idx].ID
// fxt.Comments[idx].ParentID = someExistingWorkItemID
// return nil
// }))
// Notice that I manually have to specify the ParentID of the work comment then
// because we cannot automatically resolve to which work item we will attach the
// comment.
func NewFixture(db *gorm.DB, recipeFuncs ...RecipeFunction) (*TestFixture, error) {
return newFixture(db, false, recipeFuncs...)
}
// NewTestFixture does the same as NewFixture except that it automatically
// fails the given test if the fixture could not be created correctly.
func NewTestFixture(t testing.TB, db *gorm.DB, recipeFuncs ...RecipeFunction) *TestFixture {
resource.Require(t, resource.Database)
tc, err := NewFixture(db, recipeFuncs...)
require.NoError(t, err)
require.NotNil(t, tc)
return tc
}
// NewFixtureIsolated will create a test fixture by executing the recipies from
// the given recipe functions. If recipeFuncs is empty, nothing will happen.
//
// The difference to the normal NewFixture function is that we will only create
// those object that where specified in the recipeFuncs. We will not create any
// object that is normally demanded by an object. For example, if you call
// NewFixture(t, db, WorkItems(1))
// you would (apart from other objects) get at least one work item AND a work
// item type because that is needed to create a work item. With
// NewFixtureIsolated(t, db, Comments(2), WorkItems(1))
// on the other hand, we will only create a work item, two comments for it, and
// nothing more. And for sure your test will fail if you do that because you
// need to specify a space ID and a work item type ID for the created work item:
// NewFixtureIsolated(db, Comments(2), WorkItems(1, func(fxt *TestFixture, idx int) error{
// fxt.WorkItems[idx].SpaceID = someExistingSpaceID
// fxt.WorkItems[idx].WorkItemType = someExistingWorkItemTypeID
// return nil
// }))
func NewFixtureIsolated(db *gorm.DB, setupFuncs ...RecipeFunction) (*TestFixture, error) {
return newFixture(db, true, setupFuncs...)
}
// Check runs all check functions that each recipe-function has registered to
// check that the amount of objects has been created that were demanded in the
// recipe function.
//
// In this example
// fxt, _:= NewFixture(db, WorkItems(2))
// err = fxt.Check()
// err will only be nil if at least two work items have been created and all of
// the dependencies that a work item requires. Look into the documentation of
// each recipe-function to find out what dependencies each entity has.
//
// Notice, that check is called at the end of NewFixture() and its derivatives,
// so if you don't mess with the fixture after it was created, there's no need
// to call Check() again.
func (fxt *TestFixture) Check() error {
for _, fn := range fxt.checkFuncs {
if err := fn(); err != nil {
return errs.Wrap(err, "check function failed")
}
}
return nil
}
type kind string
const (
kindUsers kind = "user"
kindIdentities kind = "identity"
kindIterations kind = "iteration"
kindAreas kind = "area"
kindSpaces kind = "space"
kindCodebases kind = "codebase"
kindWorkItems kind = "work_item"
kindComments kind = "comment"
kindWorkItemTypes kind = "work_item_type"
kindWorkItemLinkTypes kind = "work_item_link_type"
kindWorkItemLinks kind = "work_item_link"
kindLabels kind = "label"
kindTrackers kind = "tracker"
kindTrackerQueries kind = "tracker_queries"
kindQueries kind = "query"
kindSpaceTemplates kind = "space_template"
kindWorkItemTypeGroups kind = "work_item_type_group"
kindWorkItemBoards kind = "work_item_board"
)
type createInfo struct {
numInstances int
customizeEntityFuncs []CustomizeEntityFunc
}
func (fxt *TestFixture) runCustomizeEntityFuncs(idx int, k kind) error {
if fxt.info[k] == nil {
return errs.Errorf("the creation info for kind %s is nil (this should not happen)", k)
}
for _, dfn := range fxt.info[k].customizeEntityFuncs {
if err := dfn(fxt, idx); err != nil {
return errs.Wrapf(err, "failed to run customize-entity-callbacks for kind %s", k)
}
}
return nil
}
func (fxt *TestFixture) setupInfo(n int, k kind, fns ...CustomizeEntityFunc) error {
if n <= 0 {
return errs.Errorf("the number of objects to create must always be greater than zero: %d", n)
}
if _, ok := fxt.info[k]; !ok {
fxt.info[k] = &createInfo{}
}
maxN := n
if maxN < fxt.info[k].numInstances {
maxN = fxt.info[k].numInstances
}
fxt.info[k].numInstances = maxN
fxt.info[k].customizeEntityFuncs = append(fxt.info[k].customizeEntityFuncs, fns...)
return nil
}
func newFixture(db *gorm.DB, isolatedCreation bool, recipeFuncs ...RecipeFunction) (*TestFixture, error) {
fxt := TestFixture{
checkFuncs: []func() error{},
info: map[kind]*createInfo{},
db: db,
isolatedCreation: isolatedCreation,
ctx: context.Background(),
}
for _, fn := range recipeFuncs {
if err := fn(&fxt); err != nil {
return nil, errs.Wrap(err, "failed to execute recipe function")
}
}
makeFuncs := []func(fxt *TestFixture) error{
// make the objects that DON'T have any dependency
makeUsers,
makeTrackers,
makeSpaceTemplates,
// actually make the objects that DO have dependencies
makeIdentities,
makeSpaces,
makeLabels,
makeQueries,
makeWorkItemLinkTypes,
makeCodebases,
makeWorkItemTypes,
makeWorkItemTypeGroups,
makeWorkItemBoards,
makeIterations,
makeAreas,
makeTrackerQueries,
makeWorkItems,
makeComments,
makeWorkItemLinks,
}
for _, fn := range makeFuncs {
if err := fn(&fxt); err != nil {
return nil, errs.Wrap(err, "failed to make objects")
}
}
if err := fxt.Check(); err != nil {
return nil, errs.Wrap(err, "test fixture did not pass checks")
}
return &fxt, nil
}