Skip to content

Commit ecd2dfe

Browse files
authored
Ambiguous step detection - add support to all formatters (#648)
* added the missing impl of json/events/junit/pretty - still need 'progress' and 'junit,pretty' * added tests for "progress formatter" * switched from tabs to spaces in the ambiguous steps error message * rename some_scenarions_including_failing to some_scenarios_including_failing * changelog
1 parent 223efc3 commit ecd2dfe

18 files changed

+285
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
99
## Unreleased
1010

1111
- Improved the type checking of step return types and improved the error messages - ([647](https://github.com/cucumber/godog/pull/647) - [johnlon](https://github.com/johnlon))
12-
- Ambiguous step definitions will now be detected when strict mode is activated - ([636](https://github.com/cucumber/godog/pull/636) - [johnlon](https://github.com/johnlon))
12+
- Ambiguous step definitions will now be detected when strict mode is activated - ([636](https://github.com/cucumber/godog/pull/636)/([648](https://github.com/cucumber/godog/pull/648) - [johnlon](https://github.com/johnlon))
1313
- Provide support for attachments / embeddings including a new example in the examples dir - ([623](https://github.com/cucumber/godog/pull/623) - [johnlon](https://github.com/johnlon))
1414

1515
## [v0.14.1]

internal/formatters/fmt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ var (
3636
skipped = models.Skipped
3737
undefined = models.Undefined
3838
pending = models.Pending
39-
ambiguous = models.Skipped
39+
ambiguous = models.Ambiguous
4040
)
4141

4242
type sortFeaturesByName []*models.Feature

internal/formatters/fmt_base.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (f *Base) Ambiguous(*messages.Pickle, *messages.PickleStep, *formatters.Ste
9292
// Summary renders summary information.
9393
func (f *Base) Summary() {
9494
var totalSc, passedSc, undefinedSc int
95-
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
95+
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt, ambiguousSt int
9696

9797
pickleResults := f.Storage.MustGetPickleResults()
9898
for _, pr := range pickleResults {
@@ -114,6 +114,9 @@ func (f *Base) Summary() {
114114
case failed:
115115
prStatus = failed
116116
failedSt++
117+
case ambiguous:
118+
prStatus = ambiguous
119+
ambiguousSt++
117120
case skipped:
118121
skippedSt++
119122
case undefined:
@@ -144,6 +147,10 @@ func (f *Base) Summary() {
144147
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
145148
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
146149
}
150+
if ambiguousSt > 0 {
151+
parts = append(parts, yellow(fmt.Sprintf("%d ambiguous", ambiguousSt)))
152+
steps = append(steps, yellow(fmt.Sprintf("%d ambiguous", ambiguousSt)))
153+
}
147154
if undefinedSt > 0 {
148155
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
149156
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))

internal/formatters/fmt_cucumber.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (f *Cuke) buildCukeStep(pickle *messages.Pickle, stepResult models.PickleSt
299299
cukeStep.Result.Error = stepResult.Err.Error()
300300
}
301301

302-
if stepResult.Status == undefined || stepResult.Status == pending {
302+
if stepResult.Status == undefined || stepResult.Status == pending || stepResult.Status == ambiguous {
303303
cukeStep.Match.Location = fmt.Sprintf("%s:%d", pickle.Uri, step.Location.Line)
304304
}
305305

internal/formatters/fmt_events.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (f *Events) step(pickle *messages.Pickle, pickleStep *messages.PickleStep)
198198
pickleStepResults := f.Storage.MustGetPickleStepResultsByPickleID(pickle.Id)
199199
for _, stepResult := range pickleStepResults {
200200
switch stepResult.Status {
201-
case passed, failed, undefined, pending:
201+
case passed, failed, undefined, pending, ambiguous:
202202
status = stepResult.Status.String()
203203
}
204204
}
@@ -318,6 +318,16 @@ func (f *Events) Pending(pickle *messages.Pickle, step *messages.PickleStep, mat
318318
f.step(pickle, step)
319319
}
320320

321+
// Ambiguous captures ambiguous step.
322+
func (f *Events) Ambiguous(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) {
323+
f.Base.Ambiguous(pickle, step, match, err)
324+
325+
f.Lock.Lock()
326+
defer f.Lock.Unlock()
327+
328+
f.step(pickle, step)
329+
}
330+
321331
func (f *Events) scenarioLocation(pickle *messages.Pickle) string {
322332
feature := f.Storage.MustGetFeature(pickle.Uri)
323333
scenario := feature.FindScenario(pickle.AstNodeIds[0])

internal/formatters/fmt_junit.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ func (f *JUnit) buildJUNITPackageSuite() JunitPackageSuite {
117117
tc.Failure = &junitFailure{
118118
Message: fmt.Sprintf("Step %s: %s", pickleStep.Text, stepResult.Err),
119119
}
120+
case ambiguous:
121+
tc.Status = ambiguous.String()
122+
tc.Error = append(tc.Error, &junitError{
123+
Type: "ambiguous",
124+
Message: fmt.Sprintf("Step %s", pickleStep.Text),
125+
})
120126
case skipped:
121127
tc.Error = append(tc.Error, &junitError{
122128
Type: "skipped",

internal/formatters/fmt_output_test.go

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func fmtOutputTest(fmtName, testName, featureFilePath string) func(*testing.T) {
8585
att := godog.Attachments(ctx)
8686
attCount := len(att)
8787
if attCount != 4 {
88-
assert.FailNow(tT, "Unexpected attachements: "+sc.Name, "expected 4, found %d", attCount)
88+
assert.FailNow(tT, "Unexpected attachments: "+sc.Name, "expected 4, found %d", attCount)
8989
}
9090
ctx = godog.Attach(ctx,
9191
godog.Attachment{Body: []byte("AfterScenarioAttachment"), FileName: "After Scenario Attachment 2", MediaType: "text/plain"},
@@ -144,12 +144,15 @@ func fmtOutputTest(fmtName, testName, featureFilePath string) func(*testing.T) {
144144
ctx.Step(`^(?:a )?failing step`, failingStepDef)
145145
ctx.Step(`^(?:a )?pending step$`, pendingStepDef)
146146
ctx.Step(`^(?:a )?passing step$`, passingStepDef)
147+
ctx.Step(`^ambiguous step.*$`, ambiguousStepDef)
148+
ctx.Step(`^ambiguous step$`, ambiguousStepDef)
147149
ctx.Step(`^odd (\d+) and even (\d+) number$`, oddEvenStepDef)
148150
ctx.Step(`^(?:a )?a step with a single attachment call for multiple attachments$`, stepWithSingleAttachmentCall)
149151
ctx.Step(`^(?:a )?a step with multiple attachment calls$`, stepWithMultipleAttachmentCalls)
150152
}
151153

152154
return func(t *testing.T) {
155+
fmt.Printf("fmt_output_test for format %10s : sample file %v\n", fmtName, featureFilePath)
153156
expectOutputPath := strings.Replace(featureFilePath, "features", fmtName, 1)
154157
expectOutputPath = strings.TrimSuffix(expectOutputPath, path.Ext(expectOutputPath))
155158
if _, err := os.Stat(expectOutputPath); err != nil {
@@ -167,6 +170,7 @@ func fmtOutputTest(fmtName, testName, featureFilePath string) func(*testing.T) {
167170
Format: fmtName,
168171
Paths: []string{featureFilePath},
169172
Output: out,
173+
Strict: true,
170174
}
171175

172176
godog.TestSuite{
@@ -178,7 +182,14 @@ func fmtOutputTest(fmtName, testName, featureFilePath string) func(*testing.T) {
178182
// normalise on unix line ending so expected vs actual works cross platform
179183
expected := normalise(string(expectedOutput))
180184
actual := normalise(buf.String())
185+
181186
assert.Equalf(t, expected, actual, "path: %s", expectOutputPath)
187+
188+
// display as a side by side listing as the output of the assert is all one line with embedded newlines and useless
189+
if expected != actual {
190+
fmt.Printf("Error: fmt: %s, path: %s\n", fmtName, expectOutputPath)
191+
compareLists(expected, actual)
192+
}
182193
}
183194
}
184195

@@ -192,9 +203,17 @@ func normalise(s string) string {
192203
return normalised
193204
}
194205

195-
func passingStepDef() error { return nil }
206+
func passingStepDef() error {
207+
return nil
208+
}
196209

197-
func oddEvenStepDef(odd, even int) error { return oddOrEven(odd, even) }
210+
func ambiguousStepDef() error {
211+
return nil
212+
}
213+
214+
func oddEvenStepDef(odd, even int) error {
215+
return oddOrEven(odd, even)
216+
}
198217

199218
func oddOrEven(odd, even int) error {
200219
if odd%2 == 0 {
@@ -239,3 +258,88 @@ func stepWithMultipleAttachmentCalls(ctx context.Context) (context.Context, erro
239258

240259
return ctx, nil
241260
}
261+
262+
// wrapString wraps a string into chunks of the given width.
263+
func wrapString(s string, width int) []string {
264+
var result []string
265+
for len(s) > width {
266+
result = append(result, s[:width])
267+
s = s[width:]
268+
}
269+
result = append(result, s)
270+
return result
271+
}
272+
273+
// compareLists compares two lists of strings and prints them with wrapped text.
274+
func compareLists(expected, actual string) {
275+
list1 := strings.Split(expected, "\n")
276+
list2 := strings.Split(actual, "\n")
277+
278+
// Get the length of the longer list
279+
maxLength := len(list1)
280+
if len(list2) > maxLength {
281+
maxLength = len(list2)
282+
}
283+
284+
colWid := 60
285+
fmtTitle := fmt.Sprintf("%%4s: %%-%ds | %%-%ds\n", colWid+2, colWid+2)
286+
fmtData := fmt.Sprintf("%%4d: %%-%ds | %%-%ds %%s\n", colWid+2, colWid+2)
287+
288+
fmt.Printf(fmtTitle, "#", "expected", "actual")
289+
290+
for i := 0; i < maxLength; i++ {
291+
var val1, val2 string
292+
293+
// Get the value from list1 if it exists
294+
if i < len(list1) {
295+
val1 = list1[i]
296+
} else {
297+
val1 = "N/A"
298+
}
299+
300+
// Get the value from list2 if it exists
301+
if i < len(list2) {
302+
val2 = list2[i]
303+
} else {
304+
val2 = "N/A"
305+
}
306+
307+
// Wrap both strings into slices of strings with fixed width
308+
wrapped1 := wrapString(val1, colWid)
309+
wrapped2 := wrapString(val2, colWid)
310+
311+
// Find the number of wrapped lines needed for the current pair
312+
maxWrappedLines := len(wrapped1)
313+
if len(wrapped2) > maxWrappedLines {
314+
maxWrappedLines = len(wrapped2)
315+
}
316+
317+
// Print the wrapped lines with alignment
318+
for j := 0; j < maxWrappedLines; j++ {
319+
var line1, line2 string
320+
321+
// Get the wrapped line or use an empty string if it doesn't exist
322+
if j < len(wrapped1) {
323+
line1 = wrapped1[j]
324+
} else {
325+
line1 = ""
326+
}
327+
328+
if j < len(wrapped2) {
329+
line2 = wrapped2[j]
330+
} else {
331+
line2 = ""
332+
}
333+
334+
status := "same"
335+
// if val1 != val2 {
336+
if line1 != line2 {
337+
status = "different"
338+
}
339+
340+
delim := "¬"
341+
// Print the wrapped lines with fixed-width column
342+
fmt.Printf(fmtData, i+1, delim+line1+delim, delim+line2+delim, status)
343+
}
344+
}
345+
}

internal/formatters/fmt_pretty.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ func (f *Pretty) Failed(pickle *messages.Pickle, step *messages.PickleStep, matc
114114
f.printStep(pickle, step)
115115
}
116116

117+
// Failed captures failed step.
118+
func (f *Pretty) Ambiguous(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) {
119+
f.Base.Ambiguous(pickle, step, match, err)
120+
121+
f.Lock.Lock()
122+
defer f.Lock.Unlock()
123+
124+
f.printStep(pickle, step)
125+
}
126+
117127
// Pending captures pending step.
118128
func (f *Pretty) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) {
119129
f.Base.Pending(pickle, step, match)
@@ -269,6 +279,9 @@ func (f *Pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps in
269279
case result.Status == failed:
270280
errorMsg = result.Err.Error()
271281
clr = result.Status.Color()
282+
case result.Status == ambiguous:
283+
errorMsg = result.Err.Error()
284+
clr = result.Status.Color()
272285
case result.Status == undefined || result.Status == pending:
273286
clr = result.Status.Color()
274287
case result.Status == skipped && clr == nil:

internal/formatters/fmt_progress.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ func (f *Progress) step(pickleStepID string) {
9898
fmt.Fprint(f.out, red("F"))
9999
case undefined:
100100
fmt.Fprint(f.out, yellow("U"))
101+
case ambiguous:
102+
fmt.Fprint(f.out, yellow("A"))
101103
case pending:
102104
fmt.Fprint(f.out, yellow("P"))
103105
}
@@ -149,6 +151,16 @@ func (f *Progress) Failed(pickle *messages.Pickle, step *messages.PickleStep, ma
149151
f.step(step.Id)
150152
}
151153

154+
// Ambiguous steps.
155+
func (f *Progress) Ambiguous(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition, err error) {
156+
f.Base.Ambiguous(pickle, step, match, err)
157+
158+
f.Lock.Lock()
159+
defer f.Lock.Unlock()
160+
161+
f.step(step.Id)
162+
}
163+
152164
// Pending captures pending step.
153165
func (f *Progress) Pending(pickle *messages.Pickle, step *messages.PickleStep, match *formatters.StepDefinition) {
154166
f.Base.Pending(pickle, step, match)

internal/formatters/formatter-tests/cucumber/some_scenarions_including_failing renamed to internal/formatters/formatter-tests/cucumber/some_scenarios_including_failing

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"uri": "formatter-tests/features/some_scenarions_including_failing.feature",
3+
"uri": "formatter-tests/features/some_scenarios_including_failing.feature",
44
"id": "some-scenarios",
55
"keyword": "Feature",
66
"name": "some scenarios",
@@ -66,7 +66,7 @@
6666
"name": "pending step",
6767
"line": 9,
6868
"match": {
69-
"location": "formatter-tests/features/some_scenarions_including_failing.feature:9"
69+
"location": "formatter-tests/features/some_scenarios_including_failing.feature:9"
7070
},
7171
"result": {
7272
"status": "pending"
@@ -98,7 +98,7 @@
9898
"name": "undefined",
9999
"line": 13,
100100
"match": {
101-
"location": "formatter-tests/features/some_scenarions_including_failing.feature:13"
101+
"location": "formatter-tests/features/some_scenarios_including_failing.feature:13"
102102
},
103103
"result": {
104104
"status": "undefined"
@@ -116,6 +116,39 @@
116116
}
117117
}
118118
]
119+
},
120+
{
121+
"id": "some-scenarios;ambiguous",
122+
"keyword": "Scenario",
123+
"name": "ambiguous",
124+
"description": "",
125+
"line": 16,
126+
"type": "scenario",
127+
"steps": [
128+
{
129+
"keyword": "When ",
130+
"name": "ambiguous step",
131+
"line": 17,
132+
"match": {
133+
"location": "formatter-tests/features/some_scenarios_including_failing.feature:17"
134+
},
135+
"result": {
136+
"status": "ambiguous",
137+
"error_message": "ambiguous step definition, step text: ambiguous step\n matches:\n ^ambiguous step.*$\n ^ambiguous step$"
138+
}
139+
},
140+
{
141+
"keyword": "Then ",
142+
"name": "passing step",
143+
"line": 18,
144+
"match": {
145+
"location": "fmt_output_test.go:XXX"
146+
},
147+
"result": {
148+
"status": "skipped"
149+
}
150+
}
151+
]
119152
}
120153
]
121154
}

0 commit comments

Comments
 (0)