Skip to content

Commit 226f0fb

Browse files
format: report total steps correctly in progress bar (#1669)
* make cck fail: remove reordering of testCase messages * add new function to deal with testCase * dont emit testCase from PickleRunner * include in result * fix up some tests * move tests to right places * emit test cases from serial runtime * scrappy impl to get serial working * remove unused field * refactor structures, fix tests * make coordinator api more promisey * start to hook up parallel * assemble test cases without ITestStep * remove unused function * TestCase is source of truth * TestCaseRunner is more accurate than PickleRunner? * make parallel runtime work with this * add explanatory comment * fix progress bar formatter counts * changelog * remove temp tag Co-authored-by: Aurélien Reeves <aurelien.reeves@smartbear.com> * clarify changelog entry audience * cleanup Co-authored-by: Aurélien Reeves <aurelien.reeves@smartbear.com>
1 parent ee90922 commit 226f0fb

16 files changed

+732
-437
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
1616

1717
### Changed
1818

19+
* All `testCase` messages now emitted upfront at the start of the run (relevant for formatter authors) ([#1408](https://github.com/cucumber/cucumber-js/issues/1408)
20+
[#1669](https://github.com/cucumber/cucumber-js/pull/1669))
1921
* Clarify that the JSON formatter will not be removed any time soon
2022

2123
### Deprecated
@@ -24,6 +26,8 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
2426

2527
### Fixed
2628

29+
* Progress bar formatter now reports total step count correctly ([#1579](https://github.com/cucumber/cucumber-js/issues/1579)
30+
[#1669](https://github.com/cucumber/cucumber-js/pull/1669))
2731
* All messages now emitted with project-relative `uri`s
2832
([#1534](https://github.com/cucumber/cucumber-js/issues/1534)
2933
[#1672](https://github.com/cucumber/cucumber-js/pull/1672))

compatibility/cck_spec.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import path from 'path'
77
import { PassThrough, pipeline, Writable } from 'stream'
88
import { Cli } from '../src'
99
import toString from 'stream-to-string'
10-
import { doesHaveValue, doesNotHaveValue } from '../src/value_checker'
1110
import { normalizeMessageOutput } from '../features/support/formatter_output_helpers'
1211
import * as messages from '@cucumber/messages'
1312
import * as messageStreams from '@cucumber/message-streams'
@@ -103,16 +102,5 @@ function normalize(messages: any[]): any[] {
103102
messages,
104103
path.join(PROJECT_PATH, 'compatibility')
105104
)
106-
const testCases: any[] = messages.filter((message) =>
107-
doesHaveValue(message.testCase)
108-
)
109-
const everythingElse: any[] = messages.filter((message) =>
110-
doesNotHaveValue(message.testCase)
111-
)
112-
const testRunStarted = everythingElse.findIndex((message) =>
113-
doesHaveValue(message.testRunStarted)
114-
)
115-
// move all `testCase` messages to just after `testRunStarted`
116-
everythingElse.splice(testRunStarted + 1, 0, ...testCases)
117-
return everythingElse
105+
return messages
118106
}

src/cli/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,17 +228,13 @@ export default class Cli {
228228
eventBroadcaster,
229229
eventDataCollector,
230230
options: configuration.runtimeOptions,
231+
newId,
231232
pickleIds,
232233
supportCodeLibrary,
233234
supportCodePaths: configuration.supportCodePaths,
234235
supportCodeRequiredModules: configuration.supportCodeRequiredModules,
235236
})
236-
await new Promise<void>((resolve) => {
237-
parallelRuntimeCoordinator.run(configuration.parallel, (s) => {
238-
success = s
239-
resolve()
240-
})
241-
})
237+
success = await parallelRuntimeCoordinator.run(configuration.parallel)
242238
} else {
243239
const runtime = new Runtime({
244240
eventBroadcaster,

src/formatter/progress_bar_formatter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ export default class ProgressBarFormatter extends Formatter {
105105
parseEnvelope(envelope: messages.Envelope): void {
106106
if (doesHaveValue(envelope.undefinedParameterType)) {
107107
this.logUndefinedParametertype(envelope.undefinedParameterType)
108-
} else if (doesHaveValue(envelope.pickle)) {
109-
this.incrementStepCount(envelope.pickle.id)
108+
} else if (doesHaveValue(envelope.testCase)) {
109+
this.incrementStepCount(envelope.testCase.pickleId)
110110
} else if (doesHaveValue(envelope.testStepStarted)) {
111111
this.initializeProgressBar()
112112
} else if (doesHaveValue(envelope.testStepFinished)) {

src/formatter/progress_bar_formatter_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface ITestProgressBarFormatterOptions {
2727
shouldStopFn: (envelope: messages.Envelope) => boolean
2828
sources?: ITestSource[]
2929
supportCodeLibrary?: ISupportCodeLibrary
30+
pickleFilter?: (pickle: messages.Pickle) => boolean
3031
}
3132

3233
interface ITestProgressBarFormatterOutput {
@@ -39,6 +40,7 @@ async function testProgressBarFormatter({
3940
shouldStopFn,
4041
sources,
4142
supportCodeLibrary,
43+
pickleFilter = () => true,
4244
}: ITestProgressBarFormatterOptions): Promise<ITestProgressBarFormatterOutput> {
4345
if (doesNotHaveValue(supportCodeLibrary)) {
4446
supportCodeLibrary = buildSupportCodeLibrary()
@@ -48,6 +50,7 @@ async function testProgressBarFormatter({
4850
runtimeOptions,
4951
sources,
5052
supportCodeLibrary,
53+
pickleFilter,
5154
})
5255

5356
let output = ''
@@ -137,6 +140,32 @@ describe('ProgressBarFormatter', () => {
137140
// Assert
138141
expect(progressBarFormatter.progressBar.total).to.eql(5)
139142
})
143+
144+
it('initializes a progress bar with the total number of steps when some pickles filtered out', async () => {
145+
// Arrange
146+
const sources = [
147+
{
148+
data: '@yep\nFeature: a\nScenario: b\nGiven a step\nThen a step',
149+
uri: 'a.feature',
150+
},
151+
{
152+
data:
153+
'Feature: a\nScenario: b\nGiven a step\nWhen a step\nThen a step',
154+
uri: 'b.feature',
155+
},
156+
]
157+
158+
// Act
159+
const { progressBarFormatter } = await testProgressBarFormatter({
160+
shouldStopFn: (envelope) => doesHaveValue(envelope.testStepStarted),
161+
sources,
162+
pickleFilter: (pickle) =>
163+
pickle.tags.some((tag) => tag.name === '@yep'),
164+
})
165+
166+
// Assert
167+
expect(progressBarFormatter.progressBar.total).to.eql(2)
168+
})
140169
})
141170

142171
describe('testStepFinished', () => {

src/runtime/assemble_test_cases.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { EventEmitter } from 'events'
2+
import * as messages from '@cucumber/messages'
3+
import { IdGenerator } from '@cucumber/messages'
4+
import { ISupportCodeLibrary } from '../support_code_library_builder/types'
5+
import { Group } from '@cucumber/cucumber-expressions'
6+
import { doesHaveValue } from '../value_checker'
7+
import { clone } from 'lodash'
8+
9+
export declare type IAssembledTestCases = Record<string, messages.TestCase>
10+
11+
export interface IAssembleTestCasesOptions {
12+
eventBroadcaster: EventEmitter
13+
newId: IdGenerator.NewId
14+
pickles: messages.Pickle[]
15+
supportCodeLibrary: ISupportCodeLibrary
16+
}
17+
18+
export async function assembleTestCases({
19+
eventBroadcaster,
20+
newId,
21+
pickles,
22+
supportCodeLibrary,
23+
}: IAssembleTestCasesOptions): Promise<IAssembledTestCases> {
24+
const result: IAssembledTestCases = {}
25+
for (const pickle of pickles) {
26+
const { id: pickleId } = pickle
27+
const testCaseId = newId()
28+
const fromBeforeHooks: messages.TestStep[] = makeBeforeHookSteps({
29+
supportCodeLibrary,
30+
pickle,
31+
newId,
32+
})
33+
const fromStepDefinitions: messages.TestStep[] = makeSteps({
34+
pickle,
35+
supportCodeLibrary,
36+
newId,
37+
})
38+
const fromAfterHooks: messages.TestStep[] = makeAfterHookSteps({
39+
supportCodeLibrary,
40+
pickle,
41+
newId,
42+
})
43+
const testCase: messages.TestCase = {
44+
pickleId,
45+
id: testCaseId,
46+
testSteps: [
47+
...fromBeforeHooks,
48+
...fromStepDefinitions,
49+
...fromAfterHooks,
50+
],
51+
}
52+
eventBroadcaster.emit('envelope', { testCase })
53+
result[pickleId] = testCase
54+
}
55+
return result
56+
}
57+
58+
function makeAfterHookSteps({
59+
supportCodeLibrary,
60+
pickle,
61+
newId,
62+
}: {
63+
supportCodeLibrary: ISupportCodeLibrary
64+
pickle: messages.Pickle
65+
newId: IdGenerator.NewId
66+
}): messages.TestStep[] {
67+
return clone(supportCodeLibrary.afterTestCaseHookDefinitions)
68+
.reverse()
69+
.filter((hookDefinition) => hookDefinition.appliesToTestCase(pickle))
70+
.map((hookDefinition) => ({
71+
id: newId(),
72+
hookId: hookDefinition.id,
73+
}))
74+
}
75+
76+
function makeBeforeHookSteps({
77+
supportCodeLibrary,
78+
pickle,
79+
newId,
80+
}: {
81+
supportCodeLibrary: ISupportCodeLibrary
82+
pickle: messages.Pickle
83+
newId: IdGenerator.NewId
84+
}): messages.TestStep[] {
85+
return supportCodeLibrary.beforeTestCaseHookDefinitions
86+
.filter((hookDefinition) => hookDefinition.appliesToTestCase(pickle))
87+
.map((hookDefinition) => ({
88+
id: newId(),
89+
hookId: hookDefinition.id,
90+
}))
91+
}
92+
93+
function makeSteps({
94+
pickle,
95+
supportCodeLibrary,
96+
newId,
97+
}: {
98+
pickle: messages.Pickle
99+
supportCodeLibrary: ISupportCodeLibrary
100+
newId: () => string
101+
}): messages.TestStep[] {
102+
return pickle.steps.map((pickleStep) => {
103+
const stepDefinitions = supportCodeLibrary.stepDefinitions.filter(
104+
(stepDefinition) => stepDefinition.matchesStepName(pickleStep.text)
105+
)
106+
return {
107+
id: newId(),
108+
pickleStepId: pickleStep.id,
109+
stepDefinitionIds: stepDefinitions.map(
110+
(stepDefinition) => stepDefinition.id
111+
),
112+
stepMatchArgumentsLists: stepDefinitions.map((stepDefinition) => {
113+
const result = stepDefinition.expression.match(pickleStep.text)
114+
return {
115+
stepMatchArguments: result.map((arg) => {
116+
return {
117+
group: mapArgumentGroup(arg.group),
118+
parameterTypeName: arg.parameterType.name,
119+
}
120+
}),
121+
}
122+
}),
123+
}
124+
})
125+
}
126+
127+
function mapArgumentGroup(group: Group): messages.Group {
128+
return {
129+
start: group.start,
130+
value: group.value,
131+
children: doesHaveValue(group.children)
132+
? group.children.map((child) => mapArgumentGroup(child))
133+
: undefined,
134+
}
135+
}

0 commit comments

Comments
 (0)