• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

go-bdd / gobdd / 13074970359

31 Jan 2025 02:42PM UTC coverage: 82.173%. Remained the same
13074970359

Pull #155

github

web-flow
Merge d3eeb6dba into 3e8861a4a
Pull Request #155: feat: give access to testing.T directly

1 of 4 new or added lines in 1 file covered. (25.0%)

57 existing lines in 1 file now uncovered.

590 of 718 relevant lines covered (82.17%)

140.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.7
/gobdd.go
1
package gobdd
2

3
import (
4
        "bufio"
5
        "errors"
6
        "fmt"
7
        "io"
8
        "os"
9
        "path/filepath"
10
        "reflect"
11
        "regexp"
12
        "strconv"
13
        "strings"
14
        "testing"
15

16
        gherkin "github.com/cucumber/gherkin/go/v28"
17
        msgs "github.com/cucumber/messages/go/v24"
18
)
19

20
// Suite holds all the information about the suite (options, steps to execute etc)
21
type Suite struct {
22
        t              TestingT
23
        steps          []stepDef
24
        options        SuiteOptions
25
        hasStepErrors  bool
26
        parameterTypes map[string][]string
27
}
28

29
// SuiteOptions holds all the information about how the suite or features/steps should be configured
30
type SuiteOptions struct {
31
        featureSource  featureSource
32
        ignoreTags     []string
33
        tags           []string
34
        beforeScenario []func(ctx Context)
35
        afterScenario  []func(ctx Context)
36
        beforeStep     []func(ctx Context)
37
        afterStep      []func(ctx Context)
38
        runInParallel  bool
39
}
40

41
type featureSource interface {
42
        loadFeatures() ([]feature, error)
43
}
44

45
type feature interface {
46
        Open() (io.Reader, error)
47
}
48

49
type pathFeatureSource string
50

51
func (s pathFeatureSource) loadFeatures() ([]feature, error) {
85✔
52
        files, err := filepath.Glob(string(s))
85✔
53
        if err != nil {
85✔
UNCOV
54
                return nil, errors.New("cannot find features/ directory")
×
UNCOV
55
        }
×
56

57
        features := make([]feature, 0, len(files))
85✔
58

85✔
59
        for _, f := range files {
185✔
60
                features = append(features, fileFeature(f))
100✔
61
        }
100✔
62

63
        return features, nil
85✔
64
}
65

66
type fileFeature string
67

68
func (f fileFeature) Open() (io.Reader, error) {
100✔
69
        file, err := os.Open(string(f))
100✔
70
        if err != nil {
100✔
UNCOV
71
                return nil, fmt.Errorf("cannot open file %s", f)
×
UNCOV
72
        }
×
73

74
        return file, nil
100✔
75
}
76

77
// NewSuiteOptions creates a new suite configuration with default values
78
func NewSuiteOptions() SuiteOptions {
120✔
79
        return SuiteOptions{
120✔
80
                featureSource:  pathFeatureSource("features/*.feature"),
120✔
81
                ignoreTags:     []string{},
120✔
82
                tags:           []string{},
120✔
83
                beforeScenario: []func(ctx Context){},
120✔
84
                afterScenario:  []func(ctx Context){},
120✔
85
                beforeStep:     []func(ctx Context){},
120✔
86
                afterStep:      []func(ctx Context){},
120✔
87
        }
120✔
88
}
120✔
89

90
// RunInParallel runs tests in parallel
UNCOV
91
func RunInParallel() func(*SuiteOptions) {
×
UNCOV
92
        return func(options *SuiteOptions) {
×
93
                options.runInParallel = true
×
94
        }
×
95
}
96

97
// WithFeaturesPath configures a pattern (regexp) where feature can be found.
98
// The default value is "features/*.feature"
99
func WithFeaturesPath(path string) func(*SuiteOptions) {
85✔
100
        return func(options *SuiteOptions) {
170✔
101
                options.featureSource = pathFeatureSource(path)
85✔
102
        }
85✔
103
}
104

105
// WithTags configures which tags should be skipped while executing a suite
106
// Every tag has to start with @
107
func WithTags(tags ...string) func(*SuiteOptions) {
10✔
108
        return func(options *SuiteOptions) {
20✔
109
                options.tags = tags
10✔
110
        }
10✔
111
}
112

113
// WithBeforeScenario configures functions that should be executed before every scenario
114
func WithBeforeScenario(f func(ctx Context)) func(*SuiteOptions) {
5✔
115
        return func(options *SuiteOptions) {
10✔
116
                options.beforeScenario = append(options.beforeScenario, f)
5✔
117
        }
5✔
118
}
119

120
// WithAfterScenario configures functions that should be executed after every scenario
121
func WithAfterScenario(f func(ctx Context)) func(*SuiteOptions) {
5✔
122
        return func(options *SuiteOptions) {
10✔
123
                options.afterScenario = append(options.afterScenario, f)
5✔
124
        }
5✔
125
}
126

127
// WithBeforeStep configures functions that should be executed before every step
128
func WithBeforeStep(f func(ctx Context)) func(*SuiteOptions) {
5✔
129
        return func(options *SuiteOptions) {
10✔
130
                options.beforeStep = append(options.beforeStep, f)
5✔
131
        }
5✔
132
}
133

134
// WithAfterStep configures functions that should be executed after every step
135
func WithAfterStep(f func(ctx Context)) func(*SuiteOptions) {
5✔
136
        return func(options *SuiteOptions) {
10✔
137
                options.afterStep = append(options.afterStep, f)
5✔
138
        }
5✔
139
}
140

141
// WithIgnoredTags configures which tags should be skipped while executing a suite
142
// Every tag has to start with @ otherwise will be ignored
143
func WithIgnoredTags(tags ...string) func(*SuiteOptions) {
5✔
144
        return func(options *SuiteOptions) {
10✔
145
                options.ignoreTags = tags
5✔
146
        }
5✔
147
}
148

149
type stepDef struct {
150
        expr *regexp.Regexp
151
        f    interface{}
152
}
153

154
type StepTest interface {
155
        testing.TB
156
}
157

158
type TestingT interface {
159
        StepTest
160
        Parallel()
161
        Run(name string, f func(t *testing.T)) bool
162
}
163

164
// TestingTKey is used to store reference to current *testing.T instance
165
type TestingTKey struct{}
166

167
// FeatureKey is used to store reference to current *msgs.Feature instance
168
type FeatureKey struct{}
169

170
// RuleKey is used to store reference to current *msgs.Rule instance
171
type RuleKey struct{}
172

173
// ScenarioKey is used to store reference to current *msgs.Scenario instance
174
type ScenarioKey struct{}
175

176
// Creates a new suites with given configuration and empty steps defined
177
func NewSuite(t TestingT, optionClosures ...func(*SuiteOptions)) *Suite {
120✔
178
        options := NewSuiteOptions()
120✔
179

120✔
180
        for i := 0; i < len(optionClosures); i++ {
245✔
181
                optionClosures[i](&options)
125✔
182
        }
125✔
183

184
        s := &Suite{
120✔
185
                t:              t,
120✔
186
                steps:          []stepDef{},
120✔
187
                options:        options,
120✔
188
                parameterTypes: map[string][]string{},
120✔
189
        }
120✔
190

120✔
191
        // see https://github.com/cucumber/cucumber-expressions/blob/main/go/parameter_type_registry.go
120✔
192
        s.AddParameterTypes(`{int}`, []string{`(-?\d+)`})
120✔
193
        s.AddParameterTypes(`{float}`, []string{`([-+]?\d*\.?\d+)`})
120✔
194
        s.AddParameterTypes(`{word}`, []string{`([^\s]+)`})
120✔
195
        s.AddParameterTypes(`{text}`, []string{`"([^"\\]*(?:\\.[^"\\]*)*)"`, `'([^'\\]*(?:\\.[^'\\]*)*)'`})
120✔
196

120✔
197
        return s
120✔
198
}
199

200
// AddParameterTypes adds a list of parameter types that will be used to simplify step definitions.
201
//
202
// The first argument is the parameter type and the second parameter is a list of regular expressions
203
// that should replace the parameter type.
204
//
205
//        s.AddParameterTypes(`{int}`, []string{`(\d)`})
206
//
207
// The regular expression should compile, otherwise will produce an error and stop executing.
208
func (s *Suite) AddParameterTypes(from string, to []string) {
480✔
209
        for _, to := range to {
1,080✔
210
                _, err := regexp.Compile(to)
600✔
211
                if err != nil {
600✔
UNCOV
212
                        s.t.Fatalf(`the regular expression for key %s doesn't compile: %s`, from, to)
×
UNCOV
213
                }
×
214

215
                s.parameterTypes[from] = append(s.parameterTypes[from], to)
600✔
216
        }
217
}
218

219
// AddStep registers a step in the suite.
220
//
221
// The second parameter is the step function that gets executed
222
// when a step definition matches the provided regular expression.
223
//
224
// A step function can have any number of parameters (even zero),
225
// but it MUST accept a gobdd.StepTest and gobdd.Context as the first parameters (if there is any):
226
//
227
//        func myStepFunction(t gobdd.StepTest, ctx gobdd.Context, first int, second int) {
228
//        }
229
func (s *Suite) AddStep(expr string, step interface{}) {
260✔
230
        err := validateStepFunc(step)
260✔
231
        if err != nil {
285✔
232
                s.t.Errorf("the step function for step `%s` is incorrect: %s", expr, err.Error())
25✔
233
                s.hasStepErrors = true
25✔
234

25✔
235
                return
25✔
236
        }
25✔
237

238
        exprs := s.applyParameterTypes(expr)
235✔
239

235✔
240
        for _, expr := range exprs {
740✔
241
                compiled, err := regexp.Compile(expr)
505✔
242
                if err != nil {
505✔
NEW
UNCOV
243
                        s.t.Errorf("the step function is incorrect: %s", err.Error())
×
UNCOV
244
                        s.hasStepErrors = true
×
245

×
246
                        return
×
247
                }
×
248

249
                s.steps = append(s.steps, stepDef{
505✔
250
                        expr: compiled,
505✔
251
                        f:    step,
505✔
252
                })
505✔
253
        }
254
}
255

256
func (s *Suite) applyParameterTypes(expr string) []string {
505✔
257
        exprs := []string{expr}
505✔
258

505✔
259
        for from, to := range s.parameterTypes {
2,525✔
260
                for _, t := range to {
4,545✔
261
                        if strings.Contains(expr, from) {
2,795✔
262
                                exprs = append(exprs, s.applyParameterTypes(strings.ReplaceAll(expr, from, t))...)
270✔
263
                        }
270✔
264
                }
265
        }
266

267
        return exprs
505✔
268
}
269

270
// AddRegexStep registers a step in the suite.
271
//
272
// The second parameter is the step function that gets executed
273
// when a step definition matches the provided regular expression.
274
//
275
// A step function can have any number of parameters (even zero),
276
// but it MUST accept a gobdd.StepTest and gobdd.Context as the first parameters (if there is any):
277
//
278
//        func myStepFunction(t gobdd.StepTest, ctx gobdd.Context, first int, second int) {
279
//        }
280
func (s *Suite) AddRegexStep(expr *regexp.Regexp, step interface{}) {
30✔
281
        err := validateStepFunc(step)
30✔
282
        if err != nil {
30✔
NEW
UNCOV
283
                s.t.Errorf("the step function is incorrect: %s", err.Error())
×
UNCOV
284
                s.hasStepErrors = true
×
285

×
286
                return
×
287
        }
×
288

289
        s.steps = append(s.steps, stepDef{
30✔
290
                expr: expr,
30✔
291
                f:    step,
30✔
292
        })
30✔
293
}
294

295
// Executes the suite with given options and defined steps
296
func (s *Suite) Run() {
115✔
297
        if s.hasStepErrors {
140✔
298
                s.t.Fatal("the test contains invalid step definitions")
25✔
299

25✔
300
                return
25✔
301
        }
25✔
302

303
        features, err := s.options.featureSource.loadFeatures()
90✔
304
        if err != nil {
90✔
NEW
UNCOV
305
                s.t.Fatal(err.Error())
×
UNCOV
306
        }
×
307

308
        if s.options.runInParallel {
90✔
UNCOV
309
                s.t.Parallel()
×
UNCOV
310
        }
×
311

312
        for _, feature := range features {
190✔
313
                err = s.executeFeature(feature)
100✔
314
                if err != nil {
100✔
UNCOV
315
                        s.t.Fail()
×
UNCOV
316
                }
×
317
        }
318
}
319

320
func (s *Suite) executeFeature(feature feature) error {
100✔
321
        f, err := feature.Open()
100✔
322
        if err != nil {
100✔
UNCOV
323
                return err
×
UNCOV
324
        }
×
325

326
        if closer, ok := f.(io.Closer); ok {
200✔
327
                defer closer.Close()
100✔
328
        }
100✔
329

330
        featureIO := bufio.NewReader(f)
100✔
331

100✔
332
        doc, err := gherkin.ParseGherkinDocument(featureIO, (&msgs.Incrementing{}).NewId)
100✔
333
        if err != nil {
100✔
UNCOV
334
                s.t.Fatalf("error while loading document: %s\n", err)
×
UNCOV
335
        }
×
336

337
        if doc.Feature == nil {
100✔
UNCOV
338
                return nil
×
UNCOV
339
        }
×
340

341
        return s.runFeature(doc.Feature)
100✔
342
}
343

344
func (s *Suite) runFeature(feature *msgs.Feature) error {
100✔
345
        if s.shouldSkipFeatureOrRule(feature.Tags) {
105✔
346
                s.t.Logf("the feature (%s) is ignored ", feature.Name)
5✔
347
                return nil
5✔
348
        }
5✔
349

350
        hasErrors := false
95✔
351

95✔
352
        s.t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(feature.Keyword), feature.Name), func(t *testing.T) {
190✔
353
                backgrounds := []*msgs.Background{}
95✔
354

95✔
355
                for _, child := range feature.Children {
290✔
356
                        if child.Background != nil {
210✔
357
                                backgrounds = append(backgrounds, child.Background)
15✔
358
                        }
15✔
359

360
                        if rule := child.Rule; rule != nil {
235✔
361
                                s.runRule(feature, rule, backgrounds, t)
40✔
362
                        }
40✔
363
                        if scenario := child.Scenario; scenario != nil {
335✔
364
                                ctx := NewContext()
140✔
365
                                ctx.Set(FeatureKey{}, feature)
140✔
366
                                s.runScenario(ctx, scenario, backgrounds, t, feature.Tags)
140✔
367
                        }
140✔
368
                }
369
        })
370

371
        if hasErrors {
95✔
UNCOV
372
                return errors.New("the feature contains errors")
×
UNCOV
373
        }
×
374

375
        return nil
95✔
376
}
377

378
func (s *Suite) getOutlineStep(
379
        steps []*msgs.Step,
380
        examples []*msgs.Examples) []*msgs.Step {
15✔
381
        stepsList := make([][]*msgs.Step, len(steps))
15✔
382

15✔
383
        for i, outlineStep := range steps {
40✔
384
                for _, example := range examples {
50✔
385
                        stepsList[i] = append(stepsList[i], s.stepsFromExamples(outlineStep, example)...)
25✔
386
                }
25✔
387
        }
388

389
        var newSteps []*msgs.Step
15✔
390

15✔
391
        if len(stepsList) == 0 {
15✔
UNCOV
392
                return newSteps
×
UNCOV
393
        }
×
394

395
        for ei := range examples {
30✔
396
                for ci := range examples[ei].TableBody {
35✔
397
                        for si := range steps {
60✔
398
                                newSteps = append(newSteps, stepsList[si][ci])
40✔
399
                        }
40✔
400
                }
401
        }
402

403
        return newSteps
15✔
404
}
405

406
func (s *Suite) stepsFromExamples(
407
        sourceStep *msgs.Step,
408
        example *msgs.Examples) []*msgs.Step {
25✔
409
        steps := []*msgs.Step{}
25✔
410

25✔
411
        placeholdersValues := []string{}
25✔
412

25✔
413
        if example.TableHeader != nil {
45✔
414
                placeholders := example.TableHeader.Cells
20✔
415
                for _, placeholder := range placeholders {
80✔
416
                        ph := "<" + placeholder.Value + ">"
60✔
417
                        placeholdersValues = append(placeholdersValues, ph)
60✔
418
                }
60✔
419
        }
420

421
        text := sourceStep.Text
25✔
422

25✔
423
        for _, row := range example.TableBody {
65✔
424
                // iterate over the cells and update the text
40✔
425
                stepText, expr := s.stepFromExample(text, row, placeholdersValues)
40✔
426

40✔
427
                // find step definition for the new step
40✔
428
                def, err := s.findStepDef(stepText)
40✔
429
                if err != nil {
40✔
UNCOV
430
                        continue
×
431
                }
432

433
                // add the step to the list
434
                s.AddStep(expr, def.f)
40✔
435

40✔
436
                // clone a step
40✔
437
                step := &msgs.Step{
40✔
438
                        Location:    sourceStep.Location,
40✔
439
                        Keyword:     sourceStep.Keyword,
40✔
440
                        Text:        stepText,
40✔
441
                        KeywordType: sourceStep.KeywordType,
40✔
442
                        DocString:   sourceStep.DocString,
40✔
443
                        DataTable:   sourceStep.DataTable,
40✔
444
                        Id:          sourceStep.Id,
40✔
445
                }
40✔
446

40✔
447
                steps = append(steps, step)
40✔
448
        }
449

450
        return steps
25✔
451
}
452

453
func (s *Suite) stepFromExample(
454
        stepName string,
455
        row *msgs.TableRow, placeholders []string) (string, string) {
45✔
456
        expr := stepName
45✔
457

45✔
458
        for i, ph := range placeholders {
175✔
459
                t := getRegexpForVar(row.Cells[i].Value)
130✔
460
                expr = strings.ReplaceAll(expr, ph, t)
130✔
461
                stepName = strings.ReplaceAll(stepName, ph, row.Cells[i].Value)
130✔
462
        }
130✔
463

464
        return stepName, expr
45✔
465
}
466

467
func (s *Suite) callBeforeScenarios(ctx Context) {
155✔
468
        for _, f := range s.options.beforeScenario {
160✔
469
                f(ctx)
5✔
470
        }
5✔
471
}
472

473
func (s *Suite) callAfterScenarios(ctx Context) {
155✔
474
        for _, f := range s.options.afterScenario {
160✔
475
                f(ctx)
5✔
476
        }
5✔
477
}
478

479
func (s *Suite) callBeforeSteps(ctx Context) {
300✔
480
        for _, f := range s.options.beforeStep {
330✔
481
                f(ctx)
30✔
482
        }
30✔
483
}
484

485
func (s *Suite) callAfterSteps(ctx Context) {
300✔
486
        for _, f := range s.options.afterStep {
330✔
487
                f(ctx)
30✔
488
        }
30✔
489
}
490
func (s *Suite) runRule(feature *msgs.Feature, rule *msgs.Rule,
491
        backgrounds []*msgs.Background, t *testing.T) {
40✔
492
        ruleTags := feature.Tags
40✔
493
        ruleTags = append(ruleTags, rule.Tags...)
40✔
494

40✔
495
        if s.shouldSkipFeatureOrRule(ruleTags) {
45✔
496
                s.t.Logf("the rule (%s) is ignored ", feature.Name)
5✔
497
                return
5✔
498
        }
5✔
499

500
        ruleBackgrounds := []*msgs.Background{}
35✔
501
        ruleBackgrounds = append(ruleBackgrounds, backgrounds...)
35✔
502

35✔
503
        t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(rule.Keyword), rule.Name), func(t *testing.T) {
70✔
504
                for _, ruleChild := range rule.Children {
90✔
505
                        if ruleChild.Background != nil {
70✔
506
                                ruleBackgrounds = append(ruleBackgrounds, ruleChild.Background)
15✔
507
                        }
15✔
508
                        if scenario := ruleChild.Scenario; scenario != nil {
95✔
509
                                ctx := NewContext()
40✔
510
                                ctx.Set(FeatureKey{}, feature)
40✔
511
                                ctx.Set(RuleKey{}, rule)
40✔
512
                                s.runScenario(ctx, scenario, ruleBackgrounds, t, ruleTags)
40✔
513
                        }
40✔
514
                }
515
        })
516
}
517
func (s *Suite) runScenario(ctx Context, scenario *msgs.Scenario,
518
        backgrounds []*msgs.Background, t *testing.T, parentTags []*msgs.Tag) {
180✔
519
        if s.shouldSkipScenario(append(parentTags, scenario.Tags...)) {
205✔
520
                t.Logf("Skipping scenario %s", scenario.Name)
25✔
521
                return
25✔
522
        }
25✔
523

524
        t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(scenario.Keyword), scenario.Name), func(t *testing.T) {
310✔
525
                // NOTE consider passing t as argument to scenario hooks
155✔
526
                ctx.Set(ScenarioKey{}, scenario)
155✔
527
                ctx.Set(TestingTKey{}, t)
155✔
528
                defer ctx.Set(TestingTKey{}, nil)
155✔
529

155✔
530
                s.callBeforeScenarios(ctx)
155✔
531
                defer s.callAfterScenarios(ctx)
155✔
532

155✔
533
                if len(backgrounds) > 0 {
185✔
534
                        steps := s.getBackgroundSteps(backgrounds)
30✔
535
                        s.runSteps(ctx, t, steps)
30✔
536
                }
30✔
537
                steps := scenario.Steps
155✔
538
                if examples := scenario.Examples; len(examples) > 0 {
170✔
539
                        c := ctx.Clone()
15✔
540
                        steps = s.getOutlineStep(scenario.Steps, examples)
15✔
541
                        s.runSteps(c, t, steps)
15✔
542
                } else {
155✔
543
                        c := ctx.Clone()
140✔
544
                        s.runSteps(c, t, steps)
140✔
545
                }
140✔
546
        })
547
}
548

549
func (s *Suite) runSteps(ctx Context, t *testing.T, steps []*msgs.Step) {
185✔
550
        for _, step := range steps {
485✔
551
                s.runStep(ctx, t, step)
300✔
552
        }
300✔
553
}
554

555
func (s *Suite) runStep(ctx Context, t *testing.T, step *msgs.Step) {
300✔
556
        defer func() {
600✔
557
                if r := recover(); r != nil {
300✔
UNCOV
558
                        t.Error(r)
×
UNCOV
559
                }
×
560
        }()
561

562
        def, err := s.findStepDef(step.Text)
300✔
563
        if err != nil {
300✔
UNCOV
564
                t.Fatalf("cannot find step definition for step: %s%s", step.Keyword, step.Text)
×
UNCOV
565
        }
×
566

567
        matches := def.expr.FindSubmatch([]byte(step.Text))[1:]
300✔
568
        params := make([]interface{}, 0, len(matches)) // defining the slices capacity instead of the length to use append
300✔
569
        for _, m := range matches {
670✔
570
                params = append(params, m)
370✔
571
        }
370✔
572

573
        if step.DocString != nil {
325✔
574
                params = append(params, step.DocString.Content)
25✔
575
        }
25✔
576
        if step.DataTable != nil {
305✔
577
                params = append(params, *step.DataTable)
5✔
578
        }
5✔
579

580
        t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(step.Keyword), step.Text), func(t *testing.T) {
600✔
581
                // NOTE consider passing t as argument to step hooks
300✔
582
                ctx.Set(TestingTKey{}, t)
300✔
583
                defer ctx.Set(TestingTKey{}, nil)
300✔
584

300✔
585
                s.callBeforeSteps(ctx)
300✔
586
                defer s.callAfterSteps(ctx)
300✔
587

300✔
588
                def.run(ctx, t, params)
300✔
589
        })
300✔
590
}
591

592
func (def *stepDef) run(ctx Context, t TestingT, params []interface{}) { // nolint:interfacer
315✔
593
        defer func() {
630✔
594
                if r := recover(); r != nil {
320✔
595
                        t.Errorf("%+v", r)
5✔
596
                }
5✔
597
        }()
598

599
        d := reflect.ValueOf(def.f)
315✔
600
        if len(params)+2 != d.Type().NumIn() {
315✔
UNCOV
601
                t.Fatalf("the step function %s accepts %d arguments but %d received", d.String(), d.Type().NumIn(), len(params)+2)
×
602

×
603
                return
×
604
        }
×
605

606
        in := []reflect.Value{reflect.ValueOf(t), reflect.ValueOf(ctx)}
315✔
607

315✔
608
        for i, v := range params {
715✔
609
                if len(params) < i+1 {
400✔
UNCOV
610
                        break
×
611
                }
612

613
                inType := d.Type().In(i + 2)
400✔
614

400✔
615
                paramType, err := paramType(v, inType)
400✔
616
                if err != nil {
400✔
UNCOV
617
                        t.Fatal(err)
×
618
                }
×
619

620
                in = append(in, paramType)
400✔
621
        }
622

623
        d.Call(in)
315✔
624
}
625

626
func paramType(param interface{}, inType reflect.Type) (reflect.Value, error) {
400✔
627
        switch inType.Kind() { // nolint:exhaustive - the linter does not recognize 'default:' to satisfy exhaustiveness
400✔
628
        case reflect.String:
150✔
629
                s, err := shouldBeString(param)
150✔
630
                return reflect.ValueOf(s), err
150✔
631
        case reflect.Int:
215✔
632
                v, err := shouldBeInt(param)
215✔
633
                return reflect.ValueOf(v), err
215✔
634
        case reflect.Float32:
30✔
635
                v, err := shouldBeFloat(param, 32)
30✔
636
                return reflect.ValueOf(float32(v)), err
30✔
UNCOV
637
        case reflect.Float64:
×
UNCOV
638
                v, err := shouldBeFloat(param, 64)
×
UNCOV
639
                return reflect.ValueOf(v), err
×
640
        case reflect.Slice:
×
UNCOV
641
                // only []byte is supported
×
642
                if inType != reflect.TypeOf([]byte(nil)) {
×
643
                        return reflect.Value{}, fmt.Errorf("the slice argument type %s is not supported", inType.Kind())
×
644
                }
×
645

646
                v, err := shouldBeByteSlice(param)
×
647

×
648
                return reflect.ValueOf(v), err
×
649
        case reflect.Struct:
5✔
650
                // the only struct supported is the one introduced by cucumber
5✔
651
                if inType != reflect.TypeOf(msgs.DataTable{}) {
5✔
652
                        return reflect.Value{}, fmt.Errorf("the struct argument type %s is not supported", inType.Kind())
×
653
                }
×
654

655
                v, err := shouldBeDataTable(param)
5✔
656

5✔
657
                return reflect.ValueOf(v), err
5✔
658
        default:
×
UNCOV
659
                return reflect.Value{}, fmt.Errorf("the type %s is not supported", inType.Kind())
×
660
        }
661
}
662

663
func shouldBeDataTable(input interface{}) (msgs.DataTable, error) {
5✔
664
        if v, ok := input.(msgs.DataTable); ok {
10✔
665
                return v, nil
5✔
666
        }
5✔
667

UNCOV
668
        return msgs.DataTable{}, fmt.Errorf("cannot convert %v of type %T to messages.DataTable", input, input)
×
669
}
670

UNCOV
671
func shouldBeByteSlice(input interface{}) ([]byte, error) {
×
UNCOV
672
        if v, ok := input.([]byte); ok {
×
673
                return v, nil
×
UNCOV
674
        }
×
675

676
        return nil, fmt.Errorf("cannot convert %v of type %T to []byte", input, input)
×
677
}
678

679
func shouldBeInt(input interface{}) (int, error) {
215✔
680
        s, err := shouldBeString(input)
215✔
681
        if err != nil {
215✔
UNCOV
682
                return 0, err
×
UNCOV
683
        }
×
684

685
        return strconv.Atoi(s)
215✔
686
}
687

688
func shouldBeFloat(input interface{}, bitSize int) (float64, error) {
30✔
689
        s, err := shouldBeString(input)
30✔
690
        if err != nil {
30✔
UNCOV
691
                return 0, err
×
UNCOV
692
        }
×
693

694
        return strconv.ParseFloat(s, bitSize)
30✔
695
}
696

697
func shouldBeString(input interface{}) (string, error) {
395✔
698
        switch v := input.(type) {
395✔
699
        case string:
25✔
700
                return v, nil
25✔
701
        case []byte:
370✔
702
                return string(v), nil
370✔
UNCOV
703
        default:
×
UNCOV
704
                return "", fmt.Errorf("cannot convert %v of type %T to string", input, input)
×
705
        }
706
}
707

708
func (s *Suite) findStepDef(text string) (stepDef, error) {
340✔
709
        var sd stepDef
340✔
710

340✔
711
        found := 0
340✔
712

340✔
713
        for _, step := range s.steps {
4,540✔
714
                if !step.expr.MatchString(text) {
7,930✔
715
                        continue
3,730✔
716
                }
717

718
                if l := len(step.expr.FindAll([]byte(text), -1)); l > found {
810✔
719
                        found = l
340✔
720
                        sd = step
340✔
721
                }
340✔
722
        }
723

724
        if reflect.DeepEqual(sd, stepDef{}) {
340✔
UNCOV
725
                return sd, errors.New("cannot find step definition")
×
UNCOV
726
        }
×
727

728
        return sd, nil
340✔
729
}
730

731
func (s *Suite) shouldSkipFeatureOrRule(featureOrRuleTags []*msgs.Tag) bool {
140✔
732
        for _, tag := range featureOrRuleTags {
155✔
733
                if contains(s.options.ignoreTags, tag.Name) {
25✔
734
                        return true
10✔
735
                }
10✔
736
        }
737

738
        return false
130✔
739
}
740

741
func (s *Suite) shouldSkipScenario(scenarioTags []*msgs.Tag) bool {
180✔
742
        for _, tag := range scenarioTags {
205✔
743
                if contains(s.options.ignoreTags, tag.Name) {
30✔
744
                        return true
5✔
745
                }
5✔
746
        }
747

748
        if len(s.options.tags) == 0 {
310✔
749
                return false
135✔
750
        }
135✔
751

752
        for _, tag := range scenarioTags {
60✔
753
                if contains(s.options.tags, tag.Name) {
40✔
754
                        return false
20✔
755
                }
20✔
756
        }
757

758
        return true
20✔
759
}
760

761
func (s *Suite) getBackgroundSteps(backgrounds []*msgs.Background) []*msgs.Step {
30✔
762
        result := []*msgs.Step{}
30✔
763
        for _, background := range backgrounds {
75✔
764
                result = append(result, background.Steps...)
45✔
765
        }
45✔
766

767
        return result
30✔
768
}
769

770
// contains tells whether a contains x.
771
func contains(a []string, x string) bool {
60✔
772
        for _, n := range a {
95✔
773
                if x == n {
70✔
774
                        return true
35✔
775
                }
35✔
776
        }
777

778
        return false
25✔
779
}
780

781
func getRegexpForVar(v interface{}) string {
130✔
782
        s := v.(string)
130✔
783

130✔
784
        if _, err := strconv.Atoi(s); err == nil {
260✔
785
                return "(\\d+)"
130✔
786
        }
130✔
787

UNCOV
788
        if _, err := strconv.ParseFloat(s, 32); err == nil {
×
UNCOV
789
                return "([+-]?([0-9]*[.])?[0-9]+)"
×
UNCOV
790
        }
×
791

UNCOV
792
        return "(.*)"
×
793
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc